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:
broustant 2018-06-09 11:32:24 -04:00 committed by David Smiley
parent 944b24fab8
commit a06256ccee
11 changed files with 1105 additions and 649 deletions

View File

@ -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
----------------------

View File

@ -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());
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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;

View File

@ -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();
}
}
}
}

View File

@ -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:

View File

@ -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";
}