mirror of https://github.com/apache/lucene.git
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:
parent
4eed6367ff
commit
f48d27f241
|
@ -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){
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]"
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue