changeset 8499:252b29ac5f43 HEAD

fts: Added support for handling multiple namespaces.
author Timo Sirainen <tss@iki.fi>
date Sun, 30 Nov 2008 01:27:19 +0200
parents fb5fedcf4deb
children 3efcdc45d111
files doc/solr-schema.xml src/lib-storage/mail-storage-private.h src/lib-storage/mail-storage.c src/lib-storage/mail-storage.h src/plugins/fts-solr/fts-backend-solr.c src/plugins/fts-solr/fts-solr-plugin.c src/plugins/fts-solr/fts-solr-plugin.h src/plugins/fts-solr/solr-connection.c src/plugins/fts-solr/solr-connection.h src/plugins/fts/fts-storage.c src/plugins/virtual/virtual-config.c src/plugins/virtual/virtual-storage.c src/plugins/virtual/virtual-storage.h
diffstat 13 files changed, 273 insertions(+), 110 deletions(-) [+]
line wrap: on
line diff
--- a/doc/solr-schema.xml	Sun Nov 30 01:26:36 2008 +0200
+++ b/doc/solr-schema.xml	Sun Nov 30 01:27:19 2008 +0200
@@ -43,6 +43,7 @@
    <field name="uidv" type="long" indexed="true" stored="true" required="true" /> 
    <field name="box" type="string" indexed="true" stored="true" required="true" /> 
    <field name="user" type="string" indexed="true" stored="true" required="true" /> 
+   <field name="ns" type="string" indexed="true" stored="true" required="false" /> 
    <field name="last_uid" type="boolean" indexed="true" stored="false" /> 
    <field name="hdr" type="text" indexed="true" stored="false" /> 
    <field name="body" type="text" indexed="true" stored="false" /> 
--- a/src/lib-storage/mail-storage-private.h	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/lib-storage/mail-storage-private.h	Sun Nov 30 01:27:19 2008 +0200
@@ -146,8 +146,8 @@
 					  ARRAY_TYPE(mailboxes) *mailboxes,
 					  bool only_with_msgs);
 	void (*get_virtual_box_patterns)(struct mailbox *box,
-					 ARRAY_TYPE(const_string) *includes,
-					 ARRAY_TYPE(const_string) *excludes);
+				ARRAY_TYPE(mailbox_virtual_patterns) *includes,
+				ARRAY_TYPE(mailbox_virtual_patterns) *excludes);
 
 	struct mail *
 		(*mail_alloc)(struct mailbox_transaction_context *t,
--- a/src/lib-storage/mail-storage.c	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/lib-storage/mail-storage.c	Sun Nov 30 01:27:19 2008 +0200
@@ -651,13 +651,16 @@
 }
 
 void mailbox_get_virtual_box_patterns(struct mailbox *box,
-				      ARRAY_TYPE(const_string) *includes,
-				      ARRAY_TYPE(const_string) *excludes)
+				ARRAY_TYPE(mailbox_virtual_patterns) *includes,
+				ARRAY_TYPE(mailbox_virtual_patterns) *excludes)
 {
 	if (box->v.get_virtual_box_patterns == NULL) {
-		const char *name = box->name;
+		struct mailbox_virtual_pattern pat;
 
-		array_append(includes, &name, 1);
+		memset(&pat, 0, sizeof(pat));
+		pat.ns = box->storage->ns;
+		pat.pattern = box->name;
+		array_append(includes, &pat, 1);
 	} else {
 		box->v.get_virtual_box_patterns(box, includes, excludes);
 	}
--- a/src/lib-storage/mail-storage.h	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/lib-storage/mail-storage.h	Sun Nov 30 01:27:19 2008 +0200
@@ -234,6 +234,12 @@
 
 };
 
+struct mailbox_virtual_pattern {
+	struct mail_namespace *ns;
+	const char *pattern;
+};
+ARRAY_DEFINE_TYPE(mailbox_virtual_patterns, struct mailbox_virtual_pattern);
+
 ARRAY_DEFINE_TYPE(mailboxes, struct mailbox *);
 
 typedef void mailbox_notify_callback_t(struct mailbox *box, void *context);
@@ -432,8 +438,8 @@
 /* If mailbox is a virtual mailbox, return all mailbox list patterns that
    are used to figure out which mailboxes belong to the virtual mailbox. */
 void mailbox_get_virtual_box_patterns(struct mailbox *box,
-				      ARRAY_TYPE(const_string) *includes,
-				      ARRAY_TYPE(const_string) *excludes);
+				ARRAY_TYPE(mailbox_virtual_patterns) *includes,
+				ARRAY_TYPE(mailbox_virtual_patterns) *excludes);
 
 /* Initialize header lookup for given headers. */
 struct mailbox_header_lookup_ctx *
