SOLR-13289: Add Support for BlockMax WAND (#1456)

Add support for BlockMax WAND via a minExactHits parameter. Hits will be counted accurately at least until this value, and above that, the count will be an approximation. In distributed search requests, the count will be per shard, so potentially the count will be accurately counted until numShards * minExactHits. The response will include the value numFoundExact which can be true (The value in numFound is exact) or false (the value in numFound is an approximation).
This commit is contained in:
Tomas Fernandez Lobbe 2020-05-08 14:25:47 -07:00 committed by GitHub
parent 4a76a59d69
commit d9f9d6dd47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 704 additions and 196 deletions

View File

@ -122,6 +122,11 @@ Optimizations
* LUCENE-7788: fail precommit on unparameterised log messages and examine for wasted work/objects (Erick Erickson)
* SOLR-13289: When the "minExactHits" parameters is provided in queries and it's value is lower than the number of hits,
Solr can speedup the query resolution by using the Block-Max WAND algorithm (see LUCENE-8135). When doing this, the
value of matching documents in the response (numFound) will be an approximation.
(Ishan Chattopadhyaya, Munendra S N, Tomás Fernández Löbbe)
Bug Fixes
---------------------
* SOLR-13264: IndexSizeTrigger aboveOp / belowOp properties not in valid properties.

View File

@ -33,6 +33,7 @@ import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TotalHits;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
@ -358,7 +359,7 @@ public class CarrotClusteringEngine extends SearchClusteringEngine {
// See comment in ClusteringComponent#finishStage().
if (produceSummary && docIds != null) {
docsHolder[0] = docIds.get(sdoc).intValue();
DocList docAsList = new DocSlice(0, 1, docsHolder, scores, 1, 1.0f);
DocList docAsList = new DocSlice(0, 1, docsHolder, scores, 1, 1.0f, TotalHits.Relation.EQUAL_TO);
NamedList<Object> highlights = highlighter.doHighlighting(docAsList, theQuery, req, snippetFieldAry);
if (highlights != null && highlights.size() == 1) {
// should only be one value given our setup

View File

@ -30,6 +30,7 @@ import java.util.Set;
import java.util.function.Supplier;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.lucene.search.TotalHits.Relation;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
@ -238,6 +239,7 @@ public class EmbeddedSolrServer extends SolrClient {
// write an empty list...
SolrDocumentList docs = new SolrDocumentList();
docs.setNumFound(ctx.getDocList().matches());
docs.setNumFoundExact(ctx.getDocList().hitCountRelation() == Relation.EQUAL_TO);
docs.setStart(ctx.getDocList().offset());
docs.setMaxScore(ctx.getDocList().maxScore());
codec.writeSolrDocumentList(docs);

View File

@ -439,13 +439,13 @@ public class ExpandComponent extends SearchComponent implements PluginInfoInitia
scores[i] = scoreDoc.score;
}
assert topDocs.totalHits.relation == TotalHits.Relation.EQUAL_TO;
DocSlice slice = new DocSlice(0, docs.length, docs, scores, topDocs.totalHits.value, Float.NaN);
DocSlice slice = new DocSlice(0, docs.length, docs, scores, topDocs.totalHits.value, Float.NaN, TotalHits.Relation.EQUAL_TO);
addGroupSliceToOutputMap(fieldType, ordBytes, outMap, charsRef, groupValue, slice);
}
} else {
int totalHits = ((TotalHitCountCollector) cursor.value).getTotalHits();
if (totalHits > 0) {
DocSlice slice = new DocSlice(0, 0, null, null, totalHits, 0);
DocSlice slice = new DocSlice(0, 0, null, null, totalHits, 0, TotalHits.Relation.EQUAL_TO);
addGroupSliceToOutputMap(fieldType, ordBytes, outMap, charsRef, groupValue, slice);
}
}

View File

@ -44,6 +44,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorable;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.grouping.GroupDocs;
import org.apache.lucene.search.grouping.SearchGroup;
import org.apache.lucene.search.grouping.TopGroups;
@ -365,6 +366,7 @@ public class QueryComponent extends SearchComponent
QueryCommand cmd = rb.createQueryCommand();
cmd.setTimeAllowed(timeAllowed);
cmd.setMinExactHits(getMinExactHits(params));
req.getContext().put(SolrIndexSearcher.STATS_SOURCE, statsCache.get(req));
@ -401,6 +403,14 @@ public class QueryComponent extends SearchComponent
doProcessUngroupedSearch(rb, cmd, result);
}
private int getMinExactHits(SolrParams params) {
long minExactHits = params.getLong(CommonParams.MIN_EXACT_HITS, Integer.MAX_VALUE);
if (minExactHits < 0 || minExactHits > Integer.MAX_VALUE) {
minExactHits = Integer.MAX_VALUE;
}
return (int)minExactHits;
}
protected void doFieldSortValues(ResponseBuilder rb, SolrIndexSearcher searcher) throws IOException
{
SolrQueryRequest req = rb.req;
@ -840,6 +850,7 @@ public class QueryComponent extends SearchComponent
}
long numFound = 0;
boolean hitCountIsExact = true;
Float maxScore=null;
boolean thereArePartialResults = false;
Boolean segmentTerminatedEarly = null;
@ -871,6 +882,7 @@ public class QueryComponent extends SearchComponent
}
docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
nl.add("numFound", docs.getNumFound());
nl.add("numFoundExact", docs.getNumFoundExact());
nl.add("maxScore", docs.getMaxScore());
nl.add("shardAddress", srsp.getShardAddress());
}
@ -912,6 +924,10 @@ public class QueryComponent extends SearchComponent
maxScore = maxScore==null ? docs.getMaxScore() : Math.max(maxScore, docs.getMaxScore());
}
numFound += docs.getNumFound();
if (hitCountIsExact && Boolean.FALSE.equals(docs.getNumFoundExact())) {
hitCountIsExact = false;
}
NamedList sortFieldValues = (NamedList)(srsp.getSolrResponse().getResponse().get("sort_values"));
if (sortFieldValues.size()==0 && // we bypass merging this response only if it's partial itself
@ -983,6 +999,7 @@ public class QueryComponent extends SearchComponent
SolrDocumentList responseDocs = new SolrDocumentList();
if (maxScore!=null) responseDocs.setMaxScore(maxScore);
responseDocs.setNumFound(numFound);
responseDocs.setNumFoundExact(hitCountIsExact);
responseDocs.setStart(ss.getOffset());
// size appropriately
for (int i=0; i<resultSize; i++) responseDocs.add(null);
@ -1274,7 +1291,7 @@ public class QueryComponent extends SearchComponent
}
DocListAndSet res = new DocListAndSet();
res.docList = new DocSlice(0, docs, luceneIds, null, docs, 0);
res.docList = new DocSlice(0, docs, luceneIds, null, docs, 0, TotalHits.Relation.EQUAL_TO);
if (rb.isNeedDocSet()) {
// TODO: create a cache for this!
List<Query> queries = new ArrayList<>();

View File

@ -24,6 +24,7 @@ import java.util.Map;
import java.util.Set;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.grouping.SearchGroup;
import org.apache.lucene.search.grouping.TopGroups;
import org.apache.lucene.util.BytesRef;
@ -450,7 +451,7 @@ public class ResponseBuilder
rsp.getResponseHeader().asShallowMap()
.put(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY, Boolean.TRUE);
if(getResults() != null && getResults().docList==null) {
getResults().docList = new DocSlice(0, 0, new int[] {}, new float[] {}, 0, 0);
getResults().docList = new DocSlice(0, 0, new int[] {}, new float[] {}, 0, 0, TotalHits.Relation.EQUAL_TO);
}
}
final Boolean segmentTerminatedEarly = result.getSegmentTerminatedEarly();

View File

@ -26,6 +26,7 @@ import java.util.List;
import java.util.Set;
import org.apache.lucene.index.ExitableDirectoryReader;
import org.apache.lucene.search.TotalHits;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrDocumentList;
@ -484,6 +485,7 @@ public class SearchHandler extends RequestHandlerBase implements SolrCoreAware,
}
else {
nl.add("numFound", rb.getResults().docList.matches());
nl.add("numFoundExact", rb.getResults().docList.hitCountRelation() == TotalHits.Relation.EQUAL_TO);
nl.add("maxScore", rb.getResults().docList.maxScore());
}
nl.add("shardAddress", rb.shortCircuitedURL);

View File

@ -47,6 +47,7 @@ import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.util.BitSetIterator;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.FixedBitSet;
@ -284,7 +285,7 @@ public class TaggerRequestHandler extends RequestHandlerBase {
for (int i = 0; i < docIds.length; i++) {
docIds[i] = docIdIter.nextDoc();
}
return new DocSlice(0, docIds.length, docIds, null, matchDocs, 1f);
return new DocSlice(0, docIds.length, docIds, null, matchDocs, 1f, TotalHits.Relation.EQUAL_TO);
}
private TagClusterReducer chooseTagClusterReducer(String overlaps) {

View File

@ -31,6 +31,7 @@ import java.util.function.Consumer;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.TotalHits;
import org.apache.solr.client.solrj.impl.BinaryResponseParser;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.params.CommonParams;
@ -164,7 +165,7 @@ public class BinaryResponseWriter implements BinaryQueryResponseWriter {
public void writeResults(ResultContext ctx, JavaBinCodec codec) throws IOException {
codec.writeTag(JavaBinCodec.SOLRDOCLST);
List l = new ArrayList(3);
List<Object> l = new ArrayList<>(4);
l.add((long) ctx.getDocList().matches());
l.add((long) ctx.getDocList().offset());
@ -173,6 +174,7 @@ public class BinaryResponseWriter implements BinaryQueryResponseWriter {
maxScore = ctx.getDocList().maxScore();
}
l.add(maxScore);
l.add(ctx.getDocList().hitCountRelation() == TotalHits.Relation.EQUAL_TO);
codec.writeArray(l);
// this is a seprate function so that streaming responses can use just that part

View File

@ -292,11 +292,18 @@ class GeoJSONWriter extends JSONWriter {
}
}
@Deprecated
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore) throws IOException
long start, int size, long numFound, Float maxScore) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore, Boolean numFoundExact) throws IOException
{
writeMapOpener((maxScore==null) ? 3 : 4);
writeMapOpener(headerSize(maxScore, numFoundExact));
incLevel();
writeKey("type",false);
writeStr(null, "FeatureCollection", false);
@ -312,6 +319,13 @@ class GeoJSONWriter extends JSONWriter {
writeKey("maxScore",false);
writeFloat(null,maxScore);
}
if (numFoundExact != null) {
writeMapSeparator();
writeKey("numFoundExact",false);
writeBool(null, numFoundExact);
}
writeMapSeparator();
// if can we get bbox of all results, we should write it here

View File

@ -215,11 +215,19 @@ class ArrayOfNameTypeValueJSONWriter extends JSONWriter {
super.writeSolrDocument(name, doc, returnFields, idx);
}
@Deprecated
@Override
public void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore) throws IOException {
ifNeededWriteTypeAndValueKey("doclist");
super.writeStartDocumentList(name, start, size, numFound, maxScore);
}
@Override
public void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore, Boolean numFoundExact) throws IOException {
ifNeededWriteTypeAndValueKey("doclist");
super.writeStartDocumentList(name, start, size, numFound, maxScore, numFoundExact);
}
@Override
public void writeMap(String name, Map val, boolean excludeOuter, boolean isFirstVal) throws IOException {

View File

@ -132,11 +132,16 @@ public class JSONWriter extends TextResponseWriter implements JsonTextWriter {
// that the size could not be reliably determined.
//
/**
* This method will be removed in Solr 9
* @deprecated Use {{@link #writeStartDocumentList(String, long, int, long, Float, Boolean)}.
*/
@Override
@Deprecated
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore) throws IOException
{
writeMapOpener((maxScore==null) ? 3 : 4);
writeMapOpener(headerSize(maxScore, null));
incLevel();
writeKey("numFound",false);
writeLong(null,numFound);
@ -156,6 +161,42 @@ public class JSONWriter extends TextResponseWriter implements JsonTextWriter {
incLevel();
}
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore, Boolean numFoundExact) throws IOException {
writeMapOpener(headerSize(maxScore, numFoundExact));
incLevel();
writeKey("numFound",false);
writeLong(null,numFound);
writeMapSeparator();
writeKey("start",false);
writeLong(null,start);
if (maxScore != null) {
writeMapSeparator();
writeKey("maxScore",false);
writeFloat(null,maxScore);
}
if (numFoundExact != null) {
writeMapSeparator();
writeKey("numFoundExact",false);
writeBool(null, numFoundExact);
}
writeMapSeparator();
writeKey("docs",false);
writeArrayOpener(size);
incLevel();
}
protected int headerSize(Float maxScore, Boolean numFoundExact) {
int headerSize = 3;
if (maxScore != null) headerSize++;
if (numFoundExact != null) headerSize++;
return headerSize;
}
@Override
public void writeEndDocumentList() throws IOException

View File

@ -89,13 +89,19 @@ class PHPSerializedWriter extends JSONWriter {
writeNamedListAsMapMangled(name,val);
}
@Deprecated
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore) throws IOException
{
writeMapOpener((maxScore==null) ? 3 : 4);
throw new UnsupportedOperationException();
}
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore, Boolean numFoundExact) throws IOException
{
writeMapOpener(headerSize(maxScore, numFoundExact));
writeKey("numFound",false);
writeLong(null,numFound);
writeKey("start",false);
@ -105,6 +111,10 @@ class PHPSerializedWriter extends JSONWriter {
writeKey("maxScore",false);
writeFloat(null,maxScore);
}
if (numFoundExact != null) {
writeKey("numFoundExact",false);
writeBool(null, numFoundExact);
}
writeKey("docs",false);
writeArrayOpener(size);
}

View File

@ -466,6 +466,11 @@ public class SchemaXmlWriter extends TextResponseWriter {
public void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore) throws IOException {
// no-op
}
@Override
public void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore, Boolean numFoundExact) throws IOException {
// no-op
}
@Override
public void writeSolrDocument(String name, SolrDocument doc, ReturnFields returnFields, int idx) throws IOException {

View File

@ -127,6 +127,13 @@ public abstract class TabularResponseWriter extends TextResponseWriter {
{
// nothing
}
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore, Boolean numFoundExact) throws IOException
{
// nothing
}
@Override
public void writeEndDocumentList() throws IOException

View File

@ -23,6 +23,7 @@ import java.util.Iterator;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
@ -156,7 +157,16 @@ public abstract class TextResponseWriter implements TextWriter {
// types of formats, including those where the name may come after the value (like
// some XML formats).
public abstract void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore) throws IOException;
//TODO: Make abstract in Solr 9.0
public void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore, Boolean numFoundExact) throws IOException {
writeStartDocumentList(name, start, size, numFound, maxScore);
}
/**
* @deprecated Use {@link #writeStartDocumentList(String, long, int, long, Float, Boolean)}
*/
@Deprecated
public abstract void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore) throws IOException;
public abstract void writeSolrDocument(String name, SolrDocument doc, ReturnFields fields, int idx) throws IOException;
@ -165,7 +175,7 @@ public abstract class TextResponseWriter implements TextWriter {
// Assume each SolrDocument is already transformed
public final void writeSolrDocumentList(String name, SolrDocumentList docs, ReturnFields fields) throws IOException
{
writeStartDocumentList(name, docs.getStart(), docs.size(), docs.getNumFound(), docs.getMaxScore() );
writeStartDocumentList(name, docs.getStart(), docs.size(), docs.getNumFound(), docs.getMaxScore(), docs.getNumFoundExact());
for( int i=0; i<docs.size(); i++ ) {
writeSolrDocument( null, docs.get(i), fields, i );
}
@ -177,7 +187,7 @@ public abstract class TextResponseWriter implements TextWriter {
DocList ids = res.getDocList();
Iterator<SolrDocument> docsStreamer = res.getProcessedDocuments();
writeStartDocumentList(name, ids.offset(), ids.size(), ids.matches(),
res.wantsScores() ? ids.maxScore() : null);
res.wantsScores() ? ids.maxScore() : null, ids.hitCountRelation() == TotalHits.Relation.EQUAL_TO);
int idx = 0;
while (docsStreamer.hasNext()) {

View File

@ -166,6 +166,28 @@ public class XMLWriter extends TextResponseWriter {
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore, Boolean numFoundExact) throws IOException
{
if (doIndent) indent();
writer.write("<result");
writeAttr(NAME, name);
writeAttr("numFound",Long.toString(numFound));
writeAttr("start",Long.toString(start));
if (maxScore != null) {
writeAttr("maxScore",Float.toString(maxScore));
}
if (numFoundExact != null) {
writeAttr("numFoundExact", numFoundExact.toString());
}
writer.write(">");
incLevel();
}
@Override
@Deprecated
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore) throws IOException
{

View File

@ -24,6 +24,7 @@ import java.util.concurrent.Callable;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TotalHits;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
import org.apache.solr.client.solrj.request.QueryRequest;
@ -177,7 +178,8 @@ class SubQueryAugmenter extends DocTransformer {
return new DocSlice((int)docList.getStart(),
docList.size(), new int[0], new float[docList.size()],
(int) docList.getNumFound(),
docList.getMaxScore() == null ? Float.NaN : docList.getMaxScore());
docList.getMaxScore() == null ? Float.NaN : docList.getMaxScore(),
docList.getNumFoundExact() ? TotalHits.Relation.EQUAL_TO : TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO);
}
@Override

View File

@ -16,6 +16,7 @@
*/
package org.apache.solr.search;
import org.apache.lucene.search.TotalHits;
/**
* <code>DocList</code> represents the result of a query: an ordered list of document ids with optional score.
@ -45,6 +46,8 @@ public interface DocList {
* @return number of matches for the search(query &amp; any filters)
*/
public long matches();
public TotalHits.Relation hitCountRelation();
/***

View File

@ -19,6 +19,7 @@ package org.apache.solr.search;
import java.util.Collection;
import java.util.Collections;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.RamUsageEstimator;
@ -37,6 +38,7 @@ public class DocSlice implements DocList, Accountable {
final float[] scores; // optional score list
final long matches;
final TotalHits.Relation matchesRelation;
final float maxScore;
final long ramBytesUsed; // cached value
@ -48,8 +50,9 @@ public class DocSlice implements DocList, Accountable {
* @param docs array of docids starting at position 0
* @param scores array of scores that corresponds to docs, may be null
* @param matches total number of matches for the query
* @param matchesRelation Indicates if {@code matches} is exact or an approximation
*/
public DocSlice(int offset, int len, int[] docs, float[] scores, long matches, float maxScore) {
public DocSlice(int offset, int len, int[] docs, float[] scores, long matches, float maxScore, TotalHits.Relation matchesRelation) {
this.offset=offset;
this.len=len;
this.docs=docs;
@ -57,6 +60,7 @@ public class DocSlice implements DocList, Accountable {
this.matches=matches;
this.maxScore=maxScore;
this.ramBytesUsed = BASE_RAM_BYTES_USED + (docs == null ? 0 : ((long)docs.length << 2)) + (scores == null ? 0 : ((long)scores.length<<2)+RamUsageEstimator.NUM_BYTES_ARRAY_HEADER);
this.matchesRelation = matchesRelation;
}
@Override
@ -70,7 +74,7 @@ public class DocSlice implements DocList, Accountable {
int realEndDoc = Math.min(requestedEnd, docs.length);
int realLen = Math.max(realEndDoc-offset,0);
if (this.offset == offset && this.len == realLen) return this;
return new DocSlice(offset, realLen, docs, scores, matches, maxScore);
return new DocSlice(offset, realLen, docs, scores, matches, maxScore, matchesRelation);
}
@Override
@ -139,4 +143,9 @@ public class DocSlice implements DocList, Accountable {
public Collection<Accountable> getChildResources() {
return Collections.emptyList();
}
@Override
public TotalHits.Relation hitCountRelation() {
return matchesRelation;
}
}

View File

@ -414,7 +414,7 @@ public class Grouping {
for (int val : idSet) {
ids[idx++] = val;
}
qr.setDocList(new DocSlice(0, sz, ids, null, maxMatches, maxScore));
qr.setDocList(new DocSlice(0, sz, ids, null, maxMatches, maxScore, TotalHits.Relation.EQUAL_TO));
}
}
@ -630,7 +630,7 @@ public class Grouping {
float score = groups.maxScore;
maxScore = maxAvoidNaN(score, maxScore);
DocSlice docs = new DocSlice(off, Math.max(0, ids.length - off), ids, scores, groups.totalHits.value, score);
DocSlice docs = new DocSlice(off, Math.max(0, ids.length - off), ids, scores, groups.totalHits.value, score, TotalHits.Relation.EQUAL_TO);
if (getDocList) {
DocIterator iter = docs.iterator();
@ -672,7 +672,7 @@ public class Grouping {
int len = docsGathered > offset ? docsGathered - offset : 0;
int[] docs = ArrayUtils.toPrimitive(ids.toArray(new Integer[ids.size()]));
float[] docScores = ArrayUtils.toPrimitive(scores.toArray(new Float[scores.size()]));
DocSlice docSlice = new DocSlice(offset, len, docs, docScores, getMatches(), maxScore);
DocSlice docSlice = new DocSlice(offset, len, docs, docScores, getMatches(), maxScore, TotalHits.Relation.EQUAL_TO);
if (getDocList) {
for (int i = offset; i < docs.length; i++) {

View File

@ -37,9 +37,7 @@ public class MaxScoreCollector extends SimpleCollector {
@Override
public ScoreMode scoreMode() {
// Should be TOP_SCORES but this would wrap the scorer unnecessarily since
// this collector is only used in a MultiCollector.
return ScoreMode.COMPLETE;
return ScoreMode.TOP_SCORES;
}
@Override

View File

@ -37,6 +37,7 @@ public class QueryCommand {
private int supersetMaxDoc;
private int flags;
private long timeAllowed = -1;
private int minExactHits = Integer.MAX_VALUE;
private CursorMark cursorMark;
public CursorMark getCursorMark() {
@ -182,6 +183,15 @@ public class QueryCommand {
this.timeAllowed = timeAllowed;
return this;
}
public int getMinExactHits() {
return minExactHits;
}
public QueryCommand setMinExactHits(int hits) {
this.minExactHits = hits;
return this;
}
public boolean isNeedDocSet() {
return (flags & SolrIndexSearcher.GET_DOCSET) != 0;

View File

@ -16,15 +16,15 @@
*/
package org.apache.solr.search;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.RamUsageEstimator;
import java.util.List;
import java.util.ArrayList;
/** A hash key encapsulating a query, a list of filters, and a sort
*
*/
@ -37,18 +37,23 @@ public final class QueryResultKey implements Accountable {
final SortField[] sfields;
final List<Query> filters;
final int nc_flags; // non-comparable flags... ignored by hashCode and equals
final int minExactHits;
private final int hc; // cached hashCode
private final long ramBytesUsed; // cached
private static SortField[] defaultSort = new SortField[0];
public QueryResultKey(Query query, List<Query> filters, Sort sort, int nc_flags) {
this(query, filters, sort, nc_flags, Integer.MAX_VALUE);
}
public QueryResultKey(Query query, List<Query> filters, Sort sort, int nc_flags, int minExactHits) {
this.query = query;
this.sort = sort;
this.filters = filters;
this.nc_flags = nc_flags;
this.minExactHits = minExactHits;
int h = query.hashCode();
@ -65,6 +70,7 @@ public final class QueryResultKey implements Accountable {
h = h*29 + sf.hashCode();
ramSfields += BASE_SF_RAM_BYTES_USED + RamUsageEstimator.sizeOfObject(sf.getField());
}
h = h*31 + minExactHits;
hc = h;
@ -96,6 +102,7 @@ public final class QueryResultKey implements Accountable {
if (this.sfields.length != other.sfields.length) return false;
if (!this.query.equals(other.query)) return false;
if (!unorderedCompare(this.filters, other.filters)) return false;
if (this.minExactHits != other.minExactHits) return false;
for (int i=0; i<sfields.length; i++) {
SortField sf1 = this.sfields[i];

View File

@ -50,6 +50,7 @@ import org.apache.lucene.index.Term;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.search.*;
import org.apache.lucene.search.TotalHits.Relation;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
@ -1305,7 +1306,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
&& (flags & (NO_CHECK_QCACHE | NO_SET_QCACHE)) != ((NO_CHECK_QCACHE | NO_SET_QCACHE))) {
// all of the current flags can be reused during warming,
// so set all of them on the cache key.
key = new QueryResultKey(q, cmd.getFilterList(), cmd.getSort(), flags);
key = new QueryResultKey(q, cmd.getFilterList(), cmd.getSort(), flags, cmd.getMinExactHits());
if ((flags & NO_CHECK_QCACHE) == 0) {
superset = queryResultCache.get(key);
@ -1482,7 +1483,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
* The Command whose properties should determine the type of TopDocsCollector to use.
*/
private TopDocsCollector buildTopDocsCollector(int len, QueryCommand cmd) throws IOException {
int minNumFound = cmd.getMinExactHits();
Query q = cmd.getQuery();
if (q instanceof RankQuery) {
RankQuery rq = (RankQuery) q;
@ -1491,14 +1492,14 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
if (null == cmd.getSort()) {
assert null == cmd.getCursorMark() : "have cursor but no sort";
return TopScoreDocCollector.create(len, Integer.MAX_VALUE);
return TopScoreDocCollector.create(len, minNumFound);
} else {
// we have a sort
final Sort weightedSort = weightSort(cmd.getSort());
final CursorMark cursor = cmd.getCursorMark();
final FieldDoc searchAfter = (null != cursor ? cursor.getSearchAfterFieldDoc() : null);
return TopFieldCollector.create(weightedSort, len, searchAfter, Integer.MAX_VALUE);
return TopFieldCollector.create(weightedSort, len, searchAfter, minNumFound);
}
}
@ -1517,6 +1518,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
ProcessedFilter pf = getProcessedFilter(cmd.getFilter(), cmd.getFilterList());
final Query query = QueryUtils.combineQueryAndFilter(QueryUtils.makeQueryable(cmd.getQuery()), pf.filter);
Relation hitsRelation;
// handle zero case...
if (lastDocRequested <= 0) {
@ -1569,8 +1571,9 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
maxScore = totalHits > 0 ? topscore[0] : 0.0f;
// no docs on this page, so cursor doesn't change
qr.setNextCursorMark(cmd.getCursorMark());
hitsRelation = Relation.EQUAL_TO;
} else {
final TopDocsCollector topCollector = buildTopDocsCollector(len, cmd);
final TopDocsCollector<?> topCollector = buildTopDocsCollector(len, cmd);
MaxScoreCollector maxScoreCollector = null;
Collector collector = topCollector;
if ((cmd.getFlags() & GET_SCORES) != 0) {
@ -1581,6 +1584,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
totalHits = topCollector.getTotalHits();
TopDocs topDocs = topCollector.topDocs(0, len);
hitsRelation = topDocs.totalHits.relation;
if (cmd.getSort() != null && query instanceof RankQuery == false && (cmd.getFlags() & GET_SCORES) != 0) {
TopFieldCollector.populateScores(topDocs.scoreDocs, this, query);
}
@ -1599,7 +1603,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
int sliceLen = Math.min(lastDocRequested, nDocsReturned);
if (sliceLen < 0) sliceLen = 0;
qr.setDocList(new DocSlice(0, sliceLen, ids, scores, totalHits, maxScore));
qr.setDocList(new DocSlice(0, sliceLen, ids, scores, totalHits, maxScore, hitsRelation));
}
// any DocSet returned is for the query only, without any filtering... that way it may
@ -1618,6 +1622,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
boolean needScores = (cmd.getFlags() & GET_SCORES) != 0;
int maxDoc = maxDoc();
cmd.setMinExactHits(Integer.MAX_VALUE);// We need the full DocSet
ProcessedFilter pf = getProcessedFilter(cmd.getFilter(), cmd.getFilterList());
final Query query = QueryUtils.combineQueryAndFilter(QueryUtils.makeQueryable(cmd.getQuery()), pf.filter);
@ -1668,7 +1673,6 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
// no docs on this page, so cursor doesn't change
qr.setNextCursorMark(cmd.getCursorMark());
} else {
final TopDocsCollector topCollector = buildTopDocsCollector(len, cmd);
DocSetCollector setCollector = new DocSetCollector(maxDoc);
MaxScoreCollector maxScoreCollector = null;
@ -1708,7 +1712,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
int sliceLen = Math.min(lastDocRequested, nDocsReturned);
if (sliceLen < 0) sliceLen = 0;
qr.setDocList(new DocSlice(0, sliceLen, ids, scores, totalHits, maxScore));
qr.setDocList(new DocSlice(0, sliceLen, ids, scores, totalHits, maxScore, TotalHits.Relation.EQUAL_TO));
// TODO: if we collect results before the filter, we just need to intersect with
// that filter to generate the DocSet for qr.setDocSet()
qr.setDocSet(set);
@ -1981,7 +1985,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
int nDocs = cmd.getSupersetMaxDoc();
if (nDocs == 0) {
// SOLR-2923
qr.getDocListAndSet().docList = new DocSlice(0, 0, new int[0], null, set.size(), 0f);
qr.getDocListAndSet().docList = new DocSlice(0, 0, new int[0], null, set.size(), 0f, TotalHits.Relation.EQUAL_TO);
qr.setNextCursorMark(cmd.getCursorMark());
return;
}
@ -2020,7 +2024,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
}
assert topDocs.totalHits.relation == TotalHits.Relation.EQUAL_TO;
qr.getDocListAndSet().docList = new DocSlice(0, nDocsReturned, ids, null, topDocs.totalHits.value, 0.0f);
qr.getDocListAndSet().docList = new DocSlice(0, nDocsReturned, ids, null, topDocs.totalHits.value, 0.0f, topDocs.totalHits.relation);
populateNextCursorMarkFromTopDocs(qr, cmd, topDocs);
}

View File

@ -1017,7 +1017,7 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 {
e.getMessage().contains(f));
}
}
// /** this doesn't work, but if it did, this is how we'd test it. */
// public void testOverwriteFalse() {

View File

@ -87,14 +87,14 @@ public class TestCrossCoreJoin extends SolrTestCaseJ4 {
void doTestJoin(String joinPrefix) throws Exception {
assertJQ(req("q", joinPrefix + " from=dept_id_s to=dept_s fromIndex=fromCore}cat:dev", "fl", "id",
"debugQuery", random().nextBoolean() ? "true":"false")
, "/response=={'numFound':3,'start':0,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
, "/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
);
// find people that develop stuff - but limit via filter query to a name of "john"
// this tests filters being pushed down to queries (SOLR-3062)
assertJQ(req("q", joinPrefix + " from=dept_id_s to=dept_s fromIndex=fromCore}cat:dev", "fl", "id", "fq", "name:john",
"debugQuery", random().nextBoolean() ? "true":"false")
, "/response=={'numFound':1,'start':0,'docs':[{'id':'1'}]}"
, "/response=={'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'1'}]}"
);
}

View File

@ -209,7 +209,10 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
query("q","*:*", "sort",i1+" desc", "fl","*,score");
query("q","*:*", "sort","n_tl1 asc", "fl","*,score");
query("q","*:*", "sort","n_tl1 desc");
handle.put("maxScore", SKIPVAL);
testMinExactHits();
query("q","{!func}"+i1);// does not expect maxScore. So if it comes ,ignore it. JavaBinCodec.writeSolrDocumentList()
//is agnostic of request params.
handle.remove("maxScore");
@ -1082,11 +1085,38 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
assertEquals(new EnumFieldValue(11, "Critical"),
rsp.getFieldStatsInfo().get(fieldName).getMax());
handle.put("severity", UNORDERED); // this is stupid, but stats.facet doesn't garuntee order
handle.put("severity", UNORDERED); // this is stupid, but stats.facet doesn't guarantee order
query("q", "*:*", "stats", "true", "stats.field", fieldName,
"stats.facet", fieldName);
}
private void testMinExactHits() throws Exception {
assertIsExactHitCount("q","{!cache=false}dog OR men OR cow OR country OR dumpty", CommonParams.MIN_EXACT_HITS, "200", CommonParams.ROWS, "2", CommonParams.SORT, "score desc, id asc");
assertIsExactHitCount("q","{!cache=false}dog OR men OR cow OR country OR dumpty", CommonParams.MIN_EXACT_HITS, "-1", CommonParams.ROWS, "2", CommonParams.SORT, "score desc, id asc");
assertIsExactHitCount("q","{!cache=false}dog OR men OR cow OR country OR dumpty", CommonParams.MIN_EXACT_HITS, "1", CommonParams.ROWS, "200", CommonParams.SORT, "score desc, id asc");
assertIsExactHitCount("q","{!cache=false}dog OR men OR cow OR country OR dumpty", "facet", "true", "facet.field", s1, CommonParams.MIN_EXACT_HITS,"1", CommonParams.ROWS, "200", CommonParams.SORT, "score desc, id asc");
assertIsExactHitCount("q","{!cache=false}id:1", CommonParams.MIN_EXACT_HITS,"1", CommonParams.ROWS, "1");
assertApproximatedHitCount("q","{!cache=false}dog OR men OR cow OR country OR dumpty", CommonParams.MIN_EXACT_HITS,"2", CommonParams.ROWS, "2", CommonParams.SORT, "score desc, id asc");
}
private void assertIsExactHitCount(Object... requestParams) throws Exception {
QueryResponse response = query(requestParams);
assertNotNull("Expecting exact hit count in response: " + response.getResults().toString(),
response.getResults().getNumFoundExact());
assertTrue("Expecting exact hit count in response: " + response.getResults().toString(),
response.getResults().getNumFoundExact());
}
private void assertApproximatedHitCount(Object...requestParams) throws Exception {
handle.put("numFound", SKIPVAL);
QueryResponse response = query(requestParams);
assertNotNull("Expecting numFoundExact in response: " + response.getResults().toString(),
response.getResults().getNumFoundExact());
assertFalse("Expecting aproximated results in response: " + response.getResults().toString(),
response.getResults().getNumFoundExact());
handle.remove("numFound", SKIPVAL);
}
/** comparing results with facet.method=uif */
private void queryAndCompareUIF(Object ... params) throws Exception {
final QueryResponse expect = query(params);

View File

@ -16,20 +16,6 @@
*/
package org.apache.solr;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.solr.client.solrj.impl.BinaryResponseParser;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
@ -50,6 +36,20 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class TestGroupingSearch extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@ -245,11 +245,11 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
assertJQ(
req("q", "*:*", "start", "1", "group", "true", "group.field", "id", "group.main", "true"),
"/response=={'numFound':3,'start':1,'docs':[{'id':'2'},{'id':'3'}]}"
"/response=={'numFound':3,'start':1,'numFoundExact':true,'docs':[{'id':'2'},{'id':'3'}]}"
);
assertJQ(
req("q", "*:*", "start", "1", "rows", "1", "group", "true", "group.field", "id", "group.main", "true"),
"/response=={'numFound':3,'start':1,'docs':[{'id':'2'}]}"
"/response=={'numFound':3,'start':1,'numFoundExact':true,'docs':[{'id':'2'}]}"
);
}
@ -264,7 +264,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
assertJQ(
req("q", "*:*", "start", "2", "rows", "1", "group", "true", "group.field", "id", "group.main", "true"),
"/response=={'numFound':5,'start':2,'docs':[{'id':'3'}]}"
"/response=={'numFound':5,'start':2,'numFoundExact':true,'docs':[{'id':'3'}]}"
);
}
@ -323,12 +323,12 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
assertJQ(
req("q", "*:*", "sort", "sum(value1_i, value2_i) desc", "rows", "1", "group", "true", "group.field", "id", "fl", "id"),
"/grouped=={'id':{'matches':5,'groups':[{'groupValue':'5','doclist':{'numFound':1,'start':0,'docs':[{'id':'5'}]}}]}}"
"/grouped=={'id':{'matches':5,'groups':[{'groupValue':'5','doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'5'}]}}]}}"
);
assertJQ(
req("q", "*:*", "sort", "geodist(45.18014,-93.87742,store) asc", "rows", "1", "group", "true", "group.field", "id", "fl", "id"),
"/grouped=={'id':{'matches':5,'groups':[{'groupValue':'1','doclist':{'numFound':1,'start':0,'docs':[{'id':'1'}]}}]}}"
"/grouped=={'id':{'matches':5,'groups':[{'groupValue':'1','doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'1'}]}}]}}"
);
}
@ -347,7 +347,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"value1_s1", "fl", "id", "facet", "true", "facet.field", "value3_s1", "group.truncate", "false");
assertJQ(
req,
"/grouped=={'value1_s1':{'matches':5,'groups':[{'groupValue':'1','doclist':{'numFound':3,'start':0,'docs':[{'id':'1'}]}}]}}",
"/grouped=={'value1_s1':{'matches':5,'groups':[{'groupValue':'1','doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'1'}]}}]}}",
"/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',3,'b',2]}," + EMPTY_FACETS + "}"
);
@ -356,7 +356,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"value1_s1", "fl", "id", "facet", "true", "facet.field", "value3_s1", "group.truncate", "true");
assertJQ(
req,
"/grouped=={'value1_s1':{'matches':5,'groups':[{'groupValue':'1','doclist':{'numFound':3,'start':0,'docs':[{'id':'1'}]}}]}}",
"/grouped=={'value1_s1':{'matches':5,'groups':[{'groupValue':'1','doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'1'}]}}]}}",
"/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]}," + EMPTY_FACETS + "}"
);
@ -365,7 +365,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"strdist(1,value1_s1,edit)", "fl", "id", "facet", "true", "facet.field", "value3_s1", "group.truncate", "true");
assertJQ(
req,
"/grouped=={'strdist(1,value1_s1,edit)':{'matches':5,'groups':[{'groupValue':1.0,'doclist':{'numFound':3,'start':0,'docs':[{'id':'1'}]}}]}}",
"/grouped=={'strdist(1,value1_s1,edit)':{'matches':5,'groups':[{'groupValue':1.0,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'1'}]}}]}}",
"/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]}," + EMPTY_FACETS + "}"
);
@ -374,7 +374,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"facet.field", "value3_s1", "group.truncate", "true");
assertJQ(
req,
"/grouped=={'value4_i':{'matches':5,'groups':[{'groupValue':1,'doclist':{'numFound':3,'start':0,'docs':[{'id':'1'}]}}]}}",
"/grouped=={'value4_i':{'matches':5,'groups':[{'groupValue':1,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'1'}]}}]}}",
"/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]}," + EMPTY_FACETS + "}"
);
@ -383,7 +383,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"facet.field", "{!ex=v}value3_s1", "group.truncate", "true", "fq", "{!tag=v}value3_s1:b");
assertJQ(
req,
"/grouped=={'value4_i':{'matches':2,'groups':[{'groupValue':2,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}]}}",
"/grouped=={'value4_i':{'matches':2,'groups':[{'groupValue':2,'doclist':{'numFound':2,'start':0,numFoundExact:true,'docs':[{'id':'3'}]}}]}}",
"/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]}," + EMPTY_FACETS + "}"
);
@ -392,7 +392,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"facet.field", "{!ex=v}value3_s1", "group.truncate", "false", "fq", "{!tag=v}value3_s1:b");
assertJQ(
req,
"/grouped=={'value4_i':{'matches':2,'groups':[{'groupValue':2,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}]}}",
"/grouped=={'value4_i':{'matches':2,'groups':[{'groupValue':2,'doclist':{'numFound':2,'start':0,numFoundExact:true,'docs':[{'id':'3'}]}}]}}",
"/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',3,'b',2]}," + EMPTY_FACETS + "}"
);
@ -401,7 +401,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"facet.field", "{!ex=v}value3_s1", "group.truncate", "true", "fq", "{!tag=v}value3_s1:b");
assertJQ(
req,
"/grouped=={'sub(value4_i,1)':{'matches':2,'groups':[{'groupValue':1.0,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}]}}",
"/grouped=={'sub(value4_i,1)':{'matches':2,'groups':[{'groupValue':1.0,'doclist':{'numFound':2,'start':0,numFoundExact:true,'docs':[{'id':'3'}]}}]}}",
"/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]}," + EMPTY_FACETS + "}"
);
}
@ -424,7 +424,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"facet.query", "{!ex=chk key=LM3}bday:[2012-10-18T00:00:00Z TO 2013-01-17T23:59:59Z]");
assertJQ(
req,
"/grouped=={'cat_sI':{'matches':2,'groups':[{'groupValue':'a','doclist':{'numFound':1,'start':0,'docs':[{'id':'5'}]}}]}}",
"/grouped=={'cat_sI':{'matches':2,'groups':[{'groupValue':'a','doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'5'}]}}]}}",
"/facet_counts=={'facet_queries':{'LW1':2,'LM1':2,'LM3':2},'facet_fields':{}," + EMPTY_FACETS + "}"
);
}
@ -463,33 +463,33 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
,"/responseHeader=={'_SKIP_':'QTime', 'status':0}" // partial match by skipping some elements
,"/responseHeader=={'_MATCH_':'status', 'status':0}" // partial match by only including some elements
,"/grouped=={'"+f+"':{'matches':10,'groups':[\n" +
"{'groupValue':1,'doclist':{'numFound':3,'start':0,'docs':[{'id':'8'}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}," +
"{'groupValue':2,'doclist':{'numFound':3,'start':0,'docs':[{'id':'4'}]}}," +
"{'groupValue':5,'doclist':{'numFound':1,'start':0,'docs':[{'id':'1'}]}}," +
"{'groupValue':4,'doclist':{'numFound':1,'start':0,'docs':[{'id':'2'}]}}" +
"{'groupValue':1,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'8'}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,numFoundExact:true,'docs':[{'id':'3'}]}}," +
"{'groupValue':2,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'4'}]}}," +
"{'groupValue':5,'doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'1'}]}}," +
"{'groupValue':4,'doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'2'}]}}" +
"]}}"
);
// test that filtering cuts down the result set
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "fq",f+":2")
,"/grouped=={'"+f+"':{'matches':3,'groups':[" +
"{'groupValue':2,'doclist':{'numFound':3,'start':0,'docs':[{'id':'4'}]}}" +
"{'groupValue':2,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'4'}]}}" +
"]}}"
);
// test limiting the number of groups returned
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","2")
,"/grouped=={'"+f+"':{'matches':10,'groups':[" +
"{'groupValue':1,'doclist':{'numFound':3,'start':0,'docs':[{'id':'8'}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}" +
"{'groupValue':1,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'8'}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,numFoundExact:true,'docs':[{'id':'3'}]}}" +
"]}}"
);
// test offset into group list
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","1", "start","1")
,"/grouped=={'"+f+"':{'matches':10,'groups':[" +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}" +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,numFoundExact:true,'docs':[{'id':'3'}]}}" +
"]}}"
);
@ -502,24 +502,24 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
// test increasing the docs per group returned
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","2", "group.limit","3")
,"/grouped=={'"+f+"':{'matches':10,'groups':[" +
"{'groupValue':1,'doclist':{'numFound':3,'start':0,'docs':[{'id':'8'},{'id':'10'},{'id':'5'}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'},{'id':'6'}]}}" +
"{'groupValue':1,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'8'},{'id':'10'},{'id':'5'}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,numFoundExact:true,'docs':[{'id':'3'},{'id':'6'}]}}" +
"]}}"
);
// test offset into each group
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","2", "group.limit","3", "group.offset","1")
,"/grouped=={'"+f+"':{'matches':10,'groups':[" +
"{'groupValue':1,'doclist':{'numFound':3,'start':1,'docs':[{'id':'10'},{'id':'5'}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':1,'docs':[{'id':'6'}]}}" +
"{'groupValue':1,'doclist':{'numFound':3,'start':1,'numFoundExact':true,'docs':[{'id':'10'},{'id':'5'}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':1,'numFoundExact':true,'docs':[{'id':'6'}]}}" +
"]}}"
);
// test big offset into each group
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","2", "group.limit","3", "group.offset","10")
,"/grouped=={'"+f+"':{'matches':10,'groups':[" +
"{'groupValue':1,'doclist':{'numFound':3,'start':10,'docs':[]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':10,'docs':[]}}" +
"{'groupValue':1,'doclist':{'numFound':3,'start':10,'numFoundExact':true,'docs':[]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':10,'numFoundExact':true,'docs':[]}}" +
"]}}"
);
@ -527,8 +527,8 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id,score", "rows","2", "group.limit","2", "indent","off")
,"/grouped/"+f+"/groups==" +
"[" +
"{'groupValue':1,'doclist':{'numFound':3,'start':0,'maxScore':10.0,'docs':[{'id':'8','score':10.0},{'id':'10','score':3.0}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,'maxScore':7.0,'docs':[{'id':'3','score':7.0},{'id':'6','score':2.0}]}}" +
"{'groupValue':1,'doclist':{'numFound':3,'start':0,numFoundExact:true,'maxScore':10.0,'docs':[{'id':'8','score':10.0},{'id':'10','score':3.0}]}}," +
"{'groupValue':3,'doclist':{'numFound':2,'start':0,numFoundExact:true,'maxScore':7.0,'docs':[{'id':'3','score':7.0},{'id':'6','score':2.0}]}}" +
"]"
);
@ -537,8 +537,8 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
String func = "add("+f+","+f+")";
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.func", func , "fl","id", "rows","2")
,"/grouped=={'"+func+"':{'matches':10,'groups':[" +
"{'groupValue':2.0,'doclist':{'numFound':3,'start':0,'docs':[{'id':'8'}]}}," +
"{'groupValue':6.0,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}" +
"{'groupValue':2.0,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'8'}]}}," +
"{'groupValue':6.0,'doclist':{'numFound':2,'start':0,numFoundExact:true,'docs':[{'id':'3'}]}}" +
"]}}"
);
@ -560,7 +560,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
///////////////////////// group.query
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.query","id:[2 TO 5]", "fl","id", "group.limit","3")
,"/grouped=={'id:[2 TO 5]':{'matches':10," +
"'doclist':{'numFound':4,'start':0,'docs':[{'id':'3'},{'id':'4'},{'id':'2'}]}}}"
"'doclist':{'numFound':4,'start':0,numFoundExact:true,'docs':[{'id':'3'},{'id':'4'},{'id':'2'}]}}}"
);
// group.query that matches nothing
@ -571,50 +571,50 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"group.query","id:1000",
"fl","id",
"group.limit","3")
,"/grouped/id:[2 TO 5]=={'matches':10,'doclist':{'numFound':4,'start':0,'docs':[{'id':'3'},{'id':'4'},{'id':'2'}]}}"
,"/grouped/id:1000=={'matches':10,'doclist':{'numFound':0,'start':0,'docs':[]}}"
,"/grouped/id:[2 TO 5]=={'matches':10,'doclist':{'numFound':4,'start':0,numFoundExact:true,'docs':[{'id':'3'},{'id':'4'},{'id':'2'}]}}"
,"/grouped/id:1000=={'matches':10,'doclist':{'numFound':0,'start':0,numFoundExact:true,'docs':[]}}"
);
// group.query and sort
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.query",f+":1", "fl","id,score", "rows","2", "group.limit","2", "sort",f+" desc, score desc", "indent","off")
,"/grouped/"+f+":1==" +
"{'matches':10,'doclist':{'numFound':3,'start':0,'maxScore':10.0,'docs':[{'id':'8','score':10.0},{'id':'10','score':3.0}]}},"
"{'matches':10,'doclist':{'numFound':3,'start':0,numFoundExact:true,'maxScore':10.0,'docs':[{'id':'8','score':10.0},{'id':'10','score':3.0}]}},"
);
// group.query with fl=score and default sort
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.query",f+":1", "fl","id,score", "rows","2", "group.limit","2", "sort", "score desc", "indent","off")
,"/grouped/"+f+":1==" +
"{'matches':10,'doclist':{'numFound':3,'start':0,'maxScore':10.0,'docs':[{'id':'8','score':10.0},{'id':'10','score':3.0}]}},"
"{'matches':10,'doclist':{'numFound':3,'start':0,numFoundExact:true,'maxScore':10.0,'docs':[{'id':'8','score':10.0},{'id':'10','score':3.0}]}},"
);
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.query",f+":1", "fl","id", "rows","2", "group.limit","2", "indent","off")
,"/grouped/"+f+":1==" +
"{'matches':10,'doclist':{'numFound':3,'start':0,'docs':[{'id':'8'},{'id':'10'}]}},"
"{'matches':10,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'8'},{'id':'10'}]}},"
);
// group.query and offset
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.query","id:[2 TO 5]", "fl","id", "group.limit","3", "group.offset","2")
,"/grouped=={'id:[2 TO 5]':{'matches':10," +
"'doclist':{'numFound':4,'start':2,'docs':[{'id':'2'},{'id':'5'}]}}}"
"'doclist':{'numFound':4,'start':2,'numFoundExact':true,'docs':[{'id':'2'},{'id':'5'}]}}}"
);
// group.query and big offset
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.query","id:[2 TO 5]", "fl","id", "group.limit","3", "group.offset","10")
,"/grouped=={'id:[2 TO 5]':{'matches':10," +
"'doclist':{'numFound':4,'start':10,'docs':[]}}}"
"'doclist':{'numFound':4,'start':10,'numFoundExact':true,'docs':[]}}}"
);
///////////////////////// group.query as main result
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.query","id:[2 TO 5]", "fl","id", "rows","3", "group.main","true")
,"/response=={'numFound':4,'start':0,'docs':[{'id':'3'},{'id':'4'},{'id':'2'}]}"
,"/response=={'numFound':4,'start':0,numFoundExact:true,'docs':[{'id':'3'},{'id':'4'},{'id':'2'}]}"
);
// group.query and offset
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.query","id:[2 TO 5]", "fl","id", "rows","3", "start","2", "group.main","true")
,"/response=={'numFound':4,'start':2,'docs':[{'id':'2'},{'id':'5'}]}"
,"/response=={'numFound':4,'start':2,'numFoundExact':true,'docs':[{'id':'2'},{'id':'5'}]}"
);
// group.query and big offset
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.query","id:[2 TO 5]", "fl","id", "rows","3", "start","10", "group.main","true")
,"/response=={'numFound':4,'start':10,'docs':[]}"
,"/response=={'numFound':4,'start':10,'numFoundExact':true,'docs':[]}"
);
@ -625,46 +625,46 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"group.field",f,
"rows","1",
"fl","id", "group.limit","2")
,"/grouped/id:[2 TO 5]=={'matches':10,'doclist':{'numFound':4,'start':0,'docs':[{'id':'3'},{'id':'4'}]}}"
,"/grouped/id:[5 TO 5]=={'matches':10,'doclist':{'numFound':1,'start':0,'docs':[{'id':'5'}]}}"
,"/grouped/"+f+"=={'matches':10,'groups':[{'groupValue':1,'doclist':{'numFound':3,'start':0,'docs':[{'id':'8'},{'id':'10'}]}}]}"
,"/grouped/id:[2 TO 5]=={'matches':10,'doclist':{'numFound':4,'start':0,numFoundExact:true,'docs':[{'id':'3'},{'id':'4'}]}}"
,"/grouped/id:[5 TO 5]=={'matches':10,'doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'5'}]}}"
,"/grouped/"+f+"=={'matches':10,'groups':[{'groupValue':1,'doclist':{'numFound':3,'start':0,numFoundExact:true,'docs':[{'id':'8'},{'id':'10'}]}}]}"
);
///////////////////////// group.field as main result
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "group.main","true")
,"/response=={'numFound':10,'start':0,'docs':[{'id':'8'},{'id':'3'},{'id':'4'},{'id':'1'},{'id':'2'}]}"
,"/response=={'numFound':10,'start':0,numFoundExact:true,'docs':[{'id':'8'},{'id':'3'},{'id':'4'},{'id':'1'},{'id':'2'}]}"
);
// test that rows limits #docs
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","3", "group.main","true")
,"/response=={'numFound':10,'start':0,'docs':[{'id':'8'},{'id':'3'},{'id':'4'}]}"
,"/response=={'numFound':10,'start':0,numFoundExact:true,'docs':[{'id':'8'},{'id':'3'},{'id':'4'}]}"
);
// small offset
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","2", "start","1", "group.main","true")
,"/response=={'numFound':10,'start':1,'docs':[{'id':'3'},{'id':'4'}]}"
,"/response=={'numFound':10,'start':1,'numFoundExact':true,'docs':[{'id':'3'},{'id':'4'}]}"
);
// large offset
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","2", "start","20", "group.main","true")
,"/response=={'numFound':10,'start':20,'docs':[]}"
,"/response=={'numFound':10,'start':20,'numFoundExact':true,'docs':[]}"
);
// group.limit>1
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","3", "group.limit","2", "group.main","true")
,"/response=={'numFound':10,'start':0,'docs':[{'id':'8'},{'id':'10'},{'id':'3'}]}"
,"/response=={'numFound':10,'start':0,numFoundExact:true,'docs':[{'id':'8'},{'id':'10'},{'id':'3'}]}"
);
// group.limit>1 with start>0
assertJQ(req("fq",filt, "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","3", "start","1", "group.limit","2", "group.main","true")
,"/response=={'numFound':10,'start':1,'docs':[{'id':'10'},{'id':'3'},{'id':'6'}]}"
,"/response=={'numFound':10,'start':1,'numFoundExact':true,'docs':[{'id':'10'},{'id':'3'},{'id':'6'}]}"
);
///////////////////////// group.format == simple
assertJQ(req("fq", filt, "q", "{!func}" + f2, "group", "true", "group.field", f, "fl", "id", "rows", "3", "start", "1", "group.limit", "2", "group.format", "simple")
, "/grouped/foo_i=={'matches':10,'doclist':"
+ "{'numFound':10,'start':1,'docs':[{'id':'10'},{'id':'3'},{'id':'6'}]}}"
+ "{'numFound':10,'start':1,'numFoundExact':true,'docs':[{'id':'10'},{'id':'3'},{'id':'6'}]}}"
);
//////////////////////// grouping where main query matches nothing
assertJQ(req("fq", filt, "q", "bogus_s:nothing", "group", "true", "group.field", f, "fl", "id", "group.limit", "2", "group.format", "simple")
, "/grouped/foo_i=={'matches':0,'doclist':{'numFound':0,'start':0,'docs':[]}}"
, "/grouped/foo_i=={'matches':0,'doclist':{'numFound':0,'start':0,numFoundExact:true,'docs':[]}}"
);
assertJQ(req("fq",filt, "q","bogus_s:nothing", "group","true",
"group.query","id:[2 TO 5]",
@ -672,8 +672,8 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"group.field",f,
"rows","1",
"fl","id", "group.limit","2")
,"/grouped/id:[2 TO 5]=={'matches':0,'doclist':{'numFound':0,'start':0,'docs':[]}}"
,"/grouped/id:[5 TO 5]=={'matches':0,'doclist':{'numFound':0,'start':0,'docs':[]}}"
,"/grouped/id:[2 TO 5]=={'matches':0,'doclist':{'numFound':0,'start':0,numFoundExact:true,'docs':[]}}"
,"/grouped/id:[5 TO 5]=={'matches':0,'doclist':{'numFound':0,'start':0,numFoundExact:true,'docs':[]}}"
,"/grouped/"+f+"=={'matches':0,'groups':[]}"
);
assertJQ(req("fq",filt,
@ -683,8 +683,8 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"group.query","id:1000",
"fl","id",
"group.limit","3")
,"/grouped/id:[2 TO 5]=={'matches':0,'doclist':{'numFound':0,'start':0,'docs':[]}}"
,"/grouped/id:1000=={'matches':0,'doclist':{'numFound':0,'start':0,'docs':[]}}"
,"/grouped/id:[2 TO 5]=={'matches':0,'doclist':{'numFound':0,'start':0,numFoundExact:true,'docs':[]}}"
,"/grouped/id:1000=={'matches':0,'doclist':{'numFound':0,'start':0,numFoundExact:true,'docs':[]}}"
);
}
@ -709,7 +709,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
"facet.query", "{!ex=chk key=LM3}bday:[2012-10-18T00:00:00Z TO 2013-01-17T23:59:59Z]");
assertJQ(
req,
"/grouped=={'"+FOO_STRING_DOCVAL_FIELD+"':{'matches':2,'groups':[{'groupValue':'a','doclist':{'numFound':1,'start':0,'docs':[{'id':'5'}]}}]}}",
"/grouped=={'"+FOO_STRING_DOCVAL_FIELD+"':{'matches':2,'groups':[{'groupValue':'a','doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'5'}]}}]}}",
"/facet_counts=={'facet_queries':{'LW1':2,'LM1':2,'LM3':2},'facet_fields':{}," + EMPTY_FACETS + "}"
);
}
@ -730,10 +730,10 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
assertJQ(req(params, "group.field", "date_dt", "sort", "id asc"),
"/grouped=={'date_dt':{'matches':5,'ngroups':4, 'groups':" +
"[{'groupValue':'2012-11-20T00:00:00Z','doclist':{'numFound':2,'start':0,'docs':[{'id':'1'},{'id':'3'}]}}," +
"{'groupValue':'2012-11-21T00:00:00Z','doclist':{'numFound':1,'start':0,'docs':[{'id':'2'}]}}," +
"{'groupValue':'2013-01-15T00:00:00Z','doclist':{'numFound':1,'start':0,'docs':[{'id':'4'}]}}," +
"{'groupValue':null,'doclist':{'numFound':1,'start':0,'docs':[{'id':'5'}]}}" +
"[{'groupValue':'2012-11-20T00:00:00Z','doclist':{'numFound':2,'start':0,numFoundExact:true,'docs':[{'id':'1'},{'id':'3'}]}}," +
"{'groupValue':'2012-11-21T00:00:00Z','doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'2'}]}}," +
"{'groupValue':'2013-01-15T00:00:00Z','doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'4'}]}}," +
"{'groupValue':null,'doclist':{'numFound':1,'start':0,numFoundExact:true,'docs':[{'id':'5'}]}}" +
"]}}"
);
}
@ -939,6 +939,31 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
} // end query iter
} // end index iter
}
@Test
public void testGroupWithMinExactHitCount() throws Exception {
final int NUM_DOCS = 20;
for (int i = 0; i < NUM_DOCS ; i++) {
assertU(adoc("id", String.valueOf(i), FOO_STRING_FIELD, "Book1"));
assertU(commit());
}
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("q", FOO_STRING_FIELD + ":Book1");
assertQ(req(params, CommonParams.MIN_EXACT_HITS, "2", CommonParams.ROWS, "2")
,"/response/result[@numFoundExact='false']"
);
params.set("group", true);
params.set("group.field", FOO_STRING_FIELD);
assertQ(req(params)
,"/response/lst[@name='grouped']/lst[@name='"+FOO_STRING_FIELD+"']/arr[@name='groups']/lst[1]/result[@numFoundExact='true']"
);
assertQ(req(params, CommonParams.MIN_EXACT_HITS, "2", CommonParams.ROWS, "2")
,"/response/lst[@name='grouped']/lst[@name='"+FOO_STRING_FIELD+"']/arr[@name='groups']/lst[1]/result[@numFoundExact='true']"
);
}
public static Object buildGroupedResult(IndexSchema schema, List<Grp> sortedGroups, int start, int rows, int group_offset, int group_limit, boolean includeNGroups) {
@ -952,7 +977,7 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
if (includeNGroups) {
result.put("ngroups", sortedGroups.size());
}
List groupList = new ArrayList();
List<Map<String,Object>> groupList = new ArrayList<>();
result.put("groups", groupList);
for (int i=start; i<sortedGroups.size(); i++) {
@ -967,7 +992,8 @@ public class TestGroupingSearch extends SolrTestCaseJ4 {
group.put("doclist", resultSet);
resultSet.put("numFound", grp.docs.size());
resultSet.put("start", group_offset);
List docs = new ArrayList();
resultSet.put("numFoundExact", true);
List<Map<String,Object>> docs = new ArrayList<>();
resultSet.put("docs", docs);
for (int j=group_offset; j<grp.docs.size(); j++) {
if (group_limit != -1 && docs.size() >= group_limit) break;

View File

@ -80,37 +80,37 @@ public class TestJoin extends SolrTestCaseJ4 {
ModifiableSolrParams p = params("sort","id asc");
assertJQ(req(p, "q", buildJoinRequest(DEPT_FIELD, DEPT_ID_FIELD, "title:MTS"), "fl","id")
,"/response=={'numFound':3,'start':0,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}"
,"/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}"
);
// empty from
assertJQ(req(p, "q", buildJoinRequest("noexist_ss_dv", DEPT_ID_FIELD, "*:*", "fl","id"))
,"/response=={'numFound':0,'start':0,'docs':[]}"
,"/response=={'numFound':0,'start':0,'numFoundExact':true,'docs':[]}"
);
// empty to
assertJQ(req(p, "q", buildJoinRequest(DEPT_FIELD, "noexist_ss_dv", "*:*"), "fl","id")
,"/response=={'numFound':0,'start':0,'docs':[]}"
,"/response=={'numFound':0,'start':0,'numFoundExact':true,'docs':[]}"
);
// self join... return everyone in same dept(s) as Dave
assertJQ(req(p, "q", buildJoinRequest(DEPT_FIELD, DEPT_FIELD, "name:dave"), "fl","id")
,"/response=={'numFound':3,'start':0,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
,"/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
);
// from single-value to multi-value
assertJQ(req(p, "q", buildJoinRequest(DEPT_ID_FIELD, DEPT_FIELD, "text:develop"), "fl","id")
,"/response=={'numFound':3,'start':0,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
,"/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
);
// from multi-value to single-value
assertJQ(req(p, "q",buildJoinRequest(DEPT_FIELD, DEPT_ID_FIELD, "title:MTS"), "fl","id", "debugQuery","true")
,"/response=={'numFound':3,'start':0,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}"
,"/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}"
);
// expected outcome for a sub query matching dave joined against departments
final String davesDepartments =
"/response=={'numFound':2,'start':0,'docs':[{'id':'10'},{'id':'13'}]}";
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'10'},{'id':'13'}]}";
// straight forward query
assertJQ(req(p, "q", buildJoinRequest(DEPT_FIELD, DEPT_ID_FIELD, "name:dave"), "fl","id"),
@ -134,7 +134,7 @@ public class TestJoin extends SolrTestCaseJ4 {
// find people that develop stuff - but limit via filter query to a name of "john"
// this tests filters being pushed down to queries (SOLR-3062)
assertJQ(req(p, "q", buildJoinRequest(DEPT_ID_FIELD, DEPT_FIELD, "text:develop"), "fl","id", "fq", "name:john")
,"/response=={'numFound':1,'start':0,'docs':[{'id':'1'}]}"
,"/response=={'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'1'}]}"
);
}
@ -171,7 +171,7 @@ public class TestJoin extends SolrTestCaseJ4 {
// non-DV/text field.
assertJQ(req(p, "q","{!join from=title to=title}name:dave", "fl","id")
,"/response=={'numFound':2,'start':0,'docs':[{'id':'3'},{'id':'4'}]}"
,"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'3'},{'id':'4'}]}"
);
}
@ -248,6 +248,7 @@ public class TestJoin extends SolrTestCaseJ4 {
Map<String,Object> resultSet = new LinkedHashMap<>();
resultSet.put("numFound", docList.size());
resultSet.put("start", 0);
resultSet.put("numFoundExact", true);
resultSet.put("docs", sortedDocs);
// todo: use different join queries for better coverage

View File

@ -136,7 +136,21 @@ public class QueryResultKeyTest extends SolrTestCaseJ4 {
}
assert minIters <= iter;
}
public void testMinExactHits() {
int[] nums = smallArrayOfRandomNumbers();
final Query base = new FlatHashTermQuery("base");
assertKeyEquals(new QueryResultKey(base, buildFiltersFromNumbers(nums), null, 0, 10),
new QueryResultKey(base, buildFiltersFromNumbers(nums), null, 0, 10));
assertKeyNotEquals(new QueryResultKey(base, buildFiltersFromNumbers(nums), null, 0, 10),
new QueryResultKey(base, buildFiltersFromNumbers(nums), null, 0, 20));
assertKeyNotEquals(new QueryResultKey(base, buildFiltersFromNumbers(nums), null, 0, 10),
new QueryResultKey(base, buildFiltersFromNumbers(nums), null, 0));//Integer.MAX_VALUE
assertKeyEquals(new QueryResultKey(base, buildFiltersFromNumbers(nums), null, 0, Integer.MAX_VALUE),
new QueryResultKey(base, buildFiltersFromNumbers(nums), null, 0));
}
/**
* does bi-directional equality check as well as verifying hashCode
*/

View File

@ -80,7 +80,7 @@ public class TaggerTest extends TaggerTestCase {
" </arr>\n" +
" </lst>\n" +
"</arr>\n" +
"<result name=\"response\" numFound=\"1\" start=\"0\">\n" +
"<result name=\"response\" numFound=\"1\" start=\"0\" numFoundExact=\"true\">\n" +
" <doc>\n" +
" <str name=\"id\">1</str>\n" +
" <str name=\"name\">London Business School</str>\n" +
@ -109,7 +109,7 @@ public class TaggerTest extends TaggerTestCase {
" </arr>\n" +
" </lst>\n" +
"</arr>\n" +
"<result name=\"response\" numFound=\"1\" start=\"0\">\n" +
"<result name=\"response\" numFound=\"1\" start=\"0\" numFoundExact=\"true\">\n" +
" <doc>\n" +
" <str name=\"id\">1</str>\n" +
" <str name=\"name\">London Business School</str>\n" +
@ -307,7 +307,7 @@ public class TaggerTest extends TaggerTestCase {
"\n" +
"<int name=\"tagsCount\">0</int>\n" +
"<arr name=\"tags\"/>\n" +
"<result name=\"response\" numFound=\"0\" start=\"0\">\n" +
"<result name=\"response\" numFound=\"0\" start=\"0\" numFoundExact=\"true\">\n" +
"</result>\n" +
"</response>\n";
assertEquals(expected, rspStr);

View File

@ -27,7 +27,9 @@ import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.uninverting.DocTermOrds;
import org.junit.After;
import org.junit.BeforeClass;
@ -931,5 +933,28 @@ public class TestFaceting extends SolrTestCaseJ4 {
"//lst[@name='facet_fields']/lst[@name='title_ws']/int[2][@name='Book2']",
"//lst[@name='facet_fields']/lst[@name='title_ws']/int[3][@name='Book3']");
}
@Test
public void testFacetCountsWithMinExactHits() throws Exception {
final int NUM_DOCS = 20;
for (int i = 0; i < NUM_DOCS ; i++) {
assertU(adoc("id", String.valueOf(i), "title_ws", "Book1"));
assertU(commit());
}
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("q", "title_ws:Book1");
params.set(FacetParams.FACET, "true");
params.set(FacetParams.FACET_FIELD, "title_ws");
assertQ(req(params),
"//lst[@name='facet_fields']/lst[@name='title_ws']/int[1][@name='Book1'][.='20']"
,"//*[@numFoundExact='true']"
,"//*[@numFound='" + NUM_DOCS + "']");
// It doesn't matter if we request minExactHits, when requesting facets, the numFound value is precise
assertQ(req(params, CommonParams.MIN_EXACT_HITS, "2", CommonParams.ROWS, "2"),
"//lst[@name='facet_fields']/lst[@name='title_ws']/int[1][@name='Book1'][.='20']"
,"//*[@numFoundExact='true']"
,"//*[@numFound='" + NUM_DOCS + "']");
}
}

View File

@ -180,8 +180,8 @@ public class JSONWriterTest extends SolrTestCaseJ4 {
result.contains("\"id\"") &&
result.contains("\"score\"") && result.contains("_children_"));
String expectedResult = "{'response':{'numFound':1,'start':0,'maxScore':0.7,'docs':[{'id':'1', 'score':'0.7'," +
" '_children_':{'numFound':1,'start':0,'docs':[{'id':'2', 'score':'0.4', 'path':['a>b', 'a>b>c']}] }}] }}";
String expectedResult = "{'response':{'numFound':1,'start':0,'maxScore':0.7, 'numFoundExact':true,'docs':[{'id':'1', 'score':'0.7'," +
" '_children_':{'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'2', 'score':'0.4', 'path':['a>b', 'a>b>c']}] }}] }}";
String error = JSONTestUtil.match(result, "=="+expectedResult);
assertNull("response validation failed with error: " + error, error);

View File

@ -94,7 +94,7 @@ public class TestPHPSerializedResponseWriter extends SolrTestCaseJ4 {
rsp.addResponse(sdl);
w.write(buf, req, rsp);
assertEquals("a:1:{s:8:\"response\";a:3:{s:8:\"numFound\";i:0;s:5:\"start\";i:0;s:4:\"docs\";a:2:{i:0;a:6:{s:2:\"id\";s:1:\"1\";s:5:\"data1\";s:5:\"hello\";s:5:\"data2\";i:42;s:5:\"data3\";b:1;s:5:\"data4\";a:2:{s:7:\"data4.1\";s:7:\"hashmap\";s:7:\"data4.2\";s:5:\"hello\";}s:5:\"data5\";a:3:{i:0;s:7:\"data5.1\";i:1;s:7:\"data5.2\";i:2;s:7:\"data5.3\";}}i:1;a:1:{s:2:\"id\";s:1:\"2\";}}}}",
assertEquals("a:1:{s:8:\"response\";a:4:{s:8:\"numFound\";i:0;s:5:\"start\";i:0;s:13:\"numFoundExact\";b:1;s:4:\"docs\";a:2:{i:0;a:6:{s:2:\"id\";s:1:\"1\";s:5:\"data1\";s:5:\"hello\";s:5:\"data2\";i:42;s:5:\"data3\";b:1;s:5:\"data4\";a:2:{s:7:\"data4.1\";s:7:\"hashmap\";s:7:\"data4.2\";s:5:\"hello\";}s:5:\"data5\";a:3:{i:0;s:7:\"data5.1\";i:1;s:7:\"data5.2\";i:2;s:7:\"data5.3\";}}i:1;a:1:{s:2:\"id\";s:1:\"2\";}}}}",
buf.toString());
req.close();
}

View File

@ -0,0 +1,200 @@
/*
* 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.search;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TotalHits;
import org.apache.solr.SolrTestCaseJ4;
import org.junit.Before;
import org.junit.BeforeClass;
import java.io.IOException;
public class SolrIndexSearcherTest extends SolrTestCaseJ4 {
private final static int NUM_DOCS = 20;
@BeforeClass
public static void setUpClass() throws Exception {
initCore("solrconfig.xml", "schema.xml");
for (int i = 0 ; i < NUM_DOCS ; i ++) {
assertU(adoc("id", String.valueOf(i), "field1_s", "foo", "field2_s", String.valueOf(i % 2), "field3_s", String.valueOf(i)));
assertU(commit());
}
}
@Before
public void setUp() throws Exception {
assertU(adoc("id", "1", "field1_s", "foo", "field2_s", "1", "field3_s", "1"));
assertU(commit());
super.setUp();
}
public void testMinExactHitsLongValue() {
assertQ("test query on empty index",
req("q", "field1_s:foo",
"minExactHits", Long.toString(10L * Integer.MAX_VALUE),
"rows", "2")
,"//*[@numFoundExact='true']"
,"//*[@numFound='" + NUM_DOCS + "']"
);
}
public void testMinExactHits() {
assertQ("minExactHits is lower than numFound,should produce approximated results",
req("q", "field1_s:foo",
"minExactHits", "2",
"rows", "2")
,"//*[@numFoundExact='false']"
,"//*[@numFound<='" + NUM_DOCS + "']"
);
assertQ("minExactHits is higher than numFound,should produce exact results",
req("q", "field1_s:foo",
"minExactHits", "200",
"rows", "2")
,"//*[@numFoundExact='true']"
,"//*[@numFound='" + NUM_DOCS + "']"
);
}
private void assertMatchesEqual(int expectedCount, QueryResult qr) {
assertEquals(expectedCount, qr.getDocList().matches());
assertEquals(TotalHits.Relation.EQUAL_TO, qr.getDocList().hitCountRelation());
}
private void assertMatchesGraterThan(int expectedCount, QueryResult qr) {
assertTrue("Expecting returned matches to be greater than " + expectedCount + " but got " + qr.getDocList().matches(),
expectedCount >= qr.getDocList().matches());
assertEquals(TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, qr.getDocList().hitCountRelation());
}
public void testLowMinExactHitsGeneratesApproximation() throws IOException {
h.getCore().withSearcher(searcher -> {
QueryCommand cmd = new QueryCommand();
cmd.setMinExactHits(NUM_DOCS / 2);
cmd.setQuery(new TermQuery(new Term("field1_s", "foo")));
QueryResult qr = new QueryResult();
searcher.search(qr, cmd);
assertMatchesGraterThan(NUM_DOCS, qr);
return null;
});
h.getCore().withSearcher(searcher -> {
QueryCommand cmd = new QueryCommand();
cmd.setMinExactHits(1);
cmd.setLen(1);
// We need to disable cache, otherwise the search will be done for 20 docs (cache window size) which brings up the minExactHits
cmd.setFlags(SolrIndexSearcher.NO_CHECK_QCACHE | SolrIndexSearcher.NO_SET_QCACHE);
cmd.setQuery(new TermQuery(new Term("field2_s", "1")));
QueryResult qr = new QueryResult();
searcher.search(qr, cmd);
assertMatchesGraterThan(NUM_DOCS/2, qr);
return null;
});
}
public void testHighMinExactHitsGeneratesExactCount() throws IOException {
h.getCore().withSearcher(searcher -> {
QueryCommand cmd = new QueryCommand();
cmd.setMinExactHits(NUM_DOCS);
cmd.setQuery(new TermQuery(new Term("field1_s", "foo")));
QueryResult qr = new QueryResult();
searcher.search(qr, cmd);
assertMatchesEqual(NUM_DOCS, qr);
return null;
});
h.getCore().withSearcher(searcher -> {
QueryCommand cmd = new QueryCommand();
cmd.setMinExactHits(NUM_DOCS);
cmd.setQuery(new TermQuery(new Term("field2_s", "1")));
QueryResult qr = new QueryResult();
searcher.search(qr, cmd);
assertMatchesEqual(NUM_DOCS/2, qr);
return null;
});
}
public void testLowMinExactHitsWithQueryResultCache() throws IOException {
h.getCore().withSearcher(searcher -> {
QueryCommand cmd = new QueryCommand();
cmd.setMinExactHits(NUM_DOCS / 2);
cmd.setQuery(new TermQuery(new Term("field1_s", "foo")));
searcher.search(new QueryResult(), cmd);
QueryResult qr = new QueryResult();
searcher.search(qr, cmd);
assertMatchesGraterThan(NUM_DOCS, qr);
return null;
});
}
public void testHighMinExactHitsWithQueryResultCache() throws IOException {
h.getCore().withSearcher(searcher -> {
QueryCommand cmd = new QueryCommand();
cmd.setMinExactHits(NUM_DOCS);
cmd.setQuery(new TermQuery(new Term("field1_s", "foo")));
searcher.search(new QueryResult(), cmd);
QueryResult qr = new QueryResult();
searcher.search(qr, cmd);
assertMatchesEqual(NUM_DOCS, qr);
return null;
});
}
public void testMinExactHitsMoreRows() throws IOException {
h.getCore().withSearcher(searcher -> {
QueryCommand cmd = new QueryCommand();
cmd.setMinExactHits(2);
cmd.setLen(NUM_DOCS);
cmd.setQuery(new TermQuery(new Term("field1_s", "foo")));
QueryResult qr = new QueryResult();
searcher.search(qr, cmd);
assertMatchesEqual(NUM_DOCS, qr);
return null;
});
}
public void testMinExactHitsMatchWithDocSet() throws IOException {
h.getCore().withSearcher(searcher -> {
QueryCommand cmd = new QueryCommand();
cmd.setNeedDocSet(true);
cmd.setMinExactHits(2);
cmd.setQuery(new TermQuery(new Term("field1_s", "foo")));
searcher.search(new QueryResult(), cmd);
QueryResult qr = new QueryResult();
searcher.search(qr, cmd);
assertMatchesEqual(NUM_DOCS, qr);
return null;
});
}
public void testMinExactHitsWithMaxScoreRequested() throws IOException {
h.getCore().withSearcher(searcher -> {
QueryCommand cmd = new QueryCommand();
cmd.setMinExactHits(2);
cmd.setFlags(SolrIndexSearcher.GET_SCORES);
cmd.setQuery(new TermQuery(new Term("field1_s", "foo")));
searcher.search(new QueryResult(), cmd);
QueryResult qr = new QueryResult();
searcher.search(qr, cmd);
assertMatchesGraterThan(NUM_DOCS, qr);
assertNotEquals(Float.NaN, qr.getDocList().maxScore());
return null;
});
}
}

View File

@ -59,11 +59,13 @@ public class TestAddFieldRealTimeGet extends TestRTGBase {
String newFieldType = "string";
String newFieldValue = "xyz";
ignoreException("unknown field");
assertFailedU("Should fail due to unknown field '" + newFieldName + "'",
adoc("id", "1", newFieldName, newFieldValue));
unIgnoreException("unknown field");
IndexSchema schema = h.getCore().getLatestSchema();
SchemaField newField = schema.newField(newFieldName, newFieldType, Collections.<String,Object>emptyMap());
SchemaField newField = schema.newField(newFieldName, newFieldType, Collections.emptyMap());
IndexSchema newSchema = schema.addField(newField);
h.getCore().setLatestSchema(newSchema);
@ -74,7 +76,7 @@ public class TestAddFieldRealTimeGet extends TestRTGBase {
assertJQ(req("qt","/get", "id","1", "fl","id,"+newFieldName),
"=={'doc':{'id':'1'," + newFieldKeyValue + "}}");
assertJQ(req("qt","/get","ids","1", "fl","id,"+newFieldName),
"=={'response':{'numFound':1,'start':0,'docs':[{'id':'1'," + newFieldKeyValue + "}]}}");
"=={'response':{'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'1'," + newFieldKeyValue + "}]}}");
assertU(commit());
@ -83,6 +85,6 @@ public class TestAddFieldRealTimeGet extends TestRTGBase {
assertJQ(req("qt","/get", "id","1", "fl","id,"+newFieldName),
"=={'doc':{'id':'1'," + newFieldKeyValue + "}}");
assertJQ(req("qt","/get","ids","1", "fl","id,"+newFieldName),
"=={'response':{'numFound':1,'start':0,'docs':[{'id':'1'," + newFieldKeyValue + "}]}}");
"=={'response':{'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'1'," + newFieldKeyValue + "}]}}");
}
}

View File

@ -39,6 +39,7 @@ import org.apache.lucene.index.StoredFieldVisitor;
import org.apache.lucene.index.Terms;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.util.BitSetIterator;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.FixedBitSet;
@ -114,7 +115,7 @@ public class TestDocSet extends SolrTestCase {
arr[i] = iter.nextDoc();
}
return new DocSlice(offset, len, arr, null, len*2, 100.0f);
return new DocSlice(offset, len, arr, null, len*2, 100.0f, TotalHits.Relation.EQUAL_TO);
}

View File

@ -71,7 +71,7 @@ public class TestRealTimeGet extends TestRTGBase {
);
assertJQ(req("qt","/get","ids","1", "fl","id")
,"=={" +
" 'response':{'numFound':1,'start':0,'docs':[" +
" 'response':{'numFound':1,'start':0,'numFoundExact':true,'docs':[" +
" {" +
" 'id':'1'}]" +
" }}}"
@ -98,7 +98,7 @@ public class TestRealTimeGet extends TestRTGBase {
);
assertJQ(req("qt","/get","ids","1", "fl","id")
,"=={" +
" 'response':{'numFound':1,'start':0,'docs':[" +
" 'response':{'numFound':1,'start':0,'numFoundExact':true,'docs':[" +
" {" +
" 'id':'1'}]" +
" }}}"
@ -113,7 +113,7 @@ public class TestRealTimeGet extends TestRTGBase {
,"=={'doc':null}"
);
assertJQ(req("qt","/get","ids","1")
,"=={'response':{'numFound':0,'start':0,'docs':[]}}"
,"=={'response':{'numFound':0,'start':0,'numFoundExact':true,'docs':[]}}"
);

View File

@ -3131,7 +3131,7 @@ public class TestJsonFacets extends SolrTestCaseHS {
" }" +
"}" )
, "response=={numFound:10,start:0,docs:[]}"
, "response=={numFound:10,start:0,numFoundExact:true,docs:[]}"
, "facets=={ count:10," +
"types:{" +
" buckets:[ {val:page, count:10, in_books:2, via_field:2, via_query:2 } ]}" +

View File

@ -130,7 +130,7 @@ public class TestJsonFacetsWithNestedObjects extends SolrTestCaseHS{
" }" +
"}"
)
, "response=={numFound:2,start:0,docs:[" +
, "response=={numFound:2,start:0,'numFoundExact':true,docs:[" +
" {id:book1_c1," +
" comment_t:\"A great start to what looks like an epic series!\"}," +
" {id:book2_c1," +
@ -167,7 +167,7 @@ public class TestJsonFacetsWithNestedObjects extends SolrTestCaseHS{
" }" +
"}"
)
, "response=={numFound:2,start:0,docs:[" +
, "response=={numFound:2,start:0,'numFoundExact':true,docs:[" +
" {id:book1," +
" title_t:\"The Way of Kings\"}," +
" {id:book2," +
@ -213,7 +213,7 @@ public class TestJsonFacetsWithNestedObjects extends SolrTestCaseHS{
" facet: {" +
" in_books: \"unique(_root_)\" }}}}}" )
, "response=={numFound:2,start:0,docs:[" +
, "response=={numFound:2,start:0,'numFoundExact':true,docs:[" +
" {id:book1," +
" title_t:\"The Way of Kings\"}," +
" {id:book2," +
@ -267,7 +267,7 @@ public class TestJsonFacetsWithNestedObjects extends SolrTestCaseHS{
" facet: {" +
" in_books: \"unique(_root_)\" }}}}}" )
, "response=={numFound:2,start:0,docs:[" +
, "response=={numFound:2,start:0,'numFoundExact':true,docs:[" +
" {id:book1," +
" title_t:\"The Way of Kings\"}," +
" {id:book2," +
@ -324,7 +324,7 @@ public class TestJsonFacetsWithNestedObjects extends SolrTestCaseHS{
" in_books: \"unique(_root_)\" }}"+
"}" )
, "response=={numFound:0,start:0,docs:[]}"
, "response=={numFound:0,start:0,'numFoundExact':true,docs:[]}"
, "facets=={ count:0," +
"comments_for_author:{" +
" buckets:[ {val:mary, count:1, in_books:1} ]}," +
@ -364,7 +364,7 @@ public class TestJsonFacetsWithNestedObjects extends SolrTestCaseHS{
" }" +
"}" )
, "response=={numFound:2,start:0,docs:[]}"
, "response=={numFound:2,start:0,'numFoundExact':true,docs:[]}"
, "facets=={ count:2," +
"types:{" +
" buckets:[ {val:review, count:5, in_books1:2, in_books2:2, "

View File

@ -78,41 +78,41 @@ public class TestScoreJoinQPNoScore extends SolrTestCaseJ4 {
// );
assertJQ(req("q","{!join from=dept_ss to=dept_id_s"+whateverScore()+"}title_s:MTS", "fl","id")
,"/response=={'numFound':3,'start':0,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}"
,"/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}"
);
// empty from
assertJQ(req("q","{!join from=noexist_s to=dept_id_s"+whateverScore()+"}*:*", "fl","id")
,"/response=={'numFound':0,'start':0,'docs':[]}"
,"/response=={'numFound':0,'start':0,'numFoundExact':true,'docs':[]}"
);
// empty to
assertJQ(req("q","{!join from=dept_ss to=noexist_s"+whateverScore()+"}*:*", "fl","id")
,"/response=={'numFound':0,'start':0,'docs':[]}"
,"/response=={'numFound':0,'start':0,'numFoundExact':true,'docs':[]}"
);
// self join... return everyone with she same title as Dave
assertJQ(req("q","{!join from=title_s to=title_s"+whateverScore()+"}name_s:dave", "fl","id")
,"/response=={'numFound':2,'start':0,'docs':[{'id':'3'},{'id':'4'}]}"
,"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'3'},{'id':'4'}]}"
);
// find people that develop stuff
assertJQ(req("q","{!join from=dept_id_s to=dept_ss"+whateverScore()+"}text_t:develop", "fl","id")
,"/response=={'numFound':3,'start':0,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
,"/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
);
// self join on multivalued text_t field
assertJQ(req("q","{!join from=title_s to=title_s"+whateverScore()+"}name_s:dave", "fl","id")
,"/response=={'numFound':2,'start':0,'docs':[{'id':'3'},{'id':'4'}]}"
,"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'3'},{'id':'4'}]}"
);
assertJQ(req("q","{!join from=dept_ss to=dept_id_s"+whateverScore()+"}title_s:MTS", "fl","id", "debugQuery","true")
,"/response=={'numFound':3,'start':0,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}"
,"/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}"
);
// expected outcome for a sub query matching dave joined against departments
final String davesDepartments =
"/response=={'numFound':2,'start':0,'docs':[{'id':'10'},{'id':'13'}]}";
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'10'},{'id':'13'}]}";
// straight forward query
assertJQ(req("q","{!join from=dept_ss to=dept_id_s"+whateverScore()+"}name_s:dave",
@ -144,17 +144,17 @@ public class TestScoreJoinQPNoScore extends SolrTestCaseJ4 {
// find people that develop stuff - but limit via filter query to a name of "john"
// this tests filters being pushed down to queries (SOLR-3062)
assertJQ(req("q","{!join from=dept_id_s to=dept_ss"+whateverScore()+"}text_t:develop", "fl","id", "fq", "name_s:john")
,"/response=={'numFound':1,'start':0,'docs':[{'id':'1'}]}"
,"/response=={'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'1'}]}"
);
assertJQ(req("q","{!join from=dept_ss to=dept_id_s"+whateverScore()+"}title_s:MTS", "fl","id"
)
,"/response=={'numFound':3,'start':0,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}");
,"/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'10'},{'id':'12'},{'id':'13'}]}");
// find people that develop stuff, even if it's requested as single value
assertJQ(req("q","{!join from=dept_id_s to=dept_ss"+whateverScore()+"}text_t:develop", "fl","id")
,"/response=={'numFound':3,'start':0,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
,"/response=={'numFound':3,'start':0,'numFoundExact':true,'docs':[{'id':'1'},{'id':'4'},{'id':'5'}]}"
);
}
@ -278,6 +278,7 @@ public class TestScoreJoinQPNoScore extends SolrTestCaseJ4 {
Map<String,Object> resultSet = new LinkedHashMap<String,Object>();
resultSet.put("numFound", docList.size());
resultSet.put("start", 0);
resultSet.put("numFoundExact", true);
resultSet.put("docs", sortedDocs);
// todo: use different join queries for better coverage

View File

@ -88,7 +88,7 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
// Search for product
assertJQ(req("q", "{!join from=" + idField + " to=" + toField + " score=None}name:name2", "fl", "id")
, "/response=={'numFound':2,'start':0,'docs':[{'id':'5'},{'id':'6'}]}");
, "/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'5'},{'id':'6'}]}");
/*Query joinQuery =
JoinUtil.createJoinQuery(idField, false, toField, new TermQuery(new Term("name", "name2")), indexSearcher, ScoreMode.None);
@ -99,7 +99,7 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
assertEquals(5, result.scoreDocs[1].doc);
*/
assertJQ(req("q", "{!join from=" + idField + " to=" + toField + " score=None}name:name1", "fl", "id")
, "/response=={'numFound':2,'start':0,'docs':[{'id':'2'},{'id':'3'}]}");
, "/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'2'},{'id':'3'}]}");
/*joinQuery = JoinUtil.createJoinQuery(idField, false, toField, new TermQuery(new Term("name", "name1")), indexSearcher, ScoreMode.None);
result = indexSearcher.search(joinQuery, 10);
@ -109,7 +109,7 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
// Search for offer
assertJQ(req("q", "{!join from=" + toField + " to=" + idField + " score=None}id:5", "fl", "id")
, "/response=={'numFound':1,'start':0,'docs':[{'id':'4'}]}");
, "/response=={'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'4'}]}");
/*joinQuery = JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("id", "5")), indexSearcher, ScoreMode.None);
result = indexSearcher.search(joinQuery, 10);
assertEquals(1, result.totalHits);
@ -122,10 +122,10 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
public void testDeleteByScoreJoinQuery() throws Exception {
indexDataForScorring();
String joinQuery = "{!join from=" + toField + " to=" + idField + " score=Max}title:random";
assertJQ(req("q", joinQuery, "fl", "id"), "/response=={'numFound':2,'start':0,'docs':[{'id':'1'},{'id':'4'}]}");
assertJQ(req("q", joinQuery, "fl", "id"), "/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'1'},{'id':'4'}]}");
assertU(delQ(joinQuery));
assertU(commit());
assertJQ(req("q", joinQuery, "fl", "id"), "/response=={'numFound':0,'start':0,'docs':[]}");
assertJQ(req("q", joinQuery, "fl", "id"), "/response=={'numFound':0,'start':0,'numFoundExact':true,'docs':[]}");
}
public void testSimpleWithScoring() throws Exception {
@ -133,7 +133,7 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
// Search for movie via subtitle
assertJQ(req("q", "{!join from=" + toField + " to=" + idField + " score=Max}title:random", "fl", "id")
, "/response=={'numFound':2,'start':0,'docs':[{'id':'1'},{'id':'4'}]}");
, "/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'1'},{'id':'4'}]}");
//dump(req("q","{!scorejoin from="+toField+" to="+idField+" score=Max}title:random", "fl","id,score", "debug", "true"));
/*
Query joinQuery =
@ -149,7 +149,7 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
// dump(req("q","title:movie", "fl","id,score", "debug", "true"));
assertJQ(req("q", "{!join from=" + toField + " to=" + idField + " score=Max}title:movie", "fl", "id")
, "/response=={'numFound':2,'start':0,'docs':[{'id':'4'},{'id':'1'}]}");
, "/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'4'},{'id':'1'}]}");
/*joinQuery = JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("title", "movie")), indexSearcher, ScoreMode.Max);
result = indexSearcher.search(joinQuery, 10);
@ -159,7 +159,7 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
// Score mode total
assertJQ(req("q", "{!join from=" + toField + " to=" + idField + " score=Total}title:movie", "fl", "id")
, "/response=={'numFound':2,'start':0,'docs':[{'id':'1'},{'id':'4'}]}");
, "/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'1'},{'id':'4'}]}");
/* joinQuery = JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("title", "movie")), indexSearcher, ScoreMode.Total);
result = indexSearcher.search(joinQuery, 10);
assertEquals(2, result.totalHits);
@ -168,7 +168,7 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
*/
//Score mode avg
assertJQ(req("q", "{!join from=" + toField + " to=" + idField + " score=Avg}title:movie", "fl", "id")
, "/response=={'numFound':2,'start':0,'docs':[{'id':'4'},{'id':'1'}]}");
, "/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'4'},{'id':'1'}]}");
/* joinQuery = JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("title", "movie")), indexSearcher, ScoreMode.Avg);
result = indexSearcher.search(joinQuery, 10);

View File

@ -31,7 +31,16 @@ public class SolrDocumentList extends ArrayList<SolrDocument>
private long numFound = 0;
private long start = 0;
private Float maxScore = null;
private Boolean numFoundExact = true;
public Boolean getNumFoundExact() {
return numFoundExact;
}
public void setNumFoundExact(Boolean numFoundExact) {
this.numFoundExact = numFoundExact;
}
public Float getMaxScore() {
return maxScore;
}
@ -59,6 +68,7 @@ public class SolrDocumentList extends ArrayList<SolrDocument>
@Override
public String toString() {
return "{numFound="+numFound
+",numFoundExact="+String.valueOf(numFoundExact)
+",start="+start
+ (maxScore!=null ? ",maxScore="+maxScore : "")
+",docs="+super.toString()

View File

@ -162,6 +162,12 @@ public interface CommonParams {
* Timeout value in milliseconds. If not set, or the value is &gt;= 0, there is no timeout.
*/
String TIME_ALLOWED = "timeAllowed";
/**
* The number of hits that need to be counted accurately. If more than {@link #MIN_EXACT_HITS} documents
* match a query, then the value in "numFound" may be an estimate to speedup search.
*/
String MIN_EXACT_HITS = "minExactHits";
/** 'true' if the header should include the handler name */
String HEADER_ECHO_HANDLER = "echoHandler";

View File

@ -479,6 +479,9 @@ public class FastJavaBinDecoder implements DataEntry.FastDecoder {
solrDocs.setNumFound((Long) list.get(0));
solrDocs.setStart((Long) list.get(1));
solrDocs.setMaxScore((Float) list.get(2));
if (list.size() > 3) { //needed for back compatibility
solrDocs.setNumFoundExact((Boolean)list.get(3));
}
}
List<SolrDocument> l = codec.readArray(codec.dis, entry.size);
solrDocs.addAll(l);

View File

@ -588,10 +588,14 @@ public class JavaBinCodec implements PushWriter {
public SolrDocumentList readSolrDocumentList(DataInputInputStream dis) throws IOException {
SolrDocumentList solrDocs = new SolrDocumentList();
List list = (List) readVal(dis);
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) readVal(dis);
solrDocs.setNumFound((Long) list.get(0));
solrDocs.setStart((Long) list.get(1));
solrDocs.setMaxScore((Float) list.get(2));
if (list.size() > 3) { //needed for back compatibility
solrDocs.setNumFoundExact((Boolean)list.get(3));
}
@SuppressWarnings("unchecked")
List<SolrDocument> l = (List<SolrDocument>) readVal(dis);
@ -602,10 +606,11 @@ public class JavaBinCodec implements PushWriter {
public void writeSolrDocumentList(SolrDocumentList docs)
throws IOException {
writeTag(SOLRDOCLST);
List<Number> l = new ArrayList<>(3);
List<Object> l = new ArrayList<>(4);
l.add(docs.getNumFound());
l.add(docs.getStart());
l.add(docs.getMaxScore());
l.add(docs.getNumFoundExact());
writeArray(l);
writeArray(docs);
}

View File

@ -33,4 +33,6 @@ public class CommonParamsTest extends SolrTestCase
public void testRowsDefault() { assertEquals(10, CommonParams.ROWS_DEFAULT); }
public void testPreferLocalShards() { assertEquals("preferLocalShards", CommonParams.PREFER_LOCAL_SHARDS); }
public void testMinExactHits() { assertEquals("minExactHits", CommonParams.MIN_EXACT_HITS); }
}

View File

@ -16,6 +16,19 @@
*/
package org.apache.solr.common.util;
import org.apache.commons.io.IOUtils;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.EnumFieldValue;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.apache.solr.util.ConcurrentLRUCache;
import org.apache.solr.util.RTimer;
import org.junit.Test;
import org.noggit.CharArr;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@ -31,19 +44,6 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.commons.io.IOUtils;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.EnumFieldValue;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.apache.solr.util.ConcurrentLRUCache;
import org.apache.solr.util.RTimer;
import org.junit.Test;
import org.noggit.CharArr;
public class TestJavaBinCodec extends SolrTestCaseJ4 {
private static final String SOLRJ_JAVABIN_BACKCOMPAT_BIN = "/solrj/javabin_backcompat.bin";
@ -137,6 +137,7 @@ public class TestJavaBinCodec extends SolrTestCaseJ4 {
SolrDocumentList solrDocs = new SolrDocumentList();
solrDocs.setMaxScore(1.0f);
solrDocs.setNumFound(1);
solrDocs.setNumFoundExact(Boolean.TRUE);
solrDocs.setStart(0);
solrDocs.add(0, doc);
types.add(solrDocs);
@ -515,7 +516,7 @@ public class TestJavaBinCodec extends SolrTestCaseJ4 {
}
private static void runInThreads(int count, Runnable runnable) throws InterruptedException {
ArrayList<Thread> t =new ArrayList();
ArrayList<Thread> t =new ArrayList<>();
for(int i=0;i<count;i++ ) t.add(new Thread(runnable));
for (Thread thread : t) thread.start();
for (Thread thread : t) thread.join();
@ -537,14 +538,14 @@ public class TestJavaBinCodec extends SolrTestCaseJ4 {
}
public static void main(String[] args) {
// TestJavaBinCodec test = new TestJavaBinCodec();
// test.genBinaryFiles();
try {
doDecodePerf(args);
} catch (Exception e) {
throw new RuntimeException(e);
}
public static void main(String[] args) throws IOException {
TestJavaBinCodec test = new TestJavaBinCodec();
test.genBinaryFiles();
// try {
// doDecodePerf(args);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
}
// common-case ascii