SOLR-5720: Add ExpandComponent to expand results collapsed by the CollapsingQParserPlugin

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1573589 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Joel Bernstein 2014-03-03 15:45:59 +00:00
parent 4eed6367ff
commit f48d27f241
9 changed files with 771 additions and 1 deletions

View File

@ -84,6 +84,7 @@ import org.apache.solr.handler.component.QueryComponent;
import org.apache.solr.handler.component.RealTimeGetComponent;
import org.apache.solr.handler.component.SearchComponent;
import org.apache.solr.handler.component.StatsComponent;
import org.apache.solr.handler.component.ExpandComponent;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.BinaryResponseWriter;
@ -1237,6 +1238,8 @@ public final class SolrCore implements SolrInfoMBean {
addIfNotPresent(components,DebugComponent.COMPONENT_NAME,DebugComponent.class);
addIfNotPresent(components,RealTimeGetComponent.COMPONENT_NAME,RealTimeGetComponent.class);
addIfNotPresent(components,AnalyticsComponent.COMPONENT_NAME,AnalyticsComponent.class);
addIfNotPresent(components,ExpandComponent.COMPONENT_NAME,ExpandComponent.class);
return components;
}
private <T> void addIfNotPresent(Map<String ,T> registry, String name, Class<? extends T> c){

View File

@ -0,0 +1,337 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.handler.component;
import org.apache.lucene.index.AtomicReader;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.TopDocsCollector;
import org.apache.lucene.search.TopFieldCollector;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.CharsRef;
import org.apache.lucene.util.FixedBitSet;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.ShardParams;
import org.apache.solr.search.CollapsingQParserPlugin;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocList;
import org.apache.solr.search.QueryParsing;
import org.apache.solr.schema.FieldType;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.ExpandParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.search.DocSlice;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.util.plugin.PluginInfoInitialized;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrCore;
import com.carrotsearch.hppc.IntObjectOpenHashMap;
import com.carrotsearch.hppc.IntOpenHashSet;
import com.carrotsearch.hppc.cursors.IntObjectCursor;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
/**
* The ExpandComponent is designed to work with the CollapsingPostFilter.
* The CollapsingPostFilter collapses a result set on a field.
* <p/>
* The ExpandComponent expands the collapsed groups for a single page.
* <p/>
* http parameters:
* <p/>
* expand=true <br/>
* expand.rows=5 </br>
* expand.sort=field asc|desc
*
**/
public class ExpandComponent extends SearchComponent implements PluginInfoInitialized, SolrCoreAware {
public static final String COMPONENT_NAME = "expand";
private PluginInfo info = PluginInfo.EMPTY_INFO;
@Override
public void init(PluginInfo info) {
this.info = info;
}
@Override
public void prepare(ResponseBuilder rb) throws IOException {
if (rb.req.getParams().getBool(ExpandParams.EXPAND,false)) {
rb.doExpand = true;
}
}
@Override
public void inform(SolrCore core) {
}
@Override
public void process(ResponseBuilder rb) throws IOException {
if(!rb.doExpand) {
return;
}
SolrQueryRequest req = rb.req;
SolrParams params = req.getParams();
boolean isShard = params.getBool(ShardParams.IS_SHARD, false);
String ids = params.get(ShardParams.IDS);
if(ids == null && isShard) {
return;
}
String field = null;
String sortParam = params.get(ExpandParams.EXPAND_SORT);
int limit = params.getInt(ExpandParams.EXPAND_ROWS, 5);
Sort sort = null;
if(sortParam != null) {
sort = QueryParsing.parseSortSpec(sortParam, rb.req).getSort();
}
Query query = rb.getQuery();
List<Query> filters = rb.getFilters();
List<Query> newFilters = new ArrayList();
for(Query q : filters) {
if(!(q instanceof CollapsingQParserPlugin.CollapsingPostFilter)) {
newFilters.add(q);
} else {
CollapsingQParserPlugin.CollapsingPostFilter cp = (CollapsingQParserPlugin.CollapsingPostFilter)q;
field = cp.getField();
}
}
if(field == null) {
throw new IOException("Expand field is null.");
}
SolrIndexSearcher searcher = req.getSearcher();
AtomicReader reader = searcher.getAtomicReader();
SortedDocValues values = FieldCache.DEFAULT.getTermsIndex(reader, field);
FixedBitSet groupBits = new FixedBitSet(values.getValueCount());
DocList docList = rb.getResults().docList;
IntOpenHashSet collapsedSet = new IntOpenHashSet(docList.size()*2);
DocIterator idit = docList.iterator();
while(idit.hasNext()) {
int doc = idit.nextDoc();
int ord = values.getOrd(doc);
if(ord > -1) {
groupBits.set(ord);
collapsedSet.add(doc);
}
}
Collector collector = null;
GroupExpandCollector groupExpandCollector = new GroupExpandCollector(values, groupBits, collapsedSet, limit, sort);
SolrIndexSearcher.ProcessedFilter pfilter = searcher.getProcessedFilter(null, newFilters);
if(pfilter.postFilter != null) {
pfilter.postFilter.setLastDelegate(groupExpandCollector);
collector = pfilter.postFilter;
} else {
collector = groupExpandCollector;
}
searcher.search(query, pfilter.filter, collector);
IntObjectOpenHashMap groups = groupExpandCollector.getGroups();
Iterator<IntObjectCursor> it = groups.iterator();
Map<String, DocSlice> outMap = new HashMap<>();
BytesRef bytesRef = new BytesRef();
CharsRef charsRef = new CharsRef();
FieldType fieldType = searcher.getSchema().getField(field).getType();
while(it.hasNext()) {
IntObjectCursor cursor = it.next();
int ord = cursor.key;
TopDocsCollector topDocsCollector = (TopDocsCollector)cursor.value;
TopDocs topDocs = topDocsCollector.topDocs();
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
if(scoreDocs.length > 0) {
int[] docs = new int[scoreDocs.length];
float[] scores = new float[scoreDocs.length];
for(int i=0; i<docs.length; i++) {
ScoreDoc scoreDoc = scoreDocs[i];
docs[i] = scoreDoc.doc;
scores[i] = scoreDoc.score;
}
DocSlice slice = new DocSlice(0, docs.length, docs, scores, topDocs.totalHits, topDocs.getMaxScore());
values.lookupOrd(ord, bytesRef);
fieldType.indexedToReadable(bytesRef, charsRef);
String group = charsRef.toString();
outMap.put(group, slice);
}
}
rb.rsp.add("expanded", outMap);
}
@Override
public void modifyRequest(ResponseBuilder rb, SearchComponent who, ShardRequest sreq) {
}
@Override
public void handleResponses(ResponseBuilder rb, ShardRequest sreq) {
if(!rb.doExpand) {
return;
}
if ((sreq.purpose & ShardRequest.PURPOSE_GET_FIELDS) != 0) {
SolrQueryRequest req = rb.req;
Map expanded = (Map)req.getContext().get("expanded");
if(expanded == null) {
expanded = new HashMap();
req.getContext().put("expanded", expanded);
}
for (ShardResponse srsp : sreq.responses) {
NamedList response = srsp.getSolrResponse().getResponse();
Map ex = (Map)response.get("expanded");
Iterator<Map.Entry<String,SolrDocumentList>>it = ex.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<String, SolrDocumentList> entry = it.next();
String name = entry.getKey();
SolrDocumentList val = entry.getValue();
expanded.put(name, val);
}
}
}
}
@Override
public void finishStage(ResponseBuilder rb) {
if(!rb.doExpand) {
return;
}
if (rb.stage != ResponseBuilder.STAGE_GET_FIELDS) {
return;
}
Map expanded = (Map)rb.req.getContext().get("expanded");
if(expanded == null) {
expanded = new HashMap();
}
rb.rsp.add("expanded", expanded);
}
private class GroupExpandCollector extends Collector {
private SortedDocValues docValues;
private IntObjectOpenHashMap groups;
private int docBase;
private FixedBitSet groupBits;
private IntOpenHashSet collapsedSet;
private List<Collector> collectors;
public GroupExpandCollector(SortedDocValues docValues, FixedBitSet groupBits, IntOpenHashSet collapsedSet, int limit, Sort sort) throws IOException {
int numGroups = collapsedSet.size();
groups = new IntObjectOpenHashMap(numGroups*2);
collectors = new ArrayList();
DocIdSetIterator iterator = groupBits.iterator();
int group = -1;
while((group = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
Collector collector = (sort == null) ? TopScoreDocCollector.create(limit, true) : TopFieldCollector.create(sort,limit, false, false,false, true);
groups.put(group, collector);
collectors.add(collector);
}
this.collapsedSet = collapsedSet;
this.groupBits = groupBits;
this.docValues = docValues;
}
public IntObjectOpenHashMap getGroups() {
return this.groups;
}
public boolean acceptsDocsOutOfOrder() {
return false;
}
public void collect(int docId) throws IOException {
int doc = docId+docBase;
int ord = docValues.getOrd(doc);
if(ord > -1 && groupBits.get(ord) && !collapsedSet.contains(doc)) {
Collector c = (Collector)groups.get(ord);
c.collect(docId);
}
}
public void setNextReader(AtomicReaderContext context) throws IOException {
this.docBase = context.docBase;
for(Collector c : collectors) {
c.setNextReader(context);
}
}
public void setScorer(Scorer scorer) throws IOException {
for(Collector c : collectors) {
c.setScorer(scorer);
}
}
}
////////////////////////////////////////////
/// SolrInfoMBean
////////////////////////////////////////////
@Override
public String getDescription() {
return "Expand Component";
}
@Override
public String getSource() {
return "$URL: https://svn.apache.org/repos/asf/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/ExpandComponent.java $";
}
@Override
public URL[] getDocs() {
try {
return new URL[]{
new URL("http://wiki.apache.org/solr/ExpandComponent")
};
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -55,6 +55,7 @@ public class ResponseBuilder
public SolrQueryResponse rsp;
public boolean doHighlights;
public boolean doFacets;
public boolean doExpand;
public boolean doStats;
public boolean doTerms;

View File

@ -72,6 +72,7 @@ public class SearchHandler extends RequestHandlerBase implements SolrCoreAware ,
names.add( StatsComponent.COMPONENT_NAME );
names.add( DebugComponent.COMPONENT_NAME );
names.add( AnalyticsComponent.COMPONENT_NAME );
names.add( ExpandComponent.COMPONENT_NAME);
return names;
}

View File

@ -140,6 +140,11 @@ public class CollapsingQParserPlugin extends QParserPlugin {
public static final int NULL_POLICY_COLLAPSE = 1;
public static final int NULL_POLICY_EXPAND = 2;
public String getField(){
return this.field;
}
public void setCache(boolean cache) {
}

View File

@ -0,0 +1,189 @@
package org.apache.solr.handler.component;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.solr.BaseDistributedSearchTestCase;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.junit.BeforeClass;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Iterator;
/**
* Test for QueryComponent's distributed querying
*
* @see org.apache.solr.handler.component.QueryComponent
*/
public class DistributedExpandComponentTest extends BaseDistributedSearchTestCase {
public DistributedExpandComponentTest() {
fixShardCount = true;
shardCount = 3;
stress = 0;
}
@BeforeClass
public static void setUpBeforeClass() throws Exception {
initCore("solrconfig-collapseqparser.xml", "schema11.xml");
}
@Override
public void doTest() throws Exception {
del("*:*");
index_specific(0,"id","1", "term_s", "YYYY", "group_s", "group1", "test_ti", "5", "test_tl", "10", "test_tf", "2000");
index_specific(0,"id","2", "term_s", "YYYY", "group_s", "group1", "test_ti", "50", "test_tl", "100", "test_tf", "200");
index_specific(1,"id","5", "term_s", "YYYY", "group_s", "group2", "test_ti", "4", "test_tl", "10", "test_tf", "2000");
index_specific(1,"id","6", "term_s", "YYYY", "group_s", "group2", "test_ti", "10", "test_tl", "100", "test_tf", "200");
index_specific(0,"id","7", "term_s", "YYYY", "group_s", "group1", "test_ti", "1", "test_tl", "100000", "test_tf", "2000");
index_specific(1,"id","8", "term_s", "YYYY", "group_s", "group2", "test_ti", "2", "test_tl", "100000", "test_tf", "200");
index_specific(2,"id","9", "term_s", "YYYY", "group_s", "group3", "test_ti", "1000", "test_tl", "1005", "test_tf", "3000");
index_specific(2, "id", "10", "term_s", "YYYY", "group_s", "group3", "test_ti", "1500", "test_tl", "1001", "test_tf", "3200");
index_specific(2,"id", "11", "term_s", "YYYY", "group_s", "group3", "test_ti", "1300", "test_tl", "1002", "test_tf", "3300");
index_specific(1,"id","12", "term_s", "YYYY", "group_s", "group4", "test_ti", "15", "test_tl", "10", "test_tf", "2000");
index_specific(1,"id","13", "term_s", "YYYY", "group_s", "group4", "test_ti", "16", "test_tl", "9", "test_tf", "2000");
index_specific(1,"id","14", "term_s", "YYYY", "group_s", "group4", "test_ti", "1", "test_tl", "20", "test_tf", "2000");
commit();
handle.put("explain", SKIPVAL);
handle.put("QTime", SKIPVAL);
handle.put("timestamp", SKIPVAL);
handle.put("score", SKIPVAL);
handle.put("wt", SKIP);
handle.put("distrib", SKIP);
handle.put("shards.qt", SKIP);
handle.put("shards", SKIP);
handle.put("q", SKIP);
handle.put("maxScore", SKIPVAL);
handle.put("_version_", SKIP);
query("q", "*:*", "fq", "{!collapse field=group_s}", "defType", "edismax", "bf", "field(test_ti)", "expand", "true", "fl","*,score");
query("q", "*:*", "fq", "{!collapse field=group_s}", "defType", "edismax", "bf", "field(test_ti)", "expand", "true", "expand.sort", "test_tl desc", "fl","*,score");
query("q", "*:*", "fq", "{!collapse field=group_s}", "defType", "edismax", "bf", "field(test_ti)", "expand", "true", "expand.sort", "test_tl desc", "expand.rows", "1", "fl","*,score");
//Test no expand results
query("q", "test_ti:5", "fq", "{!collapse field=group_s}", "defType", "edismax", "bf", "field(test_ti)", "expand", "true", "expand.sort", "test_tl desc", "expand.rows", "1", "fl","*,score");
//Test zero results
query("q", "test_ti:5434343", "fq", "{!collapse field=group_s}", "defType", "edismax", "bf", "field(test_ti)", "expand", "true", "expand.sort", "test_tl desc", "expand.rows", "1", "fl","*,score");
//First basic test case.
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "{!collapse field=group_s}");
params.add("defType", "edismax");
params.add("bf", "field(test_ti)");
params.add("expand", "true");
setDistributedParams(params);
QueryResponse rsp = queryServer(params);
Map<String, SolrDocumentList> results = rsp.getExpandedResults();
assertExpandGroups(results, "group1","group2", "group3", "group4");
assertExpandGroupCountAndOrder("group1", 2, results, "1.0", "7.0");
assertExpandGroupCountAndOrder("group2", 2, results, "5.0", "8.0");
assertExpandGroupCountAndOrder("group3", 2, results, "11.0", "9.0");
assertExpandGroupCountAndOrder("group4", 2, results, "12.0", "14.0");
//Test expand.sort
params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "{!collapse field=group_s}");
params.add("defType", "edismax");
params.add("bf", "field(test_ti)");
params.add("expand", "true");
params.add("expand.sort", "test_tl desc");
setDistributedParams(params);
rsp = queryServer(params);
results = rsp.getExpandedResults();
assertExpandGroups(results, "group1","group2", "group3", "group4");
assertExpandGroupCountAndOrder("group1", 2, results, "7.0", "1.0");
assertExpandGroupCountAndOrder("group2", 2, results, "8.0", "5.0");
assertExpandGroupCountAndOrder("group3", 2, results, "9.0", "11.0");
assertExpandGroupCountAndOrder("group4", 2, results, "14.0", "12.0");
//Test expand.rows
params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "{!collapse field=group_s}");
params.add("defType", "edismax");
params.add("bf", "field(test_ti)");
params.add("expand", "true");
params.add("expand.sort", "test_tl desc");
params.add("expand.rows", "1");
setDistributedParams(params);
rsp = queryServer(params);
results = rsp.getExpandedResults();
assertExpandGroups(results, "group1","group2", "group3", "group4");
assertExpandGroupCountAndOrder("group1", 1, results, "7.0");
assertExpandGroupCountAndOrder("group2", 1, results, "8.0");
assertExpandGroupCountAndOrder("group3", 1, results, "9.0");
assertExpandGroupCountAndOrder("group4", 1, results, "14.0");
}
private void assertExpandGroups(Map<String, SolrDocumentList> expandedResults, String... groups) throws Exception {
for(int i=0; i<groups.length; i++) {
if(!expandedResults.containsKey(groups[i])) {
throw new Exception("Expanded Group Not Found:"+groups[i]+", Found:"+exportGroups(expandedResults));
}
}
}
private String exportGroups(Map<String, SolrDocumentList> groups) {
StringBuilder buf = new StringBuilder();
Iterator<String> it = groups.keySet().iterator();
while(it.hasNext()) {
String group = it.next();
buf.append(group);
if(it.hasNext()) {
buf.append(",");
}
}
return buf.toString();
}
private void assertExpandGroupCountAndOrder(String group, int count, Map<String, SolrDocumentList>expandedResults, String... docs) throws Exception {
SolrDocumentList results = expandedResults.get(group);
if(results == null) {
throw new Exception("Group Not Found:"+group);
}
if(results.size() != count) {
throw new Exception("Expected Count "+results.size()+" Not Found:"+count);
}
for(int i=0; i<docs.length;i++) {
String id = docs[i];
SolrDocument doc = results.get(i);
if(!doc.getFieldValue("id").toString().equals(id)) {
throw new Exception("Id not in results or out of order:"+id+"!="+doc.getFieldValue("id"));
}
}
}
}

View File

@ -0,0 +1,193 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.handler.component;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.util.*;
public class TestExpandComponent extends SolrTestCaseJ4 {
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig-collapseqparser.xml", "schema11.xml");
}
@Override
@Before
public void setUp() throws Exception {
// if you override setUp or tearDown, you better call
// the super classes version
super.setUp();
clearIndex();
assertU(commit());
}
@Test
public void testExpand() throws Exception {
String[] doc = {"id","1", "term_s", "YYYY", "group_s", "group1", "test_ti", "5", "test_tl", "10", "test_tf", "2000"};
assertU(adoc(doc));
assertU(commit());
String[] doc1 = {"id","2", "term_s","YYYY", "group_s", "group1", "test_ti", "50", "test_tl", "100", "test_tf", "200"};
assertU(adoc(doc1));
String[] doc2 = {"id","3", "term_s", "YYYY", "test_ti", "5000", "test_tl", "100", "test_tf", "200"};
assertU(adoc(doc2));
assertU(commit());
String[] doc3 = {"id","4", "term_s", "YYYY", "test_ti", "500", "test_tl", "1000", "test_tf", "2000"};
assertU(adoc(doc3));
String[] doc4 = {"id","5", "term_s", "YYYY", "group_s", "group2", "test_ti", "4", "test_tl", "10", "test_tf", "2000"};
assertU(adoc(doc4));
assertU(commit());
String[] doc5 = {"id","6", "term_s","YYYY", "group_s", "group2", "test_ti", "10", "test_tl", "100", "test_tf", "200"};
assertU(adoc(doc5));
assertU(commit());
String[] doc6 = {"id","7", "term_s", "YYYY", "group_s", "group1", "test_ti", "1", "test_tl", "100000", "test_tf", "2000"};
assertU(adoc(doc6));
assertU(commit());
String[] doc7 = {"id","8", "term_s","YYYY", "group_s", "group2", "test_ti", "2", "test_tl", "100000", "test_tf", "200"};
assertU(adoc(doc7));
assertU(commit());
//First basic test case.
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "{!collapse field=group_s}");
params.add("defType", "edismax");
params.add("bf", "field(test_ti)");
params.add("expand", "true");
assertQ(req(params), "*[count(/response/result/doc)=2]",
"*[count(/response/lst[@name='expanded']/result)=2]",
"/response/result/doc[1]/float[@name='id'][.='2.0']",
"/response/result/doc[2]/float[@name='id'][.='6.0']",
"/response/lst[@name='expanded']/result[@name='group1']/doc[1]/float[@name='id'][.='1.0']",
"/response/lst[@name='expanded']/result[@name='group1']/doc[2]/float[@name='id'][.='7.0']",
"/response/lst[@name='expanded']/result[@name='group2']/doc[1]/float[@name='id'][.='5.0']",
"/response/lst[@name='expanded']/result[@name='group2']/doc[2]/float[@name='id'][.='8.0']"
);
//Test expand.sort
params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "{!collapse field=group_s}");
params.add("defType", "edismax");
params.add("bf", "field(test_ti)");
params.add("expand", "true");
params.add("expand.sort", "test_tl desc");
assertQ(req(params), "*[count(/response/result/doc)=2]",
"*[count(/response/lst[@name='expanded']/result)=2]",
"/response/result/doc[1]/float[@name='id'][.='2.0']",
"/response/result/doc[2]/float[@name='id'][.='6.0']",
"/response/lst[@name='expanded']/result[@name='group1']/doc[1]/float[@name='id'][.='7.0']",
"/response/lst[@name='expanded']/result[@name='group1']/doc[2]/float[@name='id'][.='1.0']",
"/response/lst[@name='expanded']/result[@name='group2']/doc[1]/float[@name='id'][.='8.0']",
"/response/lst[@name='expanded']/result[@name='group2']/doc[2]/float[@name='id'][.='5.0']"
);
//Test with nullPolicy, ExpandComponent should ignore docs with null values in the collapse fields.
//Main result set should include the doc with null value in the collapse field.
params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "{!collapse field=group_s nullPolicy=collapse}");
params.add("defType", "edismax");
params.add("bf", "field(test_ti)");
params.add("expand", "true");
params.add("expand.sort", "test_tl desc");
assertQ(req(params), "*[count(/response/result/doc)=3]",
"*[count(/response/lst[@name='expanded']/result)=2]",
"/response/result/doc[1]/float[@name='id'][.='3.0']",
"/response/result/doc[2]/float[@name='id'][.='2.0']",
"/response/result/doc[3]/float[@name='id'][.='6.0']",
"/response/lst[@name='expanded']/result[@name='group1']/doc[1]/float[@name='id'][.='7.0']",
"/response/lst[@name='expanded']/result[@name='group1']/doc[2]/float[@name='id'][.='1.0']",
"/response/lst[@name='expanded']/result[@name='group2']/doc[1]/float[@name='id'][.='8.0']",
"/response/lst[@name='expanded']/result[@name='group2']/doc[2]/float[@name='id'][.='5.0']"
);
//Test expand.rows
params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "{!collapse field=group_s}");
params.add("defType", "edismax");
params.add("bf", "field(test_ti)");
params.add("expand", "true");
params.add("expand.sort", "test_tl desc");
params.add("expand.rows", "1");
assertQ(req(params), "*[count(/response/result/doc)=2]",
"*[count(/response/lst[@name='expanded']/result)=2]",
"*[count(/response/lst[@name='expanded']/result[@name='group1']/doc)=1]",
"*[count(/response/lst[@name='expanded']/result[@name='group2']/doc)=1]",
"/response/result/doc[1]/float[@name='id'][.='2.0']",
"/response/result/doc[2]/float[@name='id'][.='6.0']",
"/response/lst[@name='expanded']/result[@name='group1']/doc[1]/float[@name='id'][.='7.0']",
"/response/lst[@name='expanded']/result[@name='group2']/doc[1]/float[@name='id'][.='8.0']"
);
//Test no group results
params = new ModifiableSolrParams();
params.add("q", "test_ti:5");
params.add("fq", "{!collapse field=group_s}");
params.add("defType", "edismax");
params.add("bf", "field(test_ti)");
params.add("expand", "true");
params.add("expand.sort", "test_tl desc");
params.add("expand.rows", "1");
assertQ(req(params), "*[count(/response/result/doc)=1]",
"*[count(/response/lst[@name='expanded']/result)=0]"
);
//Test zero results
params = new ModifiableSolrParams();
params.add("q", "test_ti:5532535");
params.add("fq", "{!collapse field=group_s}");
params.add("defType", "edismax");
params.add("bf", "field(test_ti)");
params.add("expand", "true");
params.add("expand.sort", "test_tl desc");
params.add("expand.rows", "1");
assertQ(req(params), "*[count(/response/result/doc)=0]",
"*[count(/response/lst[@name='expanded']/result)=0]"
);
}
}