--- a/src/plugins/fts-solr/fts-backend-solr.c	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/plugins/fts-solr/fts-backend-solr.c	Sun Nov 30 01:27:19 2008 +0200
@@ -13,10 +13,13 @@
 
 #define SOLR_CMDBUF_SIZE (1024*64)
 #define SOLR_MAX_ROWS 100000
+#define FTS_SOLR_MAX_BOX_INC_PATTERNS 5
+#define FTS_SOLR_MAX_BOX_EXC_PATTERNS 5
 
 struct solr_fts_backend {
 	struct fts_backend backend;
-	char *id_username;
+	char *id_username, *id_namespace;
+	struct mail_namespace *default_ns;
 };
 
 struct solr_fts_backend_build_context {
@@ -28,18 +31,23 @@
 	bool headers;
 };
 
+struct solr_virtual_uid_map_context {
+	struct fts_backend *backend;
+	struct mailbox *box;
+	string_t *vname;
+};
+
 struct fts_backend_solr_get_last_uids_context {
+	struct fts_backend *backend;
 	pool_t pool;
 	ARRAY_TYPE(fts_backend_uid_map) *last_uids;
+
+	struct mailbox *box;
+	string_t *vname;
 };
 
 static struct solr_connection *solr_conn = NULL;
 
-static void solr_quote_str(string_t *dest, const char *str)
-{
-	solr_connection_quote_str(solr_conn, dest, str);
-}
-
 static void
 xml_encode_data(string_t *dest, const unsigned char *data, unsigned int len)
 {
@@ -97,18 +105,50 @@
 	return str_c(tmp);
 }
 
