Index: matcher/Makefile.mk
===================================================================
--- matcher/Makefile.mk	(revision 13577)
+++ matcher/Makefile.mk	(working copy)
@@ -19,6 +19,7 @@
 	matcher/synonympostlist.h\
 	matcher/valuegepostlist.h\
 	matcher/valuerangepostlist.h\
+	matcher/valuestreamdocument.h\
 	matcher/xorpostlist.h
 
 EXTRA_DIST +=\
@@ -54,4 +55,5 @@
 	matcher/synonympostlist.cc\
 	matcher/valuegepostlist.cc\
 	matcher/valuerangepostlist.cc\
+	matcher/valuestreamdocument.cc\
 	matcher/xorpostlist.cc
Index: matcher/multimatch.cc
===================================================================
--- matcher/multimatch.cc	(revision 13577)
+++ matcher/multimatch.cc	(working copy)
@@ -44,6 +44,7 @@
 
 #include "msetcmp.h"
 
+#include "valuestreamdocument.h"
 #include "weightinternal.h"
 
 #include <xapian/errorhandler.h>
@@ -522,6 +523,10 @@
     // If a percentage cutoff is in effect, it can cause the matcher to return
     // from the second stage from the first.
 
+    ValueStreamDocument vsdoc(db);
+    ++vsdoc.ref_count;
+    Xapian::Document doc(&vsdoc);
+
     // Is the mset a valid heap?
     bool is_heap = false;
 
@@ -570,17 +575,15 @@
 	    calculated_weight = true;
 	}
 
-	Xapian::Internal::RefCntPtr<Xapian::Document::Internal> doc;
 	Xapian::docid did = pl->get_docid();
+	vsdoc.set_document(did);
 	LOGLINE(MATCH, "Candidate document id " << did << " wt " << wt);
 	Xapian::Internal::MSetItem new_item(wt, did);
 	if (sort_by != REL) {
-	    doc = db.get_document_lazily(did);
-	    Assert(doc.get());
 	    if (sorter) {
-		new_item.sort_key = (*sorter)(Xapian::Document(doc.get()));
+		new_item.sort_key = (*sorter)(doc);
 	    } else {
-		new_item.sort_key = doc->get_value(sort_key);
+		new_item.sort_key = vsdoc.get_value(sort_key);
 	    }
 
 	    // We're sorting by value (in part at least), so compare the item
@@ -595,8 +598,7 @@
 		    ++docs_matched;
 		    if (!calculated_weight) wt = pl->get_weight();
 		    if (matchspy) {
-			Xapian::Document mydoc(doc.get());
-			matchspy->operator()(mydoc, wt);
+			matchspy->operator()(doc, wt);
 		    }
 		    if (wt > greatest_wt) goto new_greatest_weight;
 		    continue;
@@ -624,18 +626,12 @@
 	    // If the results are from a remote database, then the functor will
 	    // already have been applied there so we can skip this step.
 	    if (!is_remote[n]) {
-		if (doc.get() == 0) {
-		    doc = db.get_document_lazily(did);
-		    Assert(doc.get());
-		}
-		Xapian::Document mydoc(doc.get());
-
 		++decider_considered;
-		if (matchspy_legacy && !matchspy_legacy->operator()(mydoc)) {
+		if (matchspy_legacy && !matchspy_legacy->operator()(doc)) {
 		    ++decider_denied;
 		    continue;
 		}
-		if (mdecider && !mdecider->operator()(mydoc)) {
+		if (mdecider && !mdecider->operator()(doc)) {
 		    ++decider_denied;
 		    continue;
 		}
@@ -645,7 +641,7 @@
 			new_item.wt = wt;
 			calculated_weight = true;
 		    }
-		    matchspy->operator()(mydoc, wt);
+		    matchspy->operator()(doc, wt);
 		}
 	    }
 	}
@@ -661,7 +657,7 @@
 	// Perform collapsing on key if requested.
 	if (collapser) {
 	    collapse_result res;
-	    res = collapser.process(new_item, pl, db, doc, mcmp);
+	    res = collapser.process(new_item, pl, vsdoc, mcmp);
 	    if (res == REJECTED) {
 		// If we're sorting by relevance primarily, then we throw away
 		// the lower weighted document anyway.
Index: matcher/collapser.h
===================================================================
--- matcher/collapser.h	(revision 13577)
+++ matcher/collapser.h	(working copy)
@@ -134,16 +134,14 @@
      *  @param item		The new item.
      *  @param postlist		PostList to try to get collapse key from
      *				(this happens for a remote match).
-     *  @param db		Database match is running over.
-     *  @param[inout] doc	Lazily create document for getting values.
+     *  @param doc		Document for getting values.
      *  @param mcmp		MSetItem comparison functor.
      *
      *  @return How @a item was handled: EMPTY, ADDED, REJECTED or REPLACED.
      */
     collapse_result process(Xapian::Internal::MSetItem & item,
 			    PostList * postlist,
-			    const Xapian::Database & db,
-			    Xapian::Internal::RefCntPtr<Xapian::Document::Internal> & doc,
+			    Xapian::Document::Internal & vsdoc,
 			    const MSetCmp & mcmp);
 
     Xapian::doccount get_collapse_count(const std::string & collapse_key,
Index: matcher/valuestreamdocument.cc
===================================================================
--- matcher/valuestreamdocument.cc	(revision 0)
+++ matcher/valuestreamdocument.cc	(revision 0)
@@ -0,0 +1,71 @@
+/** @file valuestreamdocument.cc
+ * @brief A document which gets its values from a ValueStreamManager.
+ */
+/* Copyright (C) 2009 Olly Betts
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#include <config.h>
+
+#include "valuestreamdocument.h"
+
+using namespace std;
+
+ValueStreamDocument::~ValueStreamDocument()
+{
+    map<Xapian::valueno, ValueList *>::const_iterator i;
+    for (i = valuelists.begin(); i != valuelists.end(); ++i) {
+	delete i->second;
+    }
+
+    delete doc;
+}
+
+string
+ValueStreamDocument::do_get_value(Xapian::valueno slot) const
+{
+    pair<map<Xapian::valueno, ValueList *>::iterator, bool> ret;
+    ret = valuelists.insert(make_pair(slot, static_cast<ValueList*>(NULL)));
+    ValueList * vl;
+    if (ret.second) {
+	// Entry didn't already exist, so open a value list for slot.
+	vl = db.open_valuelist_(slot);
+	ret.first->second = vl;
+    } else {
+	vl = ret.first->second;
+    }
+
+    if (!vl || !vl->check(did) || vl->get_docid() != did)
+	return string();
+
+    return vl->get_value();
+}
+
+void
+ValueStreamDocument::do_get_all_values(map<Xapian::valueno, string> & v) const
+{
+    if (!doc)
+	doc = db.get_document_lazily(did);
+    return doc->do_get_all_values(v);
+}
+
+string
+ValueStreamDocument::do_get_data() const
+{
+    if (!doc)
+	doc = db.get_document_lazily(did);
+    return doc->do_get_data();
+}
Index: matcher/valuestreamdocument.h
===================================================================
--- matcher/valuestreamdocument.h	(revision 0)
+++ matcher/valuestreamdocument.h	(revision 0)
@@ -0,0 +1,70 @@
+/** @file valuestreamdocument.h
+ * @brief A document which gets its values from a ValueStreamManager.
+ */
+/* Copyright (C) 2009 Olly Betts
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef XAPIAN_INCLUDED_VALUESTREAMDOCUMENT_H
+#define XAPIAN_INCLUDED_VALUESTREAMDOCUMENT_H
+
+#include "document.h"
+#include "valuelist.h"
+#include "xapian/types.h"
+
+#include <map>
+
+/// A document which gets its values from a ValueStreamManager.
+class ValueStreamDocument : public Xapian::Document::Internal {
+    /// Don't allow assignment.
+    void operator=(const ValueStreamDocument &);
+
+    /// Don't allow copying.
+    ValueStreamDocument(const ValueStreamDocument &);
+
+    mutable std::map<Xapian::valueno, ValueList *> valuelists;
+
+    Xapian::Database db;
+
+    Xapian::docid did;
+
+    mutable Xapian::Document::Internal * doc;
+
+  public:
+    ValueStreamDocument(const Xapian::Database & db_)
+       	: db(db_), did(0), doc(NULL) {
+	// Set database to an arbitrary subdatabase.  It won't be used, except
+	// to check it is set.
+	database = db.internal[0];
+    }
+
+    ~ValueStreamDocument();
+
+    void set_document(Xapian::docid did_) {
+	did = did_;
+	delete doc;
+	doc = NULL;
+    }
+
+  private:
+    /** Implementation of virtual methods @{ */
+    string do_get_value(Xapian::valueno slot) const;
+    void do_get_all_values(map<Xapian::valueno, string> & values_) const;
+    string do_get_data() const;
+    /** @} */
+};
+
+#endif // XAPIAN_INCLUDED_VALUESTREAMDOCUMENT_H
Index: matcher/collapser.cc
===================================================================
--- matcher/collapser.cc	(revision 13577)
+++ matcher/collapser.cc	(working copy)
@@ -69,8 +69,7 @@
 collapse_result
 Collapser::process(Xapian::Internal::MSetItem & item,
 		   PostList * postlist,
-		   const Xapian::Database & db,
-		   Xapian::Internal::RefCntPtr<Xapian::Document::Internal> & doc,
+		   Xapian::Document::Internal & vsdoc,
 		   const MSetCmp & mcmp)
 {
     ++docs_considered;
@@ -79,12 +78,8 @@
     if (key_ptr) {
 	item.collapse_key = *key_ptr;
     } else {
-	// Otherwise use a lazily created Document object to get the value.
-	if (!doc.get()) {
-	    doc = db.get_document_lazily(item.did);
-	    Assert(doc.get());
-	}
-	item.collapse_key = doc->get_value(slot);
+	// Otherwise use the Document object to get the value.
+	item.collapse_key = vsdoc.get_value(slot);
     }
 
     if (item.collapse_key.empty()) {
Index: include/xapian/database.h
===================================================================
--- include/xapian/database.h	(revision 13577)
+++ include/xapian/database.h	(working copy)
@@ -308,7 +308,11 @@
 	Xapian::termcount get_wdf_upper_bound(const std::string & term) const;
 
 	/// Return an iterator over the value in slot @a slot for each document.
-	ValueIterator valuestream_begin(Xapian::valueno slot) const;
+	ValueIterator valuestream_begin(Xapian::valueno slot) const {
+	    return ValueIterator(open_valuelist_(slot));
+	}
+
+	ValueIterator::Internal * open_valuelist_(Xapian::valueno slot) const;
 
 	/// Return end iterator corresponding to valuestream_begin().
 	ValueIteratorEnd_ valuestream_end(Xapian::valueno) const {
Index: common/document.h
===================================================================
--- common/document.h	(revision 13577)
+++ common/document.h	(working copy)
@@ -76,6 +76,8 @@
 	Xapian::docid did;
 
     private:
+	// FIXME: public for valuestreamdocument
+    public:
 	// Functions for backend to implement
 	virtual string do_get_value(Xapian::valueno /*valueno*/) const { return string(); }
 	virtual void do_get_all_values(map<Xapian::valueno, string> & values_) const {
Index: api/omdatabase.cc
===================================================================
--- api/omdatabase.cc	(revision 13577)
+++ api/omdatabase.cc	(working copy)
@@ -389,16 +389,15 @@
     RETURN(full_ub);
 }
 
-ValueIterator
-Database::valuestream_begin(Xapian::valueno slot) const
+ValueIterator::Internal *
+Database::open_valuelist_(Xapian::valueno slot) const
 {
-    DEBUGAPICALL(ValueIterator, "Database::valuestream_begin", slot);
-    if (internal.empty()) RETURN(ValueIterator());
+    if (internal.empty()) return NULL;
     // FIXME: support multidatabases properly.
     if (internal.size() != 1) {
 	throw Xapian::UnimplementedError("Database::valuestream_begin() doesn't support multidatabases yet");
     }
-    RETURN(ValueIterator(internal[0]->open_value_list(slot)));
+    return internal[0]->open_value_list(slot);
 }
 
 Xapian::termcount

