Solr: Update other Document in DocTransformer by Writing custom SolrWriter


Summary
Write our own XMLWriter so we can update other SolrDocument or even delete current document in DocTransformer.

The User Case
There are two types of docs in Solr: one is child doc including fields: type(value 0), groupId, time and etc. 
another type of doc is group doc: type(value 1), they are actually just some faked docs.

We use join query with includeParent=true and make sure groups are sorted by time(the max value in the group) and the group doc is always be front of all child docs.

But Solr doesn't return groupCount in flat mode: in grouped mode, Solr can return groupCount in group header, but no such thing in flat mode.

So we have to dynamically generate groupCount and time value for each group(type=1) doc.

I tried several solutions:
In DocTransformer, when current doc is group doc(type=1), run query to get num of docs in this group.
SolrPluginUtils.numDocs(req.getSearcher(), baseQuery + new TermQuery(new Term("groupId", groupId)), null);

Later I optimized it by pre-compute baseDocSet which matches the q and fq:
DocSet baseDocSet = req.getSearcher().getDocSet(baseQuery);
int groupCount = req.getSearcher().getDocSet(new TermQuery(new Term("groupId", groupId)), baseDocSet).size();

The Solution
But all seems not good to me: as all child(type==0) docs follows each group doc(type=1), there should be no need to run Solr query at all: we can easily calculate the groupCount and its mtm value.

But the problem here is that we can only change current SolrDocument in DocTransformer:
org.apache.solr.response.TextResponseWriter.writeDocuments(String, ResultContext, ReturnFields)
for (int i=0; i<sz; i++) {
 if( transformer != null ) {
  transformer.transform( sdoc, id);
 }
 // SolrWriter writes the doc to output stream
 writeSolrDocument( null, sdoc, returnFields, i );
}
writeEndDocumentList();

One way is to change Solr's code directly to support this:
We can change The code here like below:
cachMode = req.getParams().getBool("cachMode", false);
SolrDocument[] cachedDocs = new SolrDocument[sz];
for (int i = 0; i < sz; i++) {
 SolrDocument sdoc = toSolrDocument(doc);
 if (transformer != null) {
  transformer.transform(sdoc, id);
 }
 if(cachMode)
 {
    cachedDocs[i] = sdoc;
 }
 else{
    writeSolrDocument( null, sdoc, returnFields, i );
 }
 
}
if (transformer != null) {
 transformer.setContext(null);
}
if(cachMode) {
 for (int i = 0; i < sz; i++) {
  writeSolrDocument(null, cachedDocs[i], returnFields, i);
 }
}
writeEndDocumentList();


Or we can write our own Writer, so we don't have to change solr's code.

Custom Solr Writer: CachedXMLWriter
The implementation is simple: we just cache SolrDocument in writeSolrDocument, write them in writeEndDocumentList. 
We can also allow DocTransfromer to delete doc: by add one specifically field "_del_", if this field is set, we will not write this doc into output stream.
public class CachedXMLWriter extends XMLWriter {
  static class SolrDocumentHolder {
    SolrDocument doc;
    String name;
    int idx;
  }
  List<SolrDocumentHolder> holders = new ArrayList<SolrDocumentHolder>();
  public void writeSolrDocument(String name, SolrDocument doc,
      ReturnFields returnFields, int idx) throws IOException {
    Object del = doc.getFieldValue("_del_");
    if (del == null) {
      SolrDocumentHolder holder = new SolrDocumentHolder();
      holder.doc = doc;
      holder.name = name;
      holder.idx = idx;
      holders.add(holder);
    }
  }
  public void writeEndDocumentList() throws IOException {
    for (SolrDocumentHolder holder : holders) {
      super
          .writeSolrDocument(holder.name, holder.doc, returnFields, holder.idx);
    }
    super.writeEndDocumentList();
  }  
}
CachedXMLResponseWriter
Here is the companion class:
public class CachedXMLResponseWriter implements QueryResponseWriter {
  public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp)
      throws IOException {
    CachedXMLWriter w = new CachedXMLWriter(writer, req, rsp);
    try {
      w.writeResponse();
    } finally {
      w.close();
    }
  }
  public String getContentType(SolrQueryRequest request,
      SolrQueryResponse response) {
    return CONTENT_TYPE_XML_UTF8;
  }
}
at last,declare the writer in solrconfig.xml:
<queryResponseWriter name="cachexml" class="solr.CachedXMLResponseWriter" startup="lazy"/>
Now we can use it: wt=cachexml&fl=f1,[groupCount] 

To hide the implementation from client side, we can encapsulate the logic in our request handler: set wt=cachexml if transformer [groupCount] exists.

Miscs:
Transforming Result Documents
[value] - ValueAugmenterFactory
greeting:[value v='hello']
fl=id,my_number:[value v=42 t=int],my_string:[value v=42]
newname:oldname RenameFieldTransformer

[explain] doesn't work with group
if (grouping.mainResult != null) {
ResultContext ctx = new ResultContext();
ctx.query = null; // TODO? add the query?
}
[child] - ChildDocTransformerFactory
[shard] - ShardAugmenterFactory

public abstract class TransformerWithContext extends DocTransformer

Resources
Solr Join: Return Parent and Child Documents
Use Solr map function query(group.sort=map(type,1,1,-1) ) in group flat mode
Solr: Use DocTransformer to dynamically Generate groupCount and time value for group doc
SOLR-7097: Update other Document in DocTransformer

Labels

adsense (5) Algorithm (69) Algorithm Series (35) Android (7) ANT (6) bat (8) Big Data (7) Blogger (14) Bugs (6) Cache (5) Chrome (19) Code Example (29) Code Quality (7) Coding Skills (5) Database (7) Debug (16) Design (5) Dev Tips (63) Eclipse (32) Git (5) Google (33) Guava (7) How to (9) Http Client (8) IDE (7) Interview (88) J2EE (13) J2SE (49) Java (186) JavaScript (27) JSON (7) Learning code (9) Lesson Learned (6) Linux (26) Lucene-Solr (112) Mac (10) Maven (8) Network (9) Nutch2 (18) Performance (9) PowerShell (11) Problem Solving (11) Programmer Skills (6) regex (5) Scala (6) Security (9) Soft Skills (38) Spring (22) System Design (11) Testing (7) Text Mining (14) Tips (17) Tools (24) Troubleshooting (29) UIMA (9) Web Development (19) Windows (21) xml (5)