View File

@ -50,6 +50,9 @@ public class QueryResponse extends SolrResponseBase
private NamedList<Object> _groupedInfo = null;
private GroupResponse _groupResponse = null;
private NamedList<Object> _expandedInfo = null;
private Map<String, SolrDocumentList> _expandedResults = null;
// Facet stuff
private Map<String,Integer> _facetQuery = null;
private List<FacetField> _facetFields = null;
@ -119,7 +122,10 @@ public class QueryResponse extends SolrResponseBase
_groupedInfo = (NamedList<Object>) res.getVal( i );
extractGroupedInfo( _groupedInfo );
}
else if( "highlighting".equals( n ) ) {
else if("expanded".equals(n)) {
_expandedResults = (Map<String, SolrDocumentList>) res.getVal( i );
}
else if( "highlighting".equals( n ) ) {
_highlightingInfo = (NamedList<Object>) res.getVal( i );
extractHighlightingInfo( _highlightingInfo );
}
@ -410,6 +416,10 @@ public class QueryResponse extends SolrResponseBase
return _facetQuery;
}
public Map<String, SolrDocumentList> getExpandedResults(){
return this._expandedResults;
}
/**
* Returns the {@link GroupResponse} containing the group commands.
* A group command can be the result of one of the following parameters:

View File

@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.common.params;
/**
* Expand parameters
*/
public interface ExpandParams {
public static final String EXPAND = "expand";
public static final String EXPAND_SORT = EXPAND + ".sort";
public static final String EXPAND_ROWS = EXPAND + ".rows";
}