+static void solr_quote(string_t *dest, const char *str)
+{
+	str_append_c(dest, '"');
+	str_append(dest, str_escape(str));
+	str_append_c(dest, '"');
+}
+
+static void solr_quote_http(string_t *dest, const char *str)
+{
+	str_append(dest, "%22");
+	solr_connection_http_escape(solr_conn, dest, str);
+	str_append(dest, "%22");
+}
+
 static struct fts_backend *
 fts_backend_solr_init(struct mailbox *box)
 {
 	const struct fts_solr_settings *set = &fts_solr_settings;
 	struct solr_fts_backend *backend;
-	const char *username = box->storage->ns->user->username;
+	struct mail_namespace *ns = box->storage->ns;
+	const char *str;
 
 	if (solr_conn == NULL)
 		solr_conn = solr_connection_init(set->url, set->debug);
 
 	backend = i_new(struct solr_fts_backend, 1);
-	backend->id_username = i_strdup(solr_escape_id_str(username));
+	str = fts_solr_settings.default_ns_prefix;
+	if (str != NULL) {
+		backend->default_ns =
+			mail_namespace_find_prefix(ns->user->namespaces, str);
+		if (backend->default_ns == NULL) {
+			i_fatal("fts_solr: default_ns setting points to "
+				"nonexisting namespace");
+		}
+	} else {
+		backend->default_ns =
+			mail_namespace_find_inbox(ns->user->namespaces);
+	}
+	str = solr_escape_id_str(ns->user->username);
+	backend->id_username = i_strdup(str);
+	if (box->storage->ns != backend->default_ns) {
+		str = solr_escape_id_str(ns->prefix);
+		backend->id_namespace = i_strdup(str);
+	}
 	backend->backend = fts_backend_solr;
 
 	if (set->substring_search)
@@ -120,20 +160,40 @@
 {
 	struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
 
+	i_free(backend->id_namespace);
 	i_free(backend->id_username);
 	i_free(backend);
 }
 
-static const char *fts_backend_solr_username(struct fts_backend *_backend)
+static void
+solr_add_ns_query(string_t *str, struct fts_backend *_backend,
+		  struct mail_namespace *ns)
 {
 	struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
 
-	return backend->id_username;
+	if (ns == backend->default_ns || *ns->prefix == '\0')
+		str_append(str, " -ns:[* TO *]");
+	else {
+		str_append(str, " ns:");
+		solr_quote(str, ns->prefix);
+	}
+}
+
+static void
+solr_add_ns_query_http(string_t *str, struct fts_backend *backend,
+		       struct mail_namespace *ns)
+{
+	string_t *tmp;
+
+	tmp = t_str_new(64);
+	solr_add_ns_query(tmp, backend, ns);
+	solr_connection_http_escape(solr_conn, str, str_c(tmp));
 }
 
 static int fts_backend_solr_get_last_uid_fallback(struct fts_backend *backend,
 						  uint32_t *last_uid_r)
 {
+	struct mailbox *box = backend->box;
 	struct mailbox_status status;
 	ARRAY_TYPE(seq_range) uids;
 	const struct seq_range *uidvals;
@@ -141,13 +201,14 @@
 	string_t *str;
 
 	str = t_str_new(256);
-	str_append(str, "fl=uid&rows=1&sort=uid%20desc&q=");
+	str_append(str, "fl=uid&rows=1&sort=uid+desc&q=");
 
-	mailbox_get_status(backend->box, STATUS_UIDVALIDITY, &status);
-	str_printfa(str, "uidv:%u%%20box:", status.uidvalidity);
-	solr_quote_str(str, backend->box->name);
-	str_append(str, "%20user:");
-	solr_quote_str(str, backend->box->storage->ns->user->username);
+	mailbox_get_status(box, STATUS_UIDVALIDITY, &status);
+	str_printfa(str, "uidv:%u+box:", status.uidvalidity);
+	solr_quote_http(str, box->name);
+	solr_add_ns_query_http(str, backend, box->storage->ns);
+	str_append(str, "+user:");
+	solr_quote_http(str, box->storage->ns->user->username);
 
 	t_array_init(&uids, 1);
 	if (solr_connection_select(solr_conn, str_c(str),
@@ -170,6 +231,7 @@
 static int fts_backend_solr_get_last_uid(struct fts_backend *backend,
 					 uint32_t *last_uid_r)
 {
+	struct mailbox *box = backend->box;
 	struct mailbox_status status;
 	ARRAY_TYPE(seq_range) uids;
 	const struct seq_range *uidvals;
@@ -177,13 +239,14 @@
 	string_t *str;
 
 	str = t_str_new(256);
-	str_append(str, "fl=uid&rows=1&q=last_uid:TRUE%20");
+	str_append(str, "fl=uid&rows=1&q=last_uid:TRUE+");
 
-	mailbox_get_status(backend->box, STATUS_UIDVALIDITY, &status);
-	str_printfa(str, "uidv:%u%%20box:", status.uidvalidity);
-	solr_quote_str(str, backend->box->name);
-	str_append(str, "%20user:");
-	solr_quote_str(str, backend->box->storage->ns->user->username);
+	mailbox_get_status(box, STATUS_UIDVALIDITY, &status);
+	str_printfa(str, "uidv:%u+box:", status.uidvalidity);
+	solr_quote_http(str, box->name);
+	solr_add_ns_query_http(str, backend, box->storage->ns);
+	str_append(str, "+user:");
+	solr_quote_http(str, box->storage->ns->user->username);
 
 	t_array_init(&uids, 1);
 	if (solr_connection_select(solr_conn, str_c(str),
@@ -205,41 +268,67 @@
 	return 0;
 }
 
+static const char *
+solr_get_vmailbox(struct fts_backend *_backend,
+		  struct mailbox *box, const char *ns_prefix,
+		  const char *mailbox, string_t *dest)
+{
+	struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+	struct mail_namespace *namespaces = box->storage->ns->user->namespaces;
+	struct mail_namespace *ns;
+
+	if (ns_prefix == NULL)
+		ns = backend->default_ns;
+	else {
+		ns = mail_namespace_find_prefix(namespaces, ns_prefix);
+		if (ns == NULL)
+			return FALSE;
+	}
+	return mail_namespace_get_vname(ns, dest, mailbox);
+}
+
 static bool
-solr_virtual_get_last_uids(const char *mailbox, uint32_t uidvalidity,
-			   uint32_t *uid, void *context)
+solr_virtual_get_last_uids(const char *ns_prefix, const char *mailbox,
+			   uint32_t uidvalidity, uint32_t *uid, void *context)
 {
 	struct fts_backend_solr_get_last_uids_context *ctx = context;
 	struct fts_backend_uid_map *map;
+	const char *vname;
+
+	vname = solr_get_vmailbox(ctx->backend, ctx->box, ns_prefix,
+				  mailbox, ctx->vname);
 
 	map = array_append_space(ctx->last_uids);
-	map->mailbox = p_strdup(ctx->pool, mailbox);
+	map->mailbox = p_strdup(ctx->pool, vname);
 	map->uidvalidity = uidvalidity;
 	map->uid = *uid;
 	return FALSE;
 }
 
-static void add_pattern_as_solr(string_t *str, const char *pattern)
+static void
+solr_add_pattern(string_t *str, const struct mailbox_virtual_pattern *pattern)
 {
-	const char *p;
+	const char *name, *p;
+
+	name = pattern->pattern;
+	if (!mail_namespace_update_name(pattern->ns, &name))
+		name = mail_namespace_fix_sep(pattern->ns, name);
 
 	/* first check if there are any wildcards in the pattern */
-	for (p = pattern; *p != '\0'; p++) {
+	for (p = name; *p != '\0'; p++) {
 		if (*p == '%' || *p == '*')
 			break;
 	}
 	if (*p == '\0') {
 		/* full mailbox name */
-		str_append_c(str, '"');
-		str_append(str, str_escape(pattern));
-		str_append_c(str, '"');
+		solr_quote(str, name);
 		return;
 	}
 
 	/* there are at least some wildcards. */
-	for (p = pattern; *p != '\0'; p++) {
+	for (p = name; *p != '\0'; p++) {
 		if (*p == '%' || *p == '*') {
-			if (p == pattern || (p[-1] != '%' && p[-1] != '*'))
+			if (p == name || (p[-1] != '%' && p[-1] != '*'))
 				str_append_c(str, '*');
 		} else {
 			if (!i_isalnum(*p))
@@ -250,12 +339,13 @@
 }
 
 static void
-fts_backend_solr_filter_mailboxes(struct solr_connection *solr_conn,
+fts_backend_solr_filter_mailboxes(struct fts_backend *_backend,
 				  string_t *str, struct mailbox *box)
 {
-	ARRAY_TYPE(const_string) includes_arr, excludes_arr;
-	const char *const *includes, *const *excludes;
-	unsigned int i, inc_count, exc_count;
+	struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+	ARRAY_TYPE(mailbox_virtual_patterns) includes_arr, excludes_arr;
+	const struct mailbox_virtual_pattern *includes, *excludes;
+	unsigned int i, inc_count, exc_count, len;
 	string_t *fq;
 
 	t_array_init(&includes_arr, 16);
@@ -269,27 +359,41 @@
 	   Solr doesn't allow them, so in that case we'll need to return
 	   all mailboxes. */
 	for (i = 0; i < inc_count; i++) {
-		if (*includes[i] == '*' || *includes[i] == '%')
+		if (*includes[i].pattern == '*' ||
+		    *includes[i].pattern == '%')
 			break;
 	}
 
 	fq = t_str_new(128);
-	if (i == inc_count) {
+	if (i == inc_count && inc_count <= FTS_SOLR_MAX_BOX_INC_PATTERNS) {
 		/* we can filter what mailboxes we want returned */
 		str_append_c(fq, '(');
 		for (i = 0; i < inc_count; i++) {
 			if (i != 0)
 				str_append(fq, " OR ");
+			str_append_c(fq, '(');
 			str_append(fq, "box:");
-			add_pattern_as_solr(fq, includes[i]);
+			solr_add_pattern(fq, &includes[i]);
+			solr_add_ns_query(fq, _backend, includes[i].ns);
+			str_append_c(fq, ')');
 		}
 		str_append_c(fq, ')');
 	}
+	exc_count = I_MIN(FTS_SOLR_MAX_BOX_EXC_PATTERNS, exc_count);
 	for (i = 0; i < exc_count; i++) {
-		if (str_len(fq) > 0)
+		if (str_len(fq) > len)
 			str_append_c(fq, ' ');
+		str_append_c(fq, '(');
 		str_append(fq, "-box:");
-		add_pattern_as_solr(fq, excludes[i]);
+		solr_add_pattern(fq, &excludes[i]);
+		if (excludes[i].ns == backend->default_ns) {
+			str_append(fq, " OR NOT");
+			solr_add_ns_query(fq, _backend, excludes[i].ns);
+		} else if (*excludes[i].ns->prefix != '\0') {
+			str_append(fq, " OR -ns:");
+			solr_quote(fq, excludes[i].ns->prefix);
+		}
+		str_append_c(fq, ')');
 	}
 	if (str_len(fq) > 0) {
 		str_append(str, "&fq=");
@@ -305,14 +409,17 @@
 	string_t *str;
 
 	memset(&ctx, 0, sizeof(ctx));
+	ctx.backend = backend;
 	ctx.pool = pool;
 	ctx.last_uids = last_uids;
+	ctx.box = backend->box;
+	ctx.vname = t_str_new(256);
 
 	str = t_str_new(256);
-	str_printfa(str, "fl=uid,box,uidv&rows=%u&q=last_uid:TRUE%%20user:",
+	str_printfa(str, "fl=uid,box,uidv,ns&rows=%u&q=last_uid:TRUE+user:",
 		    SOLR_MAX_ROWS);
-	solr_quote_str(str, backend->box->storage->ns->user->username);
-	fts_backend_solr_filter_mailboxes(solr_conn, str, backend->box);
+	solr_quote_http(str, backend->box->storage->ns->user->username);
+	fts_backend_solr_filter_mailboxes(backend, str, backend->box);
 
 	return solr_connection_select(solr_conn, str_c(str),
 				      solr_virtual_get_last_uids, &ctx,
@@ -343,6 +450,8 @@
 fts_backend_solr_add_doc_prefix(struct solr_fts_backend_build_context *ctx,
 				uint32_t uid)
 {
+	struct solr_fts_backend *backend =
+		(struct solr_fts_backend *)ctx->ctx.backend;
 	struct mailbox *box = ctx->ctx.backend->box;
 
 	str_printfa(ctx->cmd, "<doc>"
@@ -350,6 +459,11 @@
 		    "<field name=\"uidv\">%u</field>",
 		    uid, ctx->uid_validity);
 
+	if (box->storage->ns != backend->default_ns) {
+		str_append(ctx->cmd, "<field name=\"ns\">");
+		xml_encode(ctx->cmd, box->storage->ns->prefix);
+		str_append(ctx->cmd, "</field>");
+	}
 	str_append(ctx->cmd, "<field name=\"box\">");
 	xml_encode(ctx->cmd, box->name);
 	str_append(ctx->cmd, "</field><field name=\"user\">");
@@ -357,6 +471,26 @@
 	str_append(ctx->cmd, "</field>");
 }
 
+static void xml_encode_id(string_t *str, struct fts_backend *_backend,
+			  uint32_t uid, uint32_t uid_validity,
+			  const char *mailbox)
+{
+	struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+
+	if (uid != 0)
+		str_printfa(str, "%u/", uid);
+	else
+		str_append(str, "L/");
+	if (backend->id_namespace != NULL) {
+		xml_encode(str, backend->id_namespace);
+		str_append_c(str, '/');
+	}
+	str_printfa(str, "%u/", uid_validity);
+	xml_encode(str, backend->id_username);
+	str_append_c(str, '/');
+	xml_encode(str, mailbox);
+}
+
 static int
 fts_backend_solr_build_more(struct fts_backend_build_context *_ctx,
 			    uint32_t uid, const unsigned char *data,
@@ -379,11 +513,9 @@
 		ctx->prev_uid = uid;
 
 		fts_backend_solr_add_doc_prefix(ctx, uid);
-		str_printfa(cmd, "<field name=\"id\">%u/%u/",
-			    uid, ctx->uid_validity);
-		xml_encode(cmd, fts_backend_solr_username(ctx->ctx.backend));
-		str_append_c(cmd, '/');
-		xml_encode(cmd, box->name);
+		str_printfa(cmd, "<field name=\"id\">");
+		xml_encode_id(cmd, _ctx->backend, uid, ctx->uid_validity,
+			      box->name);
 		str_append(cmd, "</field>");
 
 		ctx->headers = headers;
@@ -425,10 +557,9 @@
 	   in future by reindexing some messages. */
 	fts_backend_solr_add_doc_prefix(ctx, ctx->prev_uid);
 	str_printfa(ctx->cmd, "<field name=\"last_uid\">TRUE</field>"
-		    "<field name=\"id\">L/%u/", ctx->uid_validity);
-	xml_encode(ctx->cmd, fts_backend_solr_username(ctx->ctx.backend));
-	str_append_c(ctx->cmd, '/');
-	xml_encode(ctx->cmd, box->name);
+		    "<field name=\"id\">");
+	xml_encode_id(ctx->cmd, ctx->ctx.backend, 0, ctx->uid_validity,
+		      box->name);
 	str_append(ctx->cmd, "</field></doc></add>");
 
 	solr_connection_post_more(ctx->post, str_data(ctx->cmd),
@@ -466,11 +597,9 @@
 		string_t *cmd;
 
 		cmd = t_str_new(256);
-		str_printfa(cmd, "<delete><id>%u/%u/",
-			    mail->uid, status.uidvalidity);
-		xml_encode(cmd, fts_backend_solr_username(backend));
-		str_append_c(cmd, '/');
-		xml_encode(cmd, mail->box->name);
+		str_append(cmd, "<delete><id>");
+		xml_encode_id(cmd, backend, mail->uid, status.uidvalidity,
+			      mail->box->name);
 		str_append(cmd, "</id></delete>");
 
 		(void)solr_connection_post(solr_conn, str_c(cmd));
@@ -495,13 +624,16 @@
 {
 }
 
-static bool solr_virtual_uid_map(const char *mailbox, uint32_t uidvalidity,
-				 uint32_t *uid, void *context)
+static bool solr_virtual_uid_map(const char *ns_prefix, const char *mailbox,
+				 uint32_t uidvalidity, uint32_t *uid,
+				 void *context)
 {
-	struct mailbox *box = context;
+	struct solr_virtual_uid_map_context *ctx = context;
+	const char *vname;
 
-	return mailbox_get_virtual_uid(box, mailbox, uidvalidity,
-				       *uid, uid);
+	vname = solr_get_vmailbox(ctx->backend, ctx->box, ns_prefix,
+				  mailbox, ctx->vname);
+	return mailbox_get_virtual_uid(ctx->box, vname, uidvalidity, *uid, uid);
 }
 
 static int fts_backend_solr_lookup(struct fts_backend_lookup_context *ctx,
@@ -510,6 +642,7 @@
 				   ARRAY_TYPE(fts_score_map) *scores)
 {
 	struct mailbox *box = ctx->backend->box;
+	struct solr_virtual_uid_map_context uid_map_ctx;
 	const struct fts_backend_lookup_field *fields;
 	unsigned int i, count;
 	struct mailbox_status status;
@@ -521,11 +654,11 @@
 
 	str = t_str_new(256);
 	if (!virtual) {
-		str_printfa(str, "fl=uid,score&rows=%u&sort=uid%%20asc&q=",
+		str_printfa(str, "fl=uid,score&rows=%u&sort=uid+asc&q=",
 			    status.uidnext);
 	} else {
-		str_printfa(str, "fl=uid,score,box,uidv&rows=%u"
-			    "&sort=box%%20asc,uid%%20asc&q=",
+		str_printfa(str, "fl=uid,score,box,uidv,ns&rows=%u"
+			    "&sort=box+asc,uid+asc&q=",
 			    SOLR_MAX_ROWS);
 	}
 
@@ -533,7 +666,7 @@
 	fields = array_get(&ctx->fields, &count);
 	for (i = 0; i < count; i++) {
 		if (i > 0)
-			str_append(str, "%20");
+			str_append_c(str, '+');
 
 		if ((fields[i].flags & FTS_LOOKUP_FLAG_INVERT) != 0)
 			str_append_c(str, '-');
@@ -549,18 +682,19 @@
 			/* both */
 			str_append(str, "any:");
 		}
-		solr_quote_str(str, fields[i].key);
+		solr_quote_http(str, fields[i].key);
 	}
 
 	/* use a separate filter query for selecting the mailbox. it shouldn't
 	   affect the score and there could be some caching benefits too. */
 	str_append(str, "&fq=user:");
-	solr_quote_str(str, box->storage->ns->user->username);
+	solr_quote_http(str, box->storage->ns->user->username);
 	if (virtual)
-		fts_backend_solr_filter_mailboxes(solr_conn, str, box);
+		fts_backend_solr_filter_mailboxes(ctx->backend, str, box);
 	else {
-		str_printfa(str, "%%20uidv:%u%%20box:", status.uidvalidity);
-		solr_quote_str(str, box->name);
+		str_printfa(str, "+uidv:%u+box:", status.uidvalidity);
+		solr_quote_http(str, box->name);
+		solr_add_ns_query_http(str, ctx->backend, box->storage->ns);
 	}
 
 	array_clear(maybe_uids);
@@ -568,8 +702,13 @@
 		return solr_connection_select(solr_conn, str_c(str), NULL, NULL,
 					      definite_uids, scores);
 	} else {
+		memset(&uid_map_ctx, 0, sizeof(uid_map_ctx));
+		uid_map_ctx.backend = ctx->backend;
+		uid_map_ctx.box = box;
+		uid_map_ctx.vname = t_str_new(256);
 		return solr_connection_select(solr_conn, str_c(str),
-					      solr_virtual_uid_map, box,
+					      solr_virtual_uid_map,
+					      &uid_map_ctx,
 					      definite_uids, scores);
 	}
 }
--- a/src/plugins/fts-solr/fts-solr-plugin.c	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/plugins/fts-solr/fts-solr-plugin.c	Sun Nov 30 01:27:19 2008 +0200
@@ -24,6 +24,9 @@
 			set->debug = TRUE;
 		} else if (strcmp(*tmp, "break-imap-search") == 0) {
 			set->substring_search = TRUE;
+		} else if (strcmp(*tmp, "default_ns=") == 0) {
+			i_free(set->default_ns_prefix);
+			set->default_ns_prefix = i_strdup(*tmp + 11);
 		} else {
 			i_fatal("fts_solr: Invalid setting: %s", *tmp);
 		}
--- a/src/plugins/fts-solr/fts-solr-plugin.h	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/plugins/fts-solr/fts-solr-plugin.h	Sun Nov 30 01:27:19 2008 +0200
@@ -4,7 +4,7 @@
 #include "fts-api-private.h"
 
 struct fts_solr_settings {
-	char *url;
+	char *url, *default_ns_prefix;
 	bool debug;
 	bool substring_search;
 };
--- a/src/plugins/fts-solr/solr-connection.c	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/plugins/fts-solr/solr-connection.c	Sun Nov 30 01:27:19 2008 +0200
@@ -24,6 +24,7 @@
 	SOLR_XML_CONTENT_STATE_UID,
 	SOLR_XML_CONTENT_STATE_SCORE,
 	SOLR_XML_CONTENT_STATE_MAILBOX,
+	SOLR_XML_CONTENT_STATE_NAMESPACE,
 	SOLR_XML_CONTENT_STATE_UIDVALIDITY
 };
 
@@ -34,7 +35,7 @@
 
 	uint32_t uid, uidvalidity;
 	float score;
-	char *mailbox;
+	char *mailbox, *ns;
 
 	solr_uid_map_callback_t *callback;
 	void *context;
@@ -192,16 +193,6 @@
 	i_free(conn);
 }
 
-void solr_connection_quote_str(struct solr_connection *conn, string_t *dest,
-			       const char *str)
-{
-	char *encoded;
-
-	encoded = curl_easy_escape(conn->curl, str_escape(str), 0);
-	str_printfa(dest, "%%22%s%%22", encoded);
-	curl_free(encoded);
-}
-
 void solr_connection_http_escape(struct solr_connection *conn, string_t *dest,
 				 const char *str)
 {
@@ -251,6 +242,7 @@
 			ctx->uid = 0;
 			ctx->score = 0;
 			i_free_and_null(ctx->mailbox);
+			i_free_and_null(ctx->ns);
 			ctx->uidvalidity = 0;
 		}
 		break;
@@ -262,6 +254,8 @@
 			ctx->content_state = SOLR_XML_CONTENT_STATE_SCORE;
 		else if (strcmp(name_attr, "box") == 0)
 			ctx->content_state = SOLR_XML_CONTENT_STATE_MAILBOX;
+		else if (strcmp(name_attr, "ns") == 0)
+			ctx->content_state = SOLR_XML_CONTENT_STATE_NAMESPACE;
 		else if (strcmp(name_attr, "uidv") == 0)
 			ctx->content_state = SOLR_XML_CONTENT_STATE_UIDVALIDITY;
 		else 
@@ -287,7 +281,7 @@
 			i_error("fts_solr: Query didn't return mailbox");
 			return;
 		}
-		if (!ctx->callback(ctx->mailbox, ctx->uidvalidity,
+		if (!ctx->callback(ctx->ns, ctx->mailbox, ctx->uidvalidity,
 				   &ctx->uid, ctx->context))
 			return;
 	}
@@ -357,6 +351,12 @@
 		i_free(ctx->mailbox);
 		ctx->mailbox = new_name;
 		break;
+	case SOLR_XML_CONTENT_STATE_NAMESPACE:
+		new_name = ctx->ns == NULL ? i_strndup(str, len) :
+			i_strconcat(ctx->ns, t_strndup(str, len), NULL);
+		i_free(ctx->ns);
+		ctx->ns = new_name;
+		break;
 	case SOLR_XML_CONTENT_STATE_UIDVALIDITY:
 		if (uint32_parse(str, len, &ctx->uidvalidity) < 0)
 			i_error("fts_solr: received invalid uidvalidity");
--- a/src/plugins/fts-solr/solr-connection.h	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/plugins/fts-solr/solr-connection.h	Sun Nov 30 01:27:19 2008 +0200
@@ -5,14 +5,13 @@
 #include "fts-api.h"
 
 /* Returns TRUE if UID conversion was done, FALSE if uid should be skipped. */
-typedef bool solr_uid_map_callback_t(const char *mailbox, uint32_t uidvalidity,
-				     uint32_t *uid, void *context);
+typedef bool solr_uid_map_callback_t(const char *ns_prefix, const char *mailbox,
+				     uint32_t uidvalidity, uint32_t *uid,
+				     void *context);
 
 struct solr_connection *solr_connection_init(const char *url, bool debug);
 void solr_connection_deinit(struct solr_connection *conn);
 
-void solr_connection_quote_str(struct solr_connection *conn, string_t *dest,
-			       const char *str);
 void solr_connection_http_escape(struct solr_connection *conn, string_t *dest,
 				 const char *str);
 
--- a/src/plugins/fts/fts-storage.c	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/plugins/fts/fts-storage.c	Sun Nov 30 01:27:19 2008 +0200
@@ -7,6 +7,7 @@
 #include "istream.h"
 #include "message-parser.h"
 #include "message-decoder.h"
+#include "mail-namespace.h"
 #include "mail-search-build.h"
 #include "mail-storage-private.h"
 #include "fts-api-private.h"
@@ -289,6 +290,8 @@
 	struct mailbox *const *boxes;
 	const struct fts_backend_uid_map *last_uids;
 	unsigned int boxi, uidi, box_count, last_uid_count;
+	const char *vname;
+	string_t *tmp;
 	int ret, vret = 0;
 
 	if (vctx->pool == NULL)
@@ -300,10 +303,13 @@
 	boxes = array_get(&vctx->mailboxes, &box_count);
 	last_uids = array_get(&vctx->last_uids, &last_uid_count);
 
+	tmp = t_str_new(256);
 	boxi = vctx->boxi;
 	uidi = vctx->uidi;
 	while (vret == 0 && boxi < box_count && uidi < last_uid_count) {
-		ret = strcmp(boxes[boxi]->name, last_uids[uidi].mailbox);
+		vname = mail_namespace_get_vname(boxes[boxi]->storage->ns, tmp,
+						 boxes[boxi]->name);
+		ret = strcmp(vname, last_uids[uidi].mailbox);
 		if (ret == 0) {
 			/* match. check also that uidvalidity matches. */
 			mailbox_get_status(boxes[boxi], STATUS_UIDVALIDITY,
--- a/src/plugins/virtual/virtual-config.c	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/plugins/virtual/virtual-config.c	Sun Nov 30 01:27:19 2008 +0200
@@ -119,11 +119,11 @@
 	if (strcasecmp(line, "INBOX") == 0)
 		line = "INBOX";
 	bbox->name = p_strdup(ctx->pool, line);
+	bbox->ns = mail_namespace_find(user->namespaces, &line);
 	if (strchr(bbox->name, '*') != NULL ||
 	    strchr(bbox->name, '%') != NULL) {
 		name = bbox->name[0] == '-' ? bbox->name + 1 : bbox->name;
 		bbox->glob = imap_match_init(ctx->pool, name, TRUE, ctx->sep);
-		bbox->ns = mail_namespace_find(user->namespaces, &line);
 		ctx->have_wildcards = TRUE;
 	}
 	array_append(&ctx->mbox->backend_boxes, &bbox, 1);
@@ -134,19 +134,25 @@
 virtual_mailbox_get_list_patterns(struct virtual_parse_context *ctx)
 {
 	struct virtual_mailbox *mbox = ctx->mbox;
-	ARRAY_TYPE(const_string) *dest;
+	ARRAY_TYPE(mailbox_virtual_patterns) *dest;
+	struct mailbox_virtual_pattern pattern;
 	struct virtual_backend_box *const *bboxes;
 	unsigned int i, count;
 
+	memset(&pattern, 0, sizeof(pattern));
 	bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
 	p_array_init(&mbox->list_include_patterns, ctx->pool, count);
 	p_array_init(&mbox->list_exclude_patterns, ctx->pool, count);
 	for (i = 0; i < count; i++) {
-		if (*bboxes[i]->name == '-')
+		pattern.ns = bboxes[i]->ns;
+		pattern.pattern = bboxes[i]->name;
+		if (*pattern.pattern != '-')
+			dest = &mbox->list_include_patterns;
+		else {
 			dest = &mbox->list_exclude_patterns;
-		else
-			dest = &mbox->list_include_patterns;
-		array_append(dest, &bboxes[i]->name, 1);
+			pattern.pattern++;
+		}
+		array_append(dest, &pattern, 1);
 	}
 }
 
--- a/src/plugins/virtual/virtual-storage.c	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/plugins/virtual/virtual-storage.c	Sun Nov 30 01:27:19 2008 +0200
@@ -595,8 +595,8 @@
 
 static void
 virtual_get_virtual_box_patterns(struct mailbox *box,
-				 ARRAY_TYPE(const_string) *includes,
-				 ARRAY_TYPE(const_string) *excludes)
+				 ARRAY_TYPE(mailbox_virtual_patterns) *includes,
+				 ARRAY_TYPE(mailbox_virtual_patterns) *excludes)
 {
 	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
 
--- a/src/plugins/virtual/virtual-storage.h	Sun Nov 30 01:26:36 2008 +0200
+++ b/src/plugins/virtual/virtual-storage.h	Sun Nov 30 01:27:19 2008 +0200
@@ -109,8 +109,8 @@
 	/* Mailboxes this virtual mailbox consists of, sorted by mailbox_id */
 	ARRAY_TYPE(virtual_backend_box) backend_boxes;
 
-	ARRAY_TYPE(const_string) list_include_patterns;
-	ARRAY_TYPE(const_string) list_exclude_patterns;
+	ARRAY_TYPE(mailbox_virtual_patterns) list_include_patterns;
+	ARRAY_TYPE(mailbox_virtual_patterns) list_exclude_patterns;
 
 	unsigned int uids_mapped:1;
 	unsigned int sync_initialized:1;