mirror of https://github.com/apache/lucene.git
SOLR-11865: QueryElevationComponent changes
* new useConfiguredElevatedOrder setting * more extensible (customizable via subclass) ** ElevationProvider ** handleInitializationException with cause enum * use BytesRef for uniqueKey ID pervasively instead of String. * ElevatorComparatorSource now reuses getBoostedDocs logic * setSort will short-circuit if there are no elevated Ids * extensive refactoring and affects some interrelated components
This commit is contained in:
parent
944b24fab8
commit
a06256ccee
|
@ -67,7 +67,10 @@ Upgrade Notes
|
|||
New Features
|
||||
----------------------
|
||||
|
||||
(no changes)
|
||||
* SOLR-11865: The QueryElevationComponent now has a useConfiguredElevatedOrder setting. When multiple docs are elevated,
|
||||
this specifies whether their relative order should be the order in the configuration file or if not then should they
|
||||
be subject to whatever the sort criteria is. Additionally, QEC was extensively refactored to be more extensible.
|
||||
(Bruno Roustant, David Smiley)
|
||||
|
||||
Bug Fixes
|
||||
----------------------
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -20,6 +20,8 @@ package org.apache.solr.response.transform;
|
|||
import java.util.Set;
|
||||
|
||||
import org.apache.lucene.index.IndexableField;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.BytesRefBuilder;
|
||||
import org.apache.solr.common.SolrDocument;
|
||||
import org.apache.solr.schema.FieldType;
|
||||
|
||||
|
@ -47,9 +49,9 @@ public abstract class BaseEditorialTransformer extends DocTransformer {
|
|||
@Override
|
||||
public void transform(SolrDocument doc, int docid) {
|
||||
//this only gets added if QueryElevationParams.MARK_EXCLUDED is true
|
||||
Set<String> ids = getIdSet();
|
||||
Set<BytesRef> ids = getIdSet();
|
||||
if (ids != null && ids.isEmpty() == false) {
|
||||
String key = getKey(doc);
|
||||
BytesRef key = getKey(doc);
|
||||
doc.setField(name, ids.contains(key));
|
||||
} else {
|
||||
//if we have no ids, that means we weren't marking, but the user still asked for the field to be added, so just mark everything as false
|
||||
|
@ -57,17 +59,20 @@ public abstract class BaseEditorialTransformer extends DocTransformer {
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract Set<String> getIdSet();
|
||||
protected abstract Set<BytesRef> getIdSet();
|
||||
|
||||
protected String getKey(SolrDocument doc) {
|
||||
protected BytesRef getKey(SolrDocument doc) {
|
||||
Object obj = doc.get(idFieldName);
|
||||
if (obj instanceof IndexableField) {
|
||||
IndexableField f = (IndexableField) obj;
|
||||
BytesRefBuilder bytesRefBuilder = new BytesRefBuilder();
|
||||
Number n = f.numericValue();
|
||||
if (n != null) {
|
||||
return ft.readableToIndexed(n.toString());
|
||||
ft.readableToIndexed(n.toString(), bytesRefBuilder);
|
||||
} else {
|
||||
ft.readableToIndexed(f.stringValue(), bytesRefBuilder);
|
||||
}
|
||||
return ft.readableToIndexed(f.stringValue());
|
||||
return bytesRefBuilder.get();
|
||||
}
|
||||
throw new AssertionError("Expected an IndexableField but got: " + obj.getClass());
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.apache.solr.response.transform;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.handler.component.QueryElevationComponent;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
|
@ -44,9 +45,10 @@ class MarkTransformer extends BaseEditorialTransformer {
|
|||
super(name, idFieldName, ft);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Set<String> getIdSet() {
|
||||
return (Set<String>) context.getRequest().getContext().get(QueryElevationComponent.BOOSTED);
|
||||
protected Set<BytesRef> getIdSet() {
|
||||
return (Set<BytesRef>) context.getRequest().getContext().get(QueryElevationComponent.BOOSTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.apache.solr.response.transform;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.handler.component.QueryElevationComponent;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
|
@ -46,9 +47,10 @@ class ExcludedTransformer extends BaseEditorialTransformer {
|
|||
super(name, idFieldName, ft);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Set<String> getIdSet() {
|
||||
return (Set<String>)context.getRequest().getContext().get(QueryElevationComponent.EXCLUDED);
|
||||
protected Set<BytesRef> getIdSet() {
|
||||
return (Set<BytesRef>)context.getRequest().getContext().get(QueryElevationComponent.EXCLUDED);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.apache.solr.search;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
|
@ -35,7 +36,7 @@ public abstract class AbstractReRankQuery extends RankQuery {
|
|||
protected Query mainQuery;
|
||||
final protected int reRankDocs;
|
||||
final protected Rescorer reRankQueryRescorer;
|
||||
protected Map<BytesRef, Integer> boostedPriority;
|
||||
protected Set<BytesRef> boostedPriority;
|
||||
|
||||
public AbstractReRankQuery(Query mainQuery, int reRankDocs, Rescorer reRankQueryRescorer) {
|
||||
this.mainQuery = mainQuery;
|
||||
|
@ -54,13 +55,13 @@ public abstract class AbstractReRankQuery extends RankQuery {
|
|||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public TopDocsCollector getTopDocsCollector(int len, QueryCommand cmd, IndexSearcher searcher) throws IOException {
|
||||
|
||||
if(this.boostedPriority == null) {
|
||||
SolrRequestInfo info = SolrRequestInfo.getRequestInfo();
|
||||
if(info != null) {
|
||||
Map context = info.getReq().getContext();
|
||||
this.boostedPriority = (Map<BytesRef, Integer>)context.get(QueryElevationComponent.BOOSTED_PRIORITY);
|
||||
this.boostedPriority = (Set<BytesRef>)context.get(QueryElevationComponent.BOOSTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,14 @@ import java.util.HashMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.carrotsearch.hppc.FloatArrayList;
|
||||
import com.carrotsearch.hppc.IntArrayList;
|
||||
import com.carrotsearch.hppc.IntIntHashMap;
|
||||
import com.carrotsearch.hppc.IntLongHashMap;
|
||||
import com.carrotsearch.hppc.cursors.IntIntCursor;
|
||||
import com.carrotsearch.hppc.cursors.IntLongCursor;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.lucene.codecs.DocValuesProducer;
|
||||
import org.apache.lucene.index.DocValues;
|
||||
|
@ -69,13 +76,6 @@ import org.apache.solr.schema.NumberType;
|
|||
import org.apache.solr.schema.StrField;
|
||||
import org.apache.solr.uninverting.UninvertingReader;
|
||||
|
||||
import com.carrotsearch.hppc.FloatArrayList;
|
||||
import com.carrotsearch.hppc.IntArrayList;
|
||||
import com.carrotsearch.hppc.IntIntHashMap;
|
||||
import com.carrotsearch.hppc.IntLongHashMap;
|
||||
import com.carrotsearch.hppc.cursors.IntIntCursor;
|
||||
import com.carrotsearch.hppc.cursors.IntLongCursor;
|
||||
|
||||
import static org.apache.solr.common.params.CommonParams.SORT;
|
||||
|
||||
/**
|
||||
|
@ -215,7 +215,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
|
|||
public String hint;
|
||||
private boolean needsScores = true;
|
||||
private int nullPolicy;
|
||||
private Map<BytesRef, Integer> boosted;
|
||||
private Set<BytesRef> boosted; // ordered by "priority"
|
||||
public static final int NULL_POLICY_IGNORE = 0;
|
||||
public static final int NULL_POLICY_COLLAPSE = 1;
|
||||
public static final int NULL_POLICY_EXPAND = 2;
|
||||
|
@ -338,11 +338,6 @@ public class CollapsingQParserPlugin extends QParserPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
private IntIntHashMap getBoostDocs(SolrIndexSearcher indexSearcher, Map<BytesRef, Integer> boosted, Map context) throws IOException {
|
||||
IntIntHashMap boostDocs = QueryElevationComponent.getBoostDocs(indexSearcher, boosted, context);
|
||||
return boostDocs;
|
||||
}
|
||||
|
||||
public DelegatingCollector getFilterCollector(IndexSearcher indexSearcher) {
|
||||
try {
|
||||
|
||||
|
@ -360,10 +355,10 @@ public class CollapsingQParserPlugin extends QParserPlugin {
|
|||
}
|
||||
|
||||
if(this.boosted == null && context != null) {
|
||||
this.boosted = (Map<BytesRef, Integer>)context.get(QueryElevationComponent.BOOSTED_PRIORITY);
|
||||
this.boosted = (Set<BytesRef>)context.get(QueryElevationComponent.BOOSTED);
|
||||
}
|
||||
|
||||
boostDocsMap = getBoostDocs(searcher, this.boosted, context);
|
||||
boostDocsMap = QueryElevationComponent.getBoostDocs(searcher, this.boosted, context);
|
||||
return collectorFactory.getCollector(this.collapseField,
|
||||
this.groupHeadSelector,
|
||||
this.sortSpec,
|
||||
|
|
|
@ -20,9 +20,10 @@ import java.io.IOException;
|
|||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.carrotsearch.hppc.IntFloatHashMap;
|
||||
import com.carrotsearch.hppc.IntIntHashMap;
|
||||
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.LeafCollector;
|
||||
|
@ -46,7 +47,7 @@ public class ReRankCollector extends TopDocsCollector {
|
|||
final private IndexSearcher searcher;
|
||||
final private int reRankDocs;
|
||||
final private int length;
|
||||
final private Map<BytesRef, Integer> boostedPriority;
|
||||
final private Set<BytesRef> boostedPriority; // order is the "priority"
|
||||
final private Rescorer reRankQueryRescorer;
|
||||
|
||||
|
||||
|
@ -55,7 +56,7 @@ public class ReRankCollector extends TopDocsCollector {
|
|||
Rescorer reRankQueryRescorer,
|
||||
QueryCommand cmd,
|
||||
IndexSearcher searcher,
|
||||
Map<BytesRef, Integer> boostedPriority) throws IOException {
|
||||
Set<BytesRef> boostedPriority) throws IOException {
|
||||
super(null);
|
||||
this.reRankDocs = reRankDocs;
|
||||
this.length = length;
|
||||
|
|
|
@ -16,33 +16,29 @@
|
|||
*/
|
||||
package org.apache.solr.handler.component;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.common.params.CommonParams;
|
||||
import org.apache.solr.common.params.GroupParams;
|
||||
import org.apache.solr.common.params.MapSolrParams;
|
||||
import org.apache.solr.common.params.QueryElevationParams;
|
||||
import org.apache.solr.util.FileUtils;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
import org.apache.solr.handler.component.QueryElevationComponent.ElevationObj;
|
||||
import org.apache.solr.request.LocalSolrQueryRequest;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.common.params.CommonParams;
|
||||
import org.apache.solr.common.params.GroupParams;
|
||||
import org.apache.solr.common.params.QueryElevationParams;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
import org.apache.solr.search.SolrIndexSearcher;
|
||||
import org.apache.solr.util.FileUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
||||
|
||||
|
@ -71,6 +67,7 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
assertU(commit());
|
||||
}
|
||||
|
||||
//TODO should be @After ?
|
||||
private void delete() throws Exception {
|
||||
deleteCore();
|
||||
}
|
||||
|
@ -123,22 +120,22 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
assertU(adoc("id", "7", "text", "AAAA AAAA ZZZZ", "str_s", "g"));
|
||||
assertU(adoc("id", "8", "text", "XXXX", "str_s", "h"));
|
||||
assertU(adoc("id", "9", "text", "YYYY ZZZZ", "str_s", "i"));
|
||||
|
||||
|
||||
assertU(adoc("id", "22", "text", "XXXX ZZZZ AAAA", "str_s", "b"));
|
||||
assertU(adoc("id", "66", "text", "XXXX ZZZZ AAAA", "str_s", "f"));
|
||||
assertU(adoc("id", "77", "text", "XXXX ZZZZ AAAA", "str_s", "g"));
|
||||
|
||||
|
||||
assertU(commit());
|
||||
|
||||
final String groups = "//arr[@name='groups']";
|
||||
|
||||
assertQ("non-elevated group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
assertQ("non-elevated group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
CommonParams.QT, "/elevate",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP, "true",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
QueryElevationParams.ENABLE, "false",
|
||||
CommonParams.FL, "id, score, [elevated]")
|
||||
, "//*[@name='ngroups'][.='3']"
|
||||
|
@ -160,13 +157,13 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
, groups +"/lst[3]//doc[2]/bool[@name='[elevated]'][.='false']"
|
||||
);
|
||||
|
||||
assertQ("elevated group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
assertQ("elevated group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
CommonParams.QT, "/elevate",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP, "true",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
CommonParams.FL, "id, score, [elevated]")
|
||||
, "//*[@name='ngroups'][.='3']"
|
||||
, "//*[@name='matches'][.='6']"
|
||||
|
@ -187,14 +184,14 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
, groups +"/lst[3]//doc[2]/bool[@name='[elevated]'][.='false']"
|
||||
);
|
||||
|
||||
assertQ("non-elevated because sorted group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
assertQ("non-elevated because sorted group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
CommonParams.QT, "/elevate",
|
||||
CommonParams.SORT, "id asc",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP, "true",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
CommonParams.FL, "id, score, [elevated]")
|
||||
, "//*[@name='ngroups'][.='3']"
|
||||
, "//*[@name='matches'][.='6']"
|
||||
|
@ -215,15 +212,15 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
, groups +"/lst[3]//doc[2]/bool[@name='[elevated]'][.='false']"
|
||||
);
|
||||
|
||||
assertQ("force-elevated sorted group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
assertQ("force-elevated sorted group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
CommonParams.QT, "/elevate",
|
||||
CommonParams.SORT, "id asc",
|
||||
QueryElevationParams.FORCE_ELEVATION, "true",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
QueryElevationParams.FORCE_ELEVATION, "true",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP, "true",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
CommonParams.FL, "id, score, [elevated]")
|
||||
, "//*[@name='ngroups'][.='3']"
|
||||
, "//*[@name='matches'][.='6']"
|
||||
|
@ -245,15 +242,15 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
);
|
||||
|
||||
|
||||
assertQ("non-elevated because of sort within group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
assertQ("non-elevated because of sort within group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
CommonParams.QT, "/elevate",
|
||||
CommonParams.SORT, "id asc",
|
||||
GroupParams.GROUP_SORT, "id desc",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP_SORT, "id desc",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP, "true",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
CommonParams.FL, "id, score, [elevated]")
|
||||
, "//*[@name='ngroups'][.='3']"
|
||||
, "//*[@name='matches'][.='6']"
|
||||
|
@ -275,16 +272,16 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
);
|
||||
|
||||
|
||||
assertQ("force elevated sort within sorted group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
assertQ("force elevated sort within sorted group query",
|
||||
req(CommonParams.Q, "AAAA",
|
||||
CommonParams.QT, "/elevate",
|
||||
CommonParams.SORT, "id asc",
|
||||
GroupParams.GROUP_SORT, "id desc",
|
||||
QueryElevationParams.FORCE_ELEVATION, "true",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP_SORT, "id desc",
|
||||
QueryElevationParams.FORCE_ELEVATION, "true",
|
||||
GroupParams.GROUP_FIELD, "str_s",
|
||||
GroupParams.GROUP, "true",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
GroupParams.GROUP_TOTAL_COUNT, "true",
|
||||
GroupParams.GROUP_LIMIT, "100",
|
||||
CommonParams.FL, "id, score, [elevated]")
|
||||
, "//*[@name='ngroups'][.='3']"
|
||||
, "//*[@name='matches'][.='6']"
|
||||
|
@ -360,17 +357,17 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
|
||||
SolrQueryRequest req = req();
|
||||
IndexReader reader = req.getSearcher().getIndexReader();
|
||||
Map<String, ElevationObj> map = comp.getElevationMap(reader, core);
|
||||
QueryElevationComponent.ElevationProvider elevationProvider = comp.getElevationProvider(reader, core);
|
||||
req.close();
|
||||
|
||||
// Make sure the boosts loaded properly
|
||||
assertEquals(7, map.size());
|
||||
assertEquals(1, map.get("XXXX").priority.size());
|
||||
assertEquals(2, map.get("YYYY").priority.size());
|
||||
assertEquals(3, map.get("ZZZZ").priority.size());
|
||||
assertEquals(null, map.get("xxxx"));
|
||||
assertEquals(null, map.get("yyyy"));
|
||||
assertEquals(null, map.get("zzzz"));
|
||||
assertEquals(7, elevationProvider.size());
|
||||
assertEquals(1, elevationProvider.getElevationForQuery("XXXX").elevatedIds.size());
|
||||
assertEquals(2, elevationProvider.getElevationForQuery("YYYY").elevatedIds.size());
|
||||
assertEquals(3, elevationProvider.getElevationForQuery("ZZZZ").elevatedIds.size());
|
||||
assertEquals(null, elevationProvider.getElevationForQuery("xxxx"));
|
||||
assertEquals(null, elevationProvider.getElevationForQuery("yyyy"));
|
||||
assertEquals(null, elevationProvider.getElevationForQuery("zzzz"));
|
||||
|
||||
// Now test the same thing with a lowercase filter: 'lowerfilt'
|
||||
args = new NamedList<>();
|
||||
|
@ -380,17 +377,17 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
comp = new QueryElevationComponent();
|
||||
comp.init(args);
|
||||
comp.inform(core);
|
||||
map = comp.getElevationMap(reader, core);
|
||||
assertEquals(7, map.size());
|
||||
assertEquals(null, map.get("XXXX"));
|
||||
assertEquals(null, map.get("YYYY"));
|
||||
assertEquals(null, map.get("ZZZZ"));
|
||||
assertEquals(1, map.get("xxxx").priority.size());
|
||||
assertEquals(2, map.get("yyyy").priority.size());
|
||||
assertEquals(3, map.get("zzzz").priority.size());
|
||||
elevationProvider = comp.getElevationProvider(reader, core);
|
||||
assertEquals(7, elevationProvider.size());
|
||||
assertEquals(1, elevationProvider.getElevationForQuery("XXXX").elevatedIds.size());
|
||||
assertEquals(2, elevationProvider.getElevationForQuery("YYYY").elevatedIds.size());
|
||||
assertEquals(3, elevationProvider.getElevationForQuery("ZZZZ").elevatedIds.size());
|
||||
assertEquals(1, elevationProvider.getElevationForQuery("xxxx").elevatedIds.size());
|
||||
assertEquals(2, elevationProvider.getElevationForQuery("yyyy").elevatedIds.size());
|
||||
assertEquals(3, elevationProvider.getElevationForQuery("zzzz").elevatedIds.size());
|
||||
|
||||
assertEquals("xxxx", comp.getAnalyzedQuery("XXXX"));
|
||||
assertEquals("xxxxyyyy", comp.getAnalyzedQuery("XXXX YYYY"));
|
||||
assertEquals("xxxx", comp.analyzeQuery("XXXX"));
|
||||
assertEquals("xxxxyyyy", comp.analyzeQuery("XXXX YYYY"));
|
||||
|
||||
assertQ("Make sure QEC handles null queries", req("qt", "/elevate", "q.alt", "*:*", "defType", "dismax"),
|
||||
"//*[@numFound='0']");
|
||||
|
@ -456,11 +453,11 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
assertU(adoc("id", "5", "title", "YYYY YYYY", "str_s1", "y"));
|
||||
assertU(adoc("id", "6", "title", "XXXX XXXX", "str_s1", "z"));
|
||||
assertU(adoc("id", "7", "title", "AAAA", "str_s1", "a"));
|
||||
|
||||
|
||||
assertU(adoc("id", "8", "title", " QQQQ trash trash", "str_s1", "q"));
|
||||
assertU(adoc("id", "9", "title", " QQQQ QQQQ trash", "str_s1", "r"));
|
||||
assertU(adoc("id", "10", "title", "QQQQ QQQQ QQQQ ", "str_s1", "s"));
|
||||
|
||||
|
||||
assertU(commit());
|
||||
|
||||
assertQ("", req(CommonParams.Q, "XXXX XXXX", CommonParams.QT, "/elevate",
|
||||
|
@ -477,7 +474,7 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
"//result/doc[3]/bool[@name='[excluded]'][.='false']",
|
||||
"//result/doc[4]/bool[@name='[excluded]'][.='true']"
|
||||
);
|
||||
|
||||
|
||||
//ask for excluded as a field, but don't actually request the MARK_EXCLUDES
|
||||
//thus, number 6 should not be returned, b/c it is excluded
|
||||
assertQ("", req(CommonParams.Q, "XXXX XXXX", CommonParams.QT, "/elevate",
|
||||
|
@ -491,7 +488,7 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
"//result/doc[2]/bool[@name='[excluded]'][.='false']",
|
||||
"//result/doc[3]/bool[@name='[excluded]'][.='false']"
|
||||
);
|
||||
|
||||
|
||||
// test that excluded results are on the same positions in the result list
|
||||
// as when elevation component is disabled
|
||||
// (i.e. test that elevation component with MARK_EXCLUDES does not boost
|
||||
|
@ -526,28 +523,27 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
public void testSorting() throws Exception {
|
||||
try {
|
||||
init("schema12.xml");
|
||||
assertU(adoc("id", "a", "title", "ipod trash trash", "str_s1", "a"));
|
||||
assertU(adoc("id", "b", "title", "ipod ipod trash", "str_s1", "b"));
|
||||
assertU(adoc("id", "c", "title", "ipod ipod ipod ", "str_s1", "c"));
|
||||
assertU(adoc("id", "a", "title", "ipod trash trash", "str_s1", "group1"));
|
||||
assertU(adoc("id", "b", "title", "ipod ipod trash", "str_s1", "group2"));
|
||||
assertU(adoc("id", "c", "title", "ipod ipod ipod ", "str_s1", "group2"));
|
||||
|
||||
assertU(adoc("id", "x", "title", "boosted", "str_s1", "x"));
|
||||
assertU(adoc("id", "y", "title", "boosted boosted", "str_s1", "y"));
|
||||
assertU(adoc("id", "z", "title", "boosted boosted boosted", "str_s1", "z"));
|
||||
assertU(adoc("id", "x", "title", "boosted", "str_s1", "group1"));
|
||||
assertU(adoc("id", "y", "title", "boosted boosted", "str_s1", "group2"));
|
||||
assertU(adoc("id", "z", "title", "boosted boosted boosted", "str_s1", "group2"));
|
||||
assertU(commit());
|
||||
|
||||
String query = "title:ipod";
|
||||
final String query = "title:ipod";
|
||||
|
||||
Map<String, String> args = new HashMap<>(); // reusing args & requests this way is a solr-test-antipattern. PLEASE DO NOT COPY THIS CODE
|
||||
args.put(CommonParams.Q, query);
|
||||
args.put(CommonParams.QT, "/elevate");
|
||||
args.put(CommonParams.FL, "id,score");
|
||||
args.put("indent", "true");
|
||||
//args.put( CommonParams.FL, "id,title,score" );
|
||||
SolrQueryRequest req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
IndexReader reader = req.getSearcher().getIndexReader();
|
||||
QueryElevationComponent booster = (QueryElevationComponent) req.getCore().getSearchComponent("elevate");
|
||||
final SolrParams baseParams = params(
|
||||
"qt", "/elevate",
|
||||
"q", query,
|
||||
"fl", "id,score",
|
||||
"indent", "true");
|
||||
|
||||
assertQ("Make sure standard sort works as expected", req
|
||||
QueryElevationComponent booster = (QueryElevationComponent) h.getCore().getSearchComponent("elevate");
|
||||
IndexReader reader = h.getCore().withSearcher(SolrIndexSearcher::getIndexReader);
|
||||
|
||||
assertQ("Make sure standard sort works as expected", req(baseParams)
|
||||
, "//*[@numFound='3']"
|
||||
, "//result/doc[1]/str[@name='id'][.='c']"
|
||||
, "//result/doc[2]/str[@name='id'][.='b']"
|
||||
|
@ -555,12 +551,9 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
);
|
||||
|
||||
// Explicitly set what gets boosted
|
||||
booster.elevationCache.clear();
|
||||
booster.setTopQueryResults(reader, query, new String[]{"x", "y", "z"}, null);
|
||||
booster.setTopQueryResults(reader, query, false, new String[]{"x", "y", "z"}, null);
|
||||
|
||||
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
assertQ("All six should make it", req
|
||||
assertQ("All six should make it", req(baseParams)
|
||||
, "//*[@numFound='6']"
|
||||
, "//result/doc[1]/str[@name='id'][.='x']"
|
||||
, "//result/doc[2]/str[@name='id'][.='y']"
|
||||
|
@ -570,12 +563,9 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
, "//result/doc[6]/str[@name='id'][.='a']"
|
||||
);
|
||||
|
||||
booster.elevationCache.clear();
|
||||
|
||||
// now switch the order:
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
booster.setTopQueryResults(reader, query, new String[]{"a", "x"}, null);
|
||||
assertQ("All four should make it", req
|
||||
booster.setTopQueryResults(reader, query, false, new String[]{"a", "x"}, null);
|
||||
assertQ(req(baseParams)
|
||||
, "//*[@numFound='4']"
|
||||
, "//result/doc[1]/str[@name='id'][.='a']"
|
||||
, "//result/doc[2]/str[@name='id'][.='x']"
|
||||
|
@ -583,33 +573,10 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
, "//result/doc[4]/str[@name='id'][.='b']"
|
||||
);
|
||||
|
||||
// Test reverse sort
|
||||
args.put(CommonParams.SORT, "score asc");
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
assertQ("All four should make it", req
|
||||
, "//*[@numFound='4']"
|
||||
// NOTE REVERSED doc[X] indices
|
||||
, "//result/doc[4]/str[@name='id'][.='a']"
|
||||
, "//result/doc[3]/str[@name='id'][.='x']"
|
||||
, "//result/doc[2]/str[@name='id'][.='c']"
|
||||
, "//result/doc[1]/str[@name='id'][.='b']"
|
||||
);
|
||||
|
||||
// Try normal sort by 'id'
|
||||
// default 'forceBoost' should be false
|
||||
assertEquals(false, booster.forceElevation);
|
||||
args.put(CommonParams.SORT, "str_s1 asc");
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
assertQ(null, req
|
||||
, "//*[@numFound='4']"
|
||||
, "//result/doc[1]/str[@name='id'][.='a']"
|
||||
, "//result/doc[2]/str[@name='id'][.='b']"
|
||||
, "//result/doc[3]/str[@name='id'][.='c']"
|
||||
, "//result/doc[4]/str[@name='id'][.='x']"
|
||||
);
|
||||
args.put(CommonParams.SORT, "id asc");
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
assertQ(null, req
|
||||
assertQ(req(baseParams, "sort", "id asc")
|
||||
, "//*[@numFound='4']"
|
||||
, "//result/doc[1]/str[@name='id'][.='a']"
|
||||
, "//result/doc[2]/str[@name='id'][.='b']"
|
||||
|
@ -617,10 +584,17 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
, "//result/doc[4]/str[@name='id'][.='x']"
|
||||
);
|
||||
|
||||
assertQ("useConfiguredElevatedOrder=false",
|
||||
req(baseParams, "sort", "str_s1 asc,id desc", "useConfiguredElevatedOrder", "false")
|
||||
, "//*[@numFound='4']"
|
||||
, "//result/doc[1]/str[@name='id'][.='x']"//group1
|
||||
, "//result/doc[2]/str[@name='id'][.='a']"//group1
|
||||
, "//result/doc[3]/str[@name='id'][.='c']"
|
||||
, "//result/doc[4]/str[@name='id'][.='b']"
|
||||
);
|
||||
|
||||
booster.forceElevation = true;
|
||||
args.put(CommonParams.SORT, "id asc");
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
assertQ(null, req
|
||||
assertQ(req(baseParams, "sort", "id asc")
|
||||
, "//*[@numFound='4']"
|
||||
, "//result/doc[1]/str[@name='id'][.='a']"
|
||||
, "//result/doc[2]/str[@name='id'][.='x']"
|
||||
|
@ -628,23 +602,27 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
, "//result/doc[4]/str[@name='id'][.='c']"
|
||||
);
|
||||
|
||||
booster.forceElevation = true;
|
||||
assertQ("useConfiguredElevatedOrder=false and forceElevation",
|
||||
req(baseParams, "sort", "id desc", "useConfiguredElevatedOrder", "false")
|
||||
, "//*[@numFound='4']"
|
||||
, "//result/doc[1]/str[@name='id'][.='x']" // force elevated
|
||||
, "//result/doc[2]/str[@name='id'][.='a']" // force elevated
|
||||
, "//result/doc[3]/str[@name='id'][.='c']"
|
||||
, "//result/doc[4]/str[@name='id'][.='b']"
|
||||
);
|
||||
|
||||
//Test exclusive (not to be confused with exclusion)
|
||||
args.put(QueryElevationParams.EXCLUSIVE, "true");
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
booster.setTopQueryResults(reader, query, new String[]{"x", "a"}, new String[]{});
|
||||
assertQ(null, req
|
||||
booster.setTopQueryResults(reader, query, false, new String[]{"x", "a"}, new String[]{});
|
||||
assertQ(req(baseParams, "exclusive", "true")
|
||||
, "//*[@numFound='2']"
|
||||
, "//result/doc[1]/str[@name='id'][.='x']"
|
||||
, "//result/doc[2]/str[@name='id'][.='a']"
|
||||
);
|
||||
|
||||
// Test exclusion
|
||||
booster.elevationCache.clear();
|
||||
args.remove(CommonParams.SORT);
|
||||
args.remove(QueryElevationParams.EXCLUSIVE);
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
booster.setTopQueryResults(reader, query, new String[]{"x"}, new String[]{"a"});
|
||||
assertQ(null, req
|
||||
booster.setTopQueryResults(reader, query, false, new String[]{"x"}, new String[]{"a"});
|
||||
assertQ(req(baseParams)
|
||||
, "//*[@numFound='3']"
|
||||
, "//result/doc[1]/str[@name='id'][.='x']"
|
||||
, "//result/doc[2]/str[@name='id'][.='c']"
|
||||
|
@ -654,11 +632,8 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
|
||||
// Test setting ids and excludes from http parameters
|
||||
|
||||
booster.elevationCache.clear();
|
||||
args.put(QueryElevationParams.IDS, "x,y,z");
|
||||
args.put(QueryElevationParams.EXCLUDE, "b");
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
assertQ("All five should make it", req
|
||||
booster.clearElevationProviderCache();
|
||||
assertQ("All five should make it", req(baseParams, "elevateIds", "x,y,z", "excludeIds", "b")
|
||||
, "//*[@numFound='5']"
|
||||
, "//result/doc[1]/str[@name='id'][.='x']"
|
||||
, "//result/doc[2]/str[@name='id'][.='y']"
|
||||
|
@ -667,10 +642,7 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
, "//result/doc[5]/str[@name='id'][.='a']"
|
||||
);
|
||||
|
||||
args.put(QueryElevationParams.IDS, "x,z,y");
|
||||
args.put(QueryElevationParams.EXCLUDE, "b,c");
|
||||
req.close(); req = new LocalSolrQueryRequest(h.getCore(), new MapSolrParams(args));
|
||||
assertQ("All four should make it", req
|
||||
assertQ("All four should make it", req(baseParams, "elevateIds", "x,z,y", "excludeIds", "b,c")
|
||||
, "//*[@numFound='4']"
|
||||
, "//result/doc[1]/str[@name='id'][.='x']"
|
||||
, "//result/doc[2]/str[@name='id'][.='z']"
|
||||
|
@ -678,7 +650,6 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
, "//result/doc[4]/str[@name='id'][.='a']"
|
||||
);
|
||||
|
||||
req.close();
|
||||
} finally {
|
||||
delete();
|
||||
}
|
||||
|
@ -706,8 +677,8 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
try {
|
||||
init("schema12.xml");
|
||||
String testfile = "data-elevation.xml";
|
||||
File f = new File(h.getCore().getDataDir(), testfile);
|
||||
writeFile(f, "aaa", "A");
|
||||
File configFile = new File(h.getCore().getDataDir(), testfile);
|
||||
writeFile(configFile, "aaa", "A");
|
||||
|
||||
QueryElevationComponent comp = (QueryElevationComponent) h.getCore().getSearchComponent("elevate");
|
||||
NamedList<String> args = new NamedList<>();
|
||||
|
@ -715,24 +686,47 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
comp.init(args);
|
||||
comp.inform(h.getCore());
|
||||
|
||||
SolrQueryRequest req = req();
|
||||
IndexReader reader = req.getSearcher().getIndexReader();
|
||||
Map<String, ElevationObj> map = comp.getElevationMap(reader, h.getCore());
|
||||
assertTrue(map.get("aaa").priority.containsKey(new BytesRef("A")));
|
||||
assertNull(map.get("bbb"));
|
||||
req.close();
|
||||
QueryElevationComponent.ElevationProvider elevationProvider = null;
|
||||
|
||||
try (SolrQueryRequest req = req()) {
|
||||
elevationProvider = comp.getElevationProvider(req.getSearcher().getIndexReader(), req.getCore());
|
||||
assertTrue(elevationProvider.getElevationForQuery("aaa").elevatedIds.contains(new BytesRef("A")));
|
||||
assertNull(elevationProvider.getElevationForQuery("bbb"));
|
||||
}
|
||||
|
||||
// now change the file
|
||||
writeFile(f, "bbb", "B");
|
||||
assertU(adoc("id", "10000")); // will get same reader if no index change
|
||||
writeFile(configFile, "bbb", "B");
|
||||
|
||||
// With no index change, we get the same index reader, so the elevationProviderCache returns the previous ElevationProvider without the change.
|
||||
try (SolrQueryRequest req = req()) {
|
||||
elevationProvider = comp.getElevationProvider(req.getSearcher().getIndexReader(), req.getCore());
|
||||
assertTrue(elevationProvider.getElevationForQuery("aaa").elevatedIds.contains(new BytesRef("A")));
|
||||
assertNull(elevationProvider.getElevationForQuery("bbb"));
|
||||
}
|
||||
|
||||
// Index a new doc to get a new index reader.
|
||||
assertU(adoc("id", "10000"));
|
||||
assertU(commit());
|
||||
|
||||
req = req();
|
||||
reader = req.getSearcher().getIndexReader();
|
||||
map = comp.getElevationMap(reader, h.getCore());
|
||||
assertNull(map.get("aaa"));
|
||||
assertTrue(map.get("bbb").priority.containsKey(new BytesRef("B")));
|
||||
req.close();
|
||||
// Check that we effectively reload a new ElevationProvider for a different index reader (so two entries in elevationProviderCache).
|
||||
try (SolrQueryRequest req = req()) {
|
||||
elevationProvider = comp.getElevationProvider(req.getSearcher().getIndexReader(), req.getCore());
|
||||
assertNull(elevationProvider.getElevationForQuery("aaa"));
|
||||
assertTrue(elevationProvider.getElevationForQuery("bbb").elevatedIds.contains(new BytesRef("B")));
|
||||
}
|
||||
|
||||
// Now change the config file again.
|
||||
writeFile(configFile, "ccc", "C");
|
||||
|
||||
// Without index change, but calling a different method that clears the elevationProviderCache, so we should load a new ElevationProvider.
|
||||
int elevationRuleNumber = comp.loadElevationConfiguration(h.getCore());
|
||||
assertEquals(1, elevationRuleNumber);
|
||||
try (SolrQueryRequest req = req()) {
|
||||
elevationProvider = comp.getElevationProvider(req.getSearcher().getIndexReader(), req.getCore());
|
||||
assertNull(elevationProvider.getElevationForQuery("aaa"));
|
||||
assertNull(elevationProvider.getElevationForQuery("bbb"));
|
||||
assertTrue(elevationProvider.getElevationForQuery("ccc").elevatedIds.contains(new BytesRef("C")));
|
||||
}
|
||||
} finally {
|
||||
delete();
|
||||
}
|
||||
|
@ -769,4 +763,5 @@ public class QueryElevationComponentTest extends SolrTestCaseJ4 {
|
|||
delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -68,6 +68,13 @@ Path to the file that defines query elevation. This file must exist in `<instanc
|
|||
|
||||
`forceElevation`::
|
||||
By default, this component respects the requested `sort` parameter: if the request asks to sort by date, it will order the results by date. If `forceElevation=true` (the default), results will first return the boosted docs, then order by date.
|
||||
This is also a request parameter, which will override the config.
|
||||
|
||||
`useConfiguredElevatedOrder`::
|
||||
When multiple docs are elevated, should their relative order be the order in the configuration file or should
|
||||
they be subject to whatever the sort criteria is? True by default.
|
||||
This is also a request parameter, which will override the config.
|
||||
The effect is most apparent when forceElevation is true and there is sorting on fields.
|
||||
|
||||
=== The elevate.xml File
|
||||
|
||||
|
@ -119,6 +126,10 @@ You can force Solr to return only the results specified in the elevation file by
|
|||
[source,text]
|
||||
http://localhost:8983/solr/techproducts/elevate?q=ipod&df=text&debugQuery=true&exclusive=true
|
||||
|
||||
=== The useConfiguredElevatedOrder Parameter
|
||||
|
||||
You can force set `useConfiguredElevatedOrder` during runtime by supplying it as a request parameter.
|
||||
|
||||
=== Document Transformers and the markExcludes Parameter
|
||||
|
||||
The `[elevated]` <<transforming-result-documents.adoc#transforming-result-documents,Document Transformer>> can be used to annotate each document with information about whether or not it was elevated:
|
||||
|
|
|
@ -34,6 +34,7 @@ public interface QueryElevationParams {
|
|||
* See http://wiki.apache.org/solr/DocTransformers
|
||||
*/
|
||||
String EDITORIAL_MARKER_FIELD_NAME = "editorialMarkerFieldName";
|
||||
|
||||
/**
|
||||
* The name of the field that excluded editorial results will be written out as when using the QueryElevationComponent, which
|
||||
* automatically configures the EditorialMarkerFactory. The default name is "excluded". This is only used
|
||||
|
@ -48,4 +49,10 @@ public interface QueryElevationParams {
|
|||
* as excluded.
|
||||
*/
|
||||
String MARK_EXCLUDES = "markExcludes";
|
||||
}
|
||||
|
||||
/**
|
||||
* When multiple docs are elevated, should their relative order be the order in the configuration file or should
|
||||
* they be subject to whatever the sort criteria is? True by default.
|
||||
*/
|
||||
String USE_CONFIGURED_ELEVATED_ORDER = "useConfiguredElevatedOrder";
|
||||
}
|
Loading…
Reference in New Issue