mirror of https://github.com/apache/lucene.git
SOLR-5725: facet.exists=true caps counts by 1 to make facet.method=enum
faster.
This commit is contained in:
parent
abd4cfb694
commit
ff69d14868
|
@ -77,6 +77,8 @@ prefix, then you will now get an error as these options are incompatible with nu
|
||||||
|
|
||||||
New Features
|
New Features
|
||||||
----------------------
|
----------------------
|
||||||
|
* SOLR-5725: facet.method=enum can bypass exact counts calculation with facet.exists=true, it just returns 1 for
|
||||||
|
terms which exists in result docset. (Alexey Kozhemiakin, Sebastian Koziel, Radoslaw Zielinski via Mikhail Khludnev)
|
||||||
|
|
||||||
Bug Fixes
|
Bug Fixes
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
@ -1265,7 +1265,14 @@ public class FacetComponent extends SearchComponent {
|
||||||
if (facetFs != null) {
|
if (facetFs != null) {
|
||||||
|
|
||||||
for (String field : facetFs) {
|
for (String field : facetFs) {
|
||||||
DistribFieldFacet ff = new DistribFieldFacet(rb, field);
|
final DistribFieldFacet ff;
|
||||||
|
|
||||||
|
if (params.getFieldBool(field, FacetParams.FACET_EXISTS, false)) {
|
||||||
|
// cap facet count by 1 with this method
|
||||||
|
ff = new DistribFacetExistsField(rb, field);
|
||||||
|
} else {
|
||||||
|
ff = new DistribFieldFacet(rb, field);
|
||||||
|
}
|
||||||
facets.put(ff.getKey(), ff);
|
facets.put(ff.getKey(), ff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1469,7 +1476,7 @@ public class FacetComponent extends SearchComponent {
|
||||||
sfc.termNum = termNum++;
|
sfc.termNum = termNum++;
|
||||||
counts.put(name, sfc);
|
counts.put(name, sfc);
|
||||||
}
|
}
|
||||||
sfc.count += count;
|
incCount(sfc, count);
|
||||||
terms.set(sfc.termNum);
|
terms.set(sfc.termNum);
|
||||||
last = count;
|
last = count;
|
||||||
}
|
}
|
||||||
|
@ -1486,6 +1493,10 @@ public class FacetComponent extends SearchComponent {
|
||||||
counted[shardNum] = terms;
|
counted[shardNum] = terms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void incCount(ShardFacetCount sfc, long count) {
|
||||||
|
sfc.count += count;
|
||||||
|
}
|
||||||
|
|
||||||
public ShardFacetCount[] getLexSorted() {
|
public ShardFacetCount[] getLexSorted() {
|
||||||
ShardFacetCount[] arr
|
ShardFacetCount[] arr
|
||||||
= counts.values().toArray(new ShardFacetCount[counts.size()]);
|
= counts.values().toArray(new ShardFacetCount[counts.size()]);
|
||||||
|
@ -1547,4 +1558,18 @@ public class FacetComponent extends SearchComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final class DistribFacetExistsField extends DistribFieldFacet {
|
||||||
|
private DistribFacetExistsField(ResponseBuilder rb, String facetStr) {
|
||||||
|
super(rb, facetStr);
|
||||||
|
SimpleFacets.checkMincountOnExists(field, minCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void incCount(ShardFacetCount sfc, long count) {
|
||||||
|
if (count>0) {
|
||||||
|
sfc.count = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -406,6 +406,7 @@ public class SimpleFacets {
|
||||||
String prefix = params.getFieldParam(field, FacetParams.FACET_PREFIX);
|
String prefix = params.getFieldParam(field, FacetParams.FACET_PREFIX);
|
||||||
String contains = params.getFieldParam(field, FacetParams.FACET_CONTAINS);
|
String contains = params.getFieldParam(field, FacetParams.FACET_CONTAINS);
|
||||||
boolean ignoreCase = params.getFieldBool(field, FacetParams.FACET_CONTAINS_IGNORE_CASE, false);
|
boolean ignoreCase = params.getFieldBool(field, FacetParams.FACET_CONTAINS_IGNORE_CASE, false);
|
||||||
|
boolean exists = params.getFieldBool(field, FacetParams.FACET_EXISTS, false);
|
||||||
|
|
||||||
NamedList<Integer> counts;
|
NamedList<Integer> counts;
|
||||||
SchemaField sf = searcher.getSchema().getField(field);
|
SchemaField sf = searcher.getSchema().getField(field);
|
||||||
|
@ -422,13 +423,15 @@ public class SimpleFacets {
|
||||||
requestedMethod = FacetMethod.FC;
|
requestedMethod = FacetMethod.FC;
|
||||||
} else if(FacetParams.FACET_METHOD_uif.equals(methodStr)) {
|
} else if(FacetParams.FACET_METHOD_uif.equals(methodStr)) {
|
||||||
requestedMethod = FacetMethod.UIF;
|
requestedMethod = FacetMethod.UIF;
|
||||||
}else{
|
} else {
|
||||||
requestedMethod=null;
|
requestedMethod=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean multiToken = sf.multiValued() || ft.multiValuedFieldCache();
|
final boolean multiToken = sf.multiValued() || ft.multiValuedFieldCache();
|
||||||
|
|
||||||
FacetMethod appliedFacetMethod = selectFacetMethod(sf, requestedMethod, mincount);
|
FacetMethod appliedFacetMethod = selectFacetMethod(field,
|
||||||
|
sf, requestedMethod, mincount,
|
||||||
|
exists);
|
||||||
|
|
||||||
RTimer timer = null;
|
RTimer timer = null;
|
||||||
if (fdebug != null) {
|
if (fdebug != null) {
|
||||||
|
@ -446,7 +449,8 @@ public class SimpleFacets {
|
||||||
switch (appliedFacetMethod) {
|
switch (appliedFacetMethod) {
|
||||||
case ENUM:
|
case ENUM:
|
||||||
assert TrieField.getMainValuePrefix(ft) == null;
|
assert TrieField.getMainValuePrefix(ft) == null;
|
||||||
counts = getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount,missing,sort,prefix, contains, ignoreCase, params);
|
counts = getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount,missing,sort,prefix, contains, ignoreCase,
|
||||||
|
exists);
|
||||||
break;
|
break;
|
||||||
case FCS:
|
case FCS:
|
||||||
assert !multiToken;
|
assert !multiToken;
|
||||||
|
@ -538,6 +542,29 @@ public class SimpleFacets {
|
||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param existsRequested facet.exists=true is passed for the given field
|
||||||
|
* */
|
||||||
|
static FacetMethod selectFacetMethod(String fieldName,
|
||||||
|
SchemaField field, FacetMethod method, Integer mincount,
|
||||||
|
boolean existsRequested) {
|
||||||
|
if (existsRequested) {
|
||||||
|
checkMincountOnExists(fieldName, mincount);
|
||||||
|
if (method == null) {
|
||||||
|
method = FacetMethod.ENUM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final FacetMethod facetMethod = selectFacetMethod(field, method, mincount);
|
||||||
|
|
||||||
|
if (existsRequested && facetMethod!=FacetMethod.ENUM) {
|
||||||
|
throw new SolrException (ErrorCode.BAD_REQUEST,
|
||||||
|
FacetParams.FACET_EXISTS + "=true is requested, but "+
|
||||||
|
FacetParams.FACET_METHOD+"="+FacetParams.FACET_METHOD_enum+ " can't be used with "+fieldName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return facetMethod;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will force the appropriate facet method even if the user provided a different one as a request parameter
|
* This method will force the appropriate facet method even if the user provided a different one as a request parameter
|
||||||
*
|
*
|
||||||
|
@ -811,7 +838,8 @@ public class SimpleFacets {
|
||||||
* @see FacetParams#FACET_ZEROS
|
* @see FacetParams#FACET_ZEROS
|
||||||
* @see FacetParams#FACET_MISSING
|
* @see FacetParams#FACET_MISSING
|
||||||
*/
|
*/
|
||||||
public NamedList<Integer> getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing, String sort, String prefix, String contains, boolean ignoreCase, SolrParams params)
|
public NamedList<Integer> getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing,
|
||||||
|
String sort, String prefix, String contains, boolean ignoreCase, boolean intersectsCheck)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
/* :TODO: potential optimization...
|
/* :TODO: potential optimization...
|
||||||
|
@ -901,7 +929,11 @@ public class SimpleFacets {
|
||||||
deState.postingsEnum = postingsEnum;
|
deState.postingsEnum = postingsEnum;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = searcher.numDocs(docs, deState);
|
if (intersectsCheck) {
|
||||||
|
c = searcher.intersects(docs, deState) ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
c = searcher.numDocs(docs, deState);
|
||||||
|
}
|
||||||
|
|
||||||
postingsEnum = deState.postingsEnum;
|
postingsEnum = deState.postingsEnum;
|
||||||
} else {
|
} else {
|
||||||
|
@ -916,19 +948,33 @@ public class SimpleFacets {
|
||||||
if (postingsEnum instanceof MultiPostingsEnum) {
|
if (postingsEnum instanceof MultiPostingsEnum) {
|
||||||
MultiPostingsEnum.EnumWithSlice[] subs = ((MultiPostingsEnum) postingsEnum).getSubs();
|
MultiPostingsEnum.EnumWithSlice[] subs = ((MultiPostingsEnum) postingsEnum).getSubs();
|
||||||
int numSubs = ((MultiPostingsEnum) postingsEnum).getNumSubs();
|
int numSubs = ((MultiPostingsEnum) postingsEnum).getNumSubs();
|
||||||
|
|
||||||
|
SEGMENTS_LOOP:
|
||||||
for (int subindex = 0; subindex < numSubs; subindex++) {
|
for (int subindex = 0; subindex < numSubs; subindex++) {
|
||||||
MultiPostingsEnum.EnumWithSlice sub = subs[subindex];
|
MultiPostingsEnum.EnumWithSlice sub = subs[subindex];
|
||||||
if (sub.postingsEnum == null) continue;
|
if (sub.postingsEnum == null) continue;
|
||||||
int base = sub.slice.start;
|
int base = sub.slice.start;
|
||||||
int docid;
|
int docid;
|
||||||
while ((docid = sub.postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
|
while ((docid = sub.postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
|
||||||
if (fastForRandomSet.exists(docid + base)) c++;
|
if (fastForRandomSet.exists(docid + base)) {
|
||||||
|
c++;
|
||||||
|
if (intersectsCheck) {
|
||||||
|
assert c==1;
|
||||||
|
break SEGMENTS_LOOP;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int docid;
|
int docid;
|
||||||
while ((docid = postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
|
while ((docid = postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
|
||||||
if (fastForRandomSet.exists(docid)) c++;
|
if (fastForRandomSet.exists(docid)) {
|
||||||
|
c++;
|
||||||
|
if (intersectsCheck) {
|
||||||
|
assert c==1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -969,6 +1015,15 @@ public class SimpleFacets {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void checkMincountOnExists(String fieldName, int mincount) {
|
||||||
|
if (mincount > 1) {
|
||||||
|
throw new SolrException (ErrorCode.BAD_REQUEST,
|
||||||
|
FacetParams.FACET_MINCOUNT + "="+mincount+" exceed 1 that's not supported with " +
|
||||||
|
FacetParams.FACET_EXISTS + "=true for " + fieldName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple key=>val pair whose natural order is such that
|
* A simple key=>val pair whose natural order is such that
|
||||||
* <b>higher</b> vals come before lower vals.
|
* <b>higher</b> vals come before lower vals.
|
||||||
|
|
|
@ -2285,6 +2285,11 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
|
||||||
return all.andNotSize(positiveA.union(positiveB));
|
return all.andNotSize(positiveA.union(positiveB));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @lucene.internal */
|
||||||
|
public boolean intersects(DocSet a, DocsEnumState deState) throws IOException {
|
||||||
|
return a.intersects(getDocSet(deState));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a list of document IDs, and returns an array of Documents containing all of the stored fields.
|
* Takes a list of document IDs, and returns an array of Documents containing all of the stored fields.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,22 +16,39 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.solr;
|
package org.apache.solr;
|
||||||
|
|
||||||
import org.apache.lucene.util.TestUtil;
|
import java.io.IOException;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.lucene.util.LuceneTestCase.Slow;
|
import org.apache.lucene.util.LuceneTestCase.Slow;
|
||||||
|
import org.apache.lucene.util.TestUtil;
|
||||||
|
import org.apache.solr.common.SolrException.ErrorCode;
|
||||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||||
import org.apache.solr.request.SolrQueryRequest;
|
import org.apache.solr.request.SolrQueryRequest;
|
||||||
import org.apache.solr.schema.SchemaField;
|
import org.apache.solr.schema.SchemaField;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.noggit.JSONUtil;
|
||||||
|
import org.noggit.ObjectBuilder;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@Slow
|
@Slow
|
||||||
public class TestRandomFaceting extends SolrTestCaseJ4 {
|
public class TestRandomFaceting extends SolrTestCaseJ4 {
|
||||||
|
|
||||||
|
private static final Pattern trieFields = Pattern.compile(".*_t.");
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
|
||||||
public static final String FOO_STRING_FIELD = "foo_s1";
|
public static final String FOO_STRING_FIELD = "foo_s1";
|
||||||
|
@ -80,6 +97,21 @@ public class TestRandomFaceting extends SolrTestCaseJ4 {
|
||||||
types.add(new FldType("missing_ss",new IRange(0,0), new SVal('a','b',1,1)));
|
types.add(new FldType("missing_ss",new IRange(0,0), new SVal('a','b',1,1)));
|
||||||
|
|
||||||
// TODO: doubles, multi-floats, ints with precisionStep>0, booleans
|
// TODO: doubles, multi-floats, ints with precisionStep>0, booleans
|
||||||
|
types.add(new FldType("small_tf",ZERO_ONE, new FVal(-4,5)));
|
||||||
|
assert trieFields.matcher("small_tf").matches();
|
||||||
|
assert !trieFields.matcher("small_f").matches();
|
||||||
|
|
||||||
|
types.add(new FldType("foo_ti",ZERO_ONE, new IRange(-2,indexSize)));
|
||||||
|
assert trieFields.matcher("foo_ti").matches();
|
||||||
|
assert !trieFields.matcher("foo_i").matches();
|
||||||
|
|
||||||
|
types.add(new FldType("bool_b",ZERO_ONE, new Vals(){
|
||||||
|
@Override
|
||||||
|
public Comparable get() {
|
||||||
|
return random().nextBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addMoreDocs(int ndocs) throws Exception {
|
void addMoreDocs(int ndocs) throws Exception {
|
||||||
|
@ -144,8 +176,8 @@ public class TestRandomFaceting extends SolrTestCaseJ4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
List<String> multiValuedMethods = Arrays.asList(new String[]{"enum","fc"});
|
List<String> multiValuedMethods = Arrays.asList(new String[]{"enum","fc", null});
|
||||||
List<String> singleValuedMethods = Arrays.asList(new String[]{"enum","fc","fcs"});
|
List<String> singleValuedMethods = Arrays.asList(new String[]{"enum","fc","fcs", null});
|
||||||
|
|
||||||
|
|
||||||
void doFacetTests(FldType ftype) throws Exception {
|
void doFacetTests(FldType ftype) throws Exception {
|
||||||
|
@ -154,10 +186,9 @@ public class TestRandomFaceting extends SolrTestCaseJ4 {
|
||||||
Random rand = random();
|
Random rand = random();
|
||||||
boolean validate = validateResponses;
|
boolean validate = validateResponses;
|
||||||
ModifiableSolrParams params = params("facet","true", "wt","json", "indent","true", "omitHeader","true");
|
ModifiableSolrParams params = params("facet","true", "wt","json", "indent","true", "omitHeader","true");
|
||||||
params.add("q","*:*", "rows","0"); // TODO: select subsets
|
params.add("q","*:*"); // TODO: select subsets
|
||||||
params.add("rows","0");
|
params.add("rows","0");
|
||||||
|
|
||||||
|
|
||||||
SchemaField sf = req.getSchema().getField(ftype.fname);
|
SchemaField sf = req.getSchema().getField(ftype.fname);
|
||||||
boolean multiValued = sf.getType().multiValuedFieldCache();
|
boolean multiValued = sf.getType().multiValuedFieldCache();
|
||||||
|
|
||||||
|
@ -198,6 +229,10 @@ public class TestRandomFaceting extends SolrTestCaseJ4 {
|
||||||
params.add("facet.missing", "true");
|
params.add("facet.missing", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rand.nextBoolean()) {
|
||||||
|
params.add("facet.enum.cache.minDf",""+ rand.nextInt(indexSize));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: randomly add other facet params
|
// TODO: randomly add other facet params
|
||||||
String key = ftype.fname;
|
String key = ftype.fname;
|
||||||
String facet_field = ftype.fname;
|
String facet_field = ftype.fname;
|
||||||
|
@ -210,45 +245,207 @@ public class TestRandomFaceting extends SolrTestCaseJ4 {
|
||||||
List<String> methods = multiValued ? multiValuedMethods : singleValuedMethods;
|
List<String> methods = multiValued ? multiValuedMethods : singleValuedMethods;
|
||||||
List<String> responses = new ArrayList<>(methods.size());
|
List<String> responses = new ArrayList<>(methods.size());
|
||||||
for (String method : methods) {
|
for (String method : methods) {
|
||||||
// params.add("facet.field", "{!key="+method+"}" + ftype.fname);
|
for (boolean exists : new boolean [] {false, true}) {
|
||||||
// TODO: allow method to be passed on local params?
|
// params.add("facet.field", "{!key="+method+"}" + ftype.fname);
|
||||||
|
// TODO: allow method to be passed on local params?
|
||||||
|
if (method!=null) {
|
||||||
|
params.set("facet.method", method);
|
||||||
|
} else {
|
||||||
|
params.remove("facet.method");
|
||||||
|
}
|
||||||
|
|
||||||
params.set("facet.method", method);
|
params.set("facet.exists", ""+exists);
|
||||||
|
if (!exists && rand.nextBoolean()) {
|
||||||
|
params.remove("facet.exists");
|
||||||
|
}
|
||||||
|
|
||||||
// if (random().nextBoolean()) params.set("facet.mincount", "1"); // uncomment to test that validation fails
|
// if (random().nextBoolean()) params.set("facet.mincount", "1"); // uncomment to test that validation fails
|
||||||
|
if (params.getInt("facet.limit", 100)!=0) { // it bypasses all processing, and we can go to empty validation
|
||||||
|
if (exists && params.getInt("facet.mincount", 0)>1) {
|
||||||
|
assertQEx("no mincount on facet.exists",
|
||||||
|
rand.nextBoolean() ? "facet.exists":"facet.mincount",
|
||||||
|
req(params), ErrorCode.BAD_REQUEST);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// facet.exists can't be combined with non-enum nor with enum requested for tries, because it will be flipped to FC/FCS
|
||||||
|
final boolean notEnum = method != null && !method.equals("enum");
|
||||||
|
final boolean trieField = trieFields.matcher(ftype.fname).matches();
|
||||||
|
if ((notEnum || trieField) && exists) {
|
||||||
|
assertQEx("facet.exists only when enum or ommitted",
|
||||||
|
"facet.exists", req(params), ErrorCode.BAD_REQUEST);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String strResponse = h.query(req(params));
|
||||||
|
responses.add(strResponse);
|
||||||
|
|
||||||
String strResponse = h.query(req(params));
|
if (responses.size()>1) {
|
||||||
// Object realResponse = ObjectBuilder.fromJSON(strResponse);
|
validateResponse(responses.get(0), strResponse, params, method, methods);
|
||||||
// System.out.println(strResponse);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
responses.add(strResponse);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
String strResponse = h.query(req(params));
|
String strResponse = h.query(req(params));
|
||||||
Object realResponse = ObjectBuilder.fromJSON(strResponse);
|
Object realResponse = ObjectBuilder.fromJSON(strResponse);
|
||||||
**/
|
**/
|
||||||
|
|
||||||
if (validate) {
|
|
||||||
for (int i=1; i<methods.size(); i++) {
|
|
||||||
String err = JSONTestUtil.match("/", responses.get(i), responses.get(0), 0.0);
|
|
||||||
if (err != null) {
|
|
||||||
log.error("ERROR: mismatch facet response: " + err +
|
|
||||||
"\n expected =" + responses.get(0) +
|
|
||||||
"\n response = " + responses.get(i) +
|
|
||||||
"\n request = " + params
|
|
||||||
);
|
|
||||||
fail(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
req.close();
|
req.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void validateResponse(String expected, String actual, ModifiableSolrParams params, String method,
|
||||||
|
List<String> methods) throws Exception {
|
||||||
|
if (params.getBool("facet.exists", false)) {
|
||||||
|
if (isSortByCount(params)) { // it's challenged with facet.sort=count
|
||||||
|
expected = getExpectationForSortByCount(params, methods);// that requires to recalculate expactation
|
||||||
|
} else { // facet.sort=index
|
||||||
|
expected = capFacetCountsTo1(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String err = JSONTestUtil.match("/", actual, expected, 0.0);
|
||||||
|
if (err != null) {
|
||||||
|
log.error("ERROR: mismatch facet response: " + err +
|
||||||
|
"\n expected =" + expected +
|
||||||
|
"\n response = " + actual +
|
||||||
|
"\n request = " + params
|
||||||
|
);
|
||||||
|
fail(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** if facet.exists=true with facet.sort=counts,
|
||||||
|
* it should return all values with 1 hits ordered by label index
|
||||||
|
* then all vals with 0 , and then missing count with null label,
|
||||||
|
* in the implementation below they are called three stratas
|
||||||
|
* */
|
||||||
|
private String getExpectationForSortByCount( ModifiableSolrParams params, List<String> methods) throws Exception {
|
||||||
|
String indexSortedResponse = getIndexSortedAllFacetValues(params, methods);
|
||||||
|
|
||||||
|
return transformFacetFields(indexSortedResponse, e -> {
|
||||||
|
List<Object> facetSortedByIndex = (List<Object>) e.getValue();
|
||||||
|
Map<Integer,List<Object>> stratas = new HashMap<Integer,List<Object>>(){
|
||||||
|
@Override // poor man multimap, I won't do that anymore, I swear.
|
||||||
|
public List<Object> get(Object key) {
|
||||||
|
if (!containsKey(key)) {
|
||||||
|
put((Integer) key, new ArrayList<>());
|
||||||
|
}
|
||||||
|
return super.get(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (Iterator iterator = facetSortedByIndex.iterator(); iterator.hasNext();) {
|
||||||
|
Object label = (Object) iterator.next();
|
||||||
|
Long count = (Long) iterator.next();
|
||||||
|
final Integer strata;
|
||||||
|
if (label==null) { // missing (here "stratas" seems like overengineering )
|
||||||
|
strata = null;
|
||||||
|
}else {
|
||||||
|
if (count>0) {
|
||||||
|
count = 1L; // capping here
|
||||||
|
strata = 1; // non-zero count become zero
|
||||||
|
} else {
|
||||||
|
strata = 0; // zero-count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final List<Object> facet = stratas.get(strata);
|
||||||
|
facet.add(label);
|
||||||
|
facet.add(count);
|
||||||
|
}
|
||||||
|
List stratified =new ArrayList<>();
|
||||||
|
for(Integer s : new Integer[]{1, 0}) { // non-zero capped to one goes first, zeroes go then
|
||||||
|
stratified.addAll(stratas.get(s));
|
||||||
|
}// cropping them now
|
||||||
|
int offset=params.getInt("facet.offset", 0) * 2;
|
||||||
|
int end = offset + params.getInt("facet.limit", 100) * 2 ;
|
||||||
|
int fromIndex = offset > stratified.size() ? stratified.size() : offset;
|
||||||
|
stratified = stratified.subList(fromIndex,
|
||||||
|
end > stratified.size() ? stratified.size() : end);
|
||||||
|
|
||||||
|
if (params.getInt("facet.limit", 100)>0) { /// limit=0 omits even miss count
|
||||||
|
stratified.addAll(stratas.get(null));
|
||||||
|
}
|
||||||
|
facetSortedByIndex.clear();
|
||||||
|
facetSortedByIndex.addAll(stratified);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getIndexSortedAllFacetValues(ModifiableSolrParams in, List<String> methods) throws Exception {
|
||||||
|
ModifiableSolrParams params = new ModifiableSolrParams(in);
|
||||||
|
params.set("facet.sort", "index");
|
||||||
|
String goodOldMethod = methods.get(random().nextInt( methods.size()));
|
||||||
|
params.set("facet.method", goodOldMethod);
|
||||||
|
params.set("facet.exists", "false");
|
||||||
|
if (random().nextBoolean()) {
|
||||||
|
params.remove("facet.exists");
|
||||||
|
}
|
||||||
|
params.set("facet.limit",-1);
|
||||||
|
params.set("facet.offset",0);
|
||||||
|
final String query;
|
||||||
|
SolrQueryRequest req = null;
|
||||||
|
try {
|
||||||
|
req = req(params);
|
||||||
|
query = h.query(req);
|
||||||
|
} finally {
|
||||||
|
req.close();
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSortByCount(ModifiableSolrParams in) {
|
||||||
|
boolean sortIsCount;
|
||||||
|
String sortParam = in.get("facet.sort");
|
||||||
|
sortIsCount = "count".equals(sortParam) || (sortParam==null && in.getInt("facet.limit",100)>0);
|
||||||
|
return sortIsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* {
|
||||||
|
"response":{"numFound":6,"start":0,"docs":[]
|
||||||
|
},
|
||||||
|
"facet_counts":{
|
||||||
|
"facet_queries":{},
|
||||||
|
"facet_fields":{
|
||||||
|
"foo_i":[
|
||||||
|
"6",2,
|
||||||
|
"2",1,
|
||||||
|
"3",1]},
|
||||||
|
"facet_ranges":{},
|
||||||
|
"facet_intervals":{},
|
||||||
|
"facet_heatmaps":{}}}
|
||||||
|
* */
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
private String capFacetCountsTo1(String expected) throws IOException {
|
||||||
|
return transformFacetFields(expected, e -> {
|
||||||
|
List<Object> facetValues = (List<Object>) e.getValue();
|
||||||
|
for (ListIterator iterator = facetValues.listIterator(); iterator.hasNext();) {
|
||||||
|
Object value = iterator.next();
|
||||||
|
Long count = (Long) iterator.next();
|
||||||
|
if (value!=null && count > 1) {
|
||||||
|
iterator.set(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String transformFacetFields(String expected, Consumer<Map.Entry<Object,Object>> consumer) throws IOException {
|
||||||
|
Object json = ObjectBuilder.fromJSON(expected);
|
||||||
|
Map facet_fields = getFacetFieldMap(json);
|
||||||
|
Set entries = facet_fields.entrySet();
|
||||||
|
for (Object facetTuples : entries) { //despite there should be only one field
|
||||||
|
Entry entry = (Entry)facetTuples;
|
||||||
|
consumer.accept(entry);
|
||||||
|
}
|
||||||
|
return JSONUtil.toJSON(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map getFacetFieldMap(Object json) {
|
||||||
|
Object facet_counts = ((Map)json).get("facet_counts");
|
||||||
|
Map facet_fields = (Map) ((Map)facet_counts).get("facet_fields");
|
||||||
|
return facet_fields;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.solr.handler.component;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.apache.solr.BaseDistributedSearchTestCase;
|
||||||
|
import org.apache.solr.client.solrj.SolrClient;
|
||||||
|
import org.apache.solr.client.solrj.SolrServerException;
|
||||||
|
import org.apache.solr.client.solrj.response.FacetField;
|
||||||
|
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||||
|
import org.apache.solr.common.SolrException;
|
||||||
|
import org.apache.solr.common.SolrException.ErrorCode;
|
||||||
|
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
|
public class DistributedFacetExistsSmallTest extends BaseDistributedSearchTestCase {
|
||||||
|
|
||||||
|
public static final String FLD = "t_s";
|
||||||
|
private int maxId;
|
||||||
|
|
||||||
|
public DistributedFacetExistsSmallTest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void prepareIndex() throws Exception {
|
||||||
|
del("*:*");
|
||||||
|
|
||||||
|
final Random rnd = random();
|
||||||
|
index(id, maxId=rnd.nextInt(5), FLD, "AAA");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "B");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "BB");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "BB");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "BBB");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "BBB");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "BBB");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "CC");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "CC");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "CCC");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "CCC");
|
||||||
|
index(id, maxId+=1+rnd.nextInt(5), FLD, "CCC");
|
||||||
|
|
||||||
|
final SolrClient shard0 = clients.get(0);
|
||||||
|
// expectidly fails test
|
||||||
|
//shard0.add(sdoc("id", 13, FLD, "DDD"));
|
||||||
|
commit();
|
||||||
|
|
||||||
|
handle.clear();
|
||||||
|
handle.put("QTime", SKIPVAL);
|
||||||
|
handle.put("timestamp", SKIPVAL);
|
||||||
|
handle.put("maxScore", SKIPVAL);
|
||||||
|
handle.put("_version_", SKIPVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ShardsFixed(num=4)
|
||||||
|
public void test() throws Exception{
|
||||||
|
checkBasicRequest();
|
||||||
|
checkWithMinCountEqOne();
|
||||||
|
checkWithSortCount();
|
||||||
|
checkWithMethodSetPerField();
|
||||||
|
|
||||||
|
{
|
||||||
|
// empty enum for checking npe
|
||||||
|
final ModifiableSolrParams params = buildParams();
|
||||||
|
params.remove("facet.exists");
|
||||||
|
QueryResponse rsp = query(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRandomParams();
|
||||||
|
|
||||||
|
checkInvalidMincount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkRandomParams() throws Exception {
|
||||||
|
final ModifiableSolrParams params = buildParams();
|
||||||
|
Random rand = random();
|
||||||
|
|
||||||
|
if (rand.nextBoolean()) {
|
||||||
|
int from;
|
||||||
|
params.set("q", "["+(from = rand.nextInt(maxId/2))+
|
||||||
|
" TO "+((from-1)+(rand.nextInt(maxId)))+"]");
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
int indexSize = 6;
|
||||||
|
if (rand .nextInt(100) < 20) {
|
||||||
|
if (rand.nextBoolean()) {
|
||||||
|
offset = rand.nextInt(100) < 10 ? rand.nextInt(indexSize *2) : rand.nextInt(indexSize/3+1);
|
||||||
|
}
|
||||||
|
params.add("facet.offset", Integer.toString(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
int limit = 100;
|
||||||
|
if (rand.nextInt(100) < 20) {
|
||||||
|
if (rand.nextBoolean()) {
|
||||||
|
limit = rand.nextInt(100) < 10 ? rand.nextInt(indexSize/2+1) : rand.nextInt(indexSize*2);
|
||||||
|
}
|
||||||
|
params.add("facet.limit", Integer.toString(limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rand.nextBoolean()) {
|
||||||
|
params.add("facet.sort", rand.nextBoolean() ? "index" : "count");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( rand.nextInt(100) < 20) {
|
||||||
|
final String[] prefixes = new String[] {"A","B","C"};
|
||||||
|
params.add("facet.prefix", prefixes[rand.nextInt(prefixes.length)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rand.nextInt(100) < 20) {
|
||||||
|
params.add("facet.missing", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rand.nextInt(100) < 20) { // assigning only valid vals
|
||||||
|
params.add("facet.mincount", rand.nextBoolean() ? "0": "1" );
|
||||||
|
}
|
||||||
|
|
||||||
|
query(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkInvalidMincount() throws SolrServerException, IOException {
|
||||||
|
final ModifiableSolrParams params = buildParams();
|
||||||
|
if (random().nextBoolean()) {
|
||||||
|
params.remove("facet.exists");
|
||||||
|
params.set("f."+FLD+".facet.exists","true");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (random().nextBoolean()) {
|
||||||
|
params.set("facet.mincount", ""+(2+random().nextInt(100)) );
|
||||||
|
} else {
|
||||||
|
params.set("f."+FLD+".facet.mincount", ""+(2+random().nextInt(100)) );
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (random().nextBoolean()) {
|
||||||
|
setDistributedParams(params);
|
||||||
|
queryServer(params);
|
||||||
|
} else {
|
||||||
|
params.set("distrib", "false");
|
||||||
|
controlClient.query(params);
|
||||||
|
}
|
||||||
|
fail();
|
||||||
|
} catch(SolrException e) { // check that distr and single index search fail the same
|
||||||
|
assertEquals(e.code(), ErrorCode.BAD_REQUEST.code);
|
||||||
|
assertTrue(e.getMessage().contains("facet.exists"));
|
||||||
|
assertTrue(e.getMessage().contains("facet.mincount"));
|
||||||
|
assertTrue(e.getMessage().contains(FLD));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBasicRequest() throws Exception {
|
||||||
|
final ModifiableSolrParams params = buildParams();
|
||||||
|
QueryResponse rsp = query(params);
|
||||||
|
assertResponse(rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkWithMinCountEqOne() throws Exception {
|
||||||
|
final ModifiableSolrParams params = buildParams("facet.mincount","1");
|
||||||
|
QueryResponse rsp = query(params);
|
||||||
|
assertResponse(rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkWithSortCount() throws Exception {
|
||||||
|
final ModifiableSolrParams params = buildParams("facet.sort","count");
|
||||||
|
QueryResponse rsp = query(params);
|
||||||
|
assertResponse(rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkWithMethodSetPerField() throws Exception {
|
||||||
|
final ModifiableSolrParams params = buildParams("f." + FLD + ".facet.exists", "true");
|
||||||
|
params.remove("facet.exists");
|
||||||
|
QueryResponse rsp = query(params);
|
||||||
|
assertResponse(rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModifiableSolrParams buildParams(String... additionalParams) {
|
||||||
|
final ModifiableSolrParams params = new ModifiableSolrParams();
|
||||||
|
|
||||||
|
params.add("q", "*:*");
|
||||||
|
params.add("rows", "0");
|
||||||
|
//params.add("debugQuery", "true");
|
||||||
|
params.add("facet", "true");
|
||||||
|
params.add("sort", "id asc");
|
||||||
|
|
||||||
|
if(random().nextBoolean()){
|
||||||
|
params.add("facet.method", "enum");
|
||||||
|
}
|
||||||
|
|
||||||
|
params.add("facet.exists", "true");
|
||||||
|
params.add("facet.field", FLD);
|
||||||
|
for(int i = 0; i < additionalParams.length;) {
|
||||||
|
params.add(additionalParams[i++], additionalParams[i++]);
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertResponse(QueryResponse rsp) {
|
||||||
|
final FacetField facetField = rsp.getFacetField(FLD);
|
||||||
|
|
||||||
|
assertThat(facetField.getValueCount(), is(6));
|
||||||
|
final List<FacetField.Count> counts = facetField.getValues();
|
||||||
|
for (FacetField.Count count : counts) {
|
||||||
|
assertThat("Count for: " + count.getName(), count.getCount(), is(1L));
|
||||||
|
}
|
||||||
|
assertThat(counts.get(0).getName(), is("AAA"));
|
||||||
|
assertThat(counts.get(1).getName(), is("B"));
|
||||||
|
assertThat(counts.get(2).getName(), is("BB"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,7 +38,6 @@ import org.apache.solr.response.SolrQueryResponse;
|
||||||
import org.apache.solr.schema.SchemaField;
|
import org.apache.solr.schema.SchemaField;
|
||||||
import org.apache.solr.util.TimeZoneUtils;
|
import org.apache.solr.util.TimeZoneUtils;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.noggit.ObjectBuilder;
|
import org.noggit.ObjectBuilder;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -494,11 +493,9 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
|
||||||
|
|
||||||
ModifiableSolrParams params = params("q","*:*", "rows","0", "facet","true", "facet.field","{!key=myalias}"+field);
|
ModifiableSolrParams params = params("q","*:*", "rows","0", "facet","true", "facet.field","{!key=myalias}"+field);
|
||||||
|
|
||||||
String[] methods = {null, "fc","enum","fcs", "uif"
|
String[] methods = {null, "fc","enum","fcs", "uif"};
|
||||||
};
|
|
||||||
if (sf.multiValued() || sf.getType().multiValuedFieldCache()) {
|
if (sf.multiValued() || sf.getType().multiValuedFieldCache()) {
|
||||||
methods = new String[]{null, "fc","enum", "uif"
|
methods = new String[]{null, "fc","enum", "uif"};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixes = prefixes==null ? new String[]{null} : prefixes;
|
prefixes = prefixes==null ? new String[]{null} : prefixes;
|
||||||
|
@ -2017,6 +2014,49 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
|
||||||
doFacetPrefix("t_s", null, "", "facet.method", "enum", "facet.enum.cache.minDf", "3");
|
doFacetPrefix("t_s", null, "", "facet.method", "enum", "facet.enum.cache.minDf", "3");
|
||||||
doFacetPrefix("t_s", null, "", "facet.method", "enum", "facet.enum.cache.minDf", "100");
|
doFacetPrefix("t_s", null, "", "facet.method", "enum", "facet.enum.cache.minDf", "100");
|
||||||
doFacetPrefix("t_s", null, "", "facet.method", "fc");
|
doFacetPrefix("t_s", null, "", "facet.method", "fc");
|
||||||
|
doFacetExistsPrefix("t_s", null, "");
|
||||||
|
doFacetExistsPrefix("t_s", null, "", "facet.enum.cache.minDf", "3");
|
||||||
|
doFacetExistsPrefix("t_s", null, "", "facet.enum.cache.minDf", "100");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFacetExistsShouldThrowExceptionForMincountGreaterThanOne () throws Exception {
|
||||||
|
final String f = "t_s";
|
||||||
|
final List<String> msg = Arrays.asList("facet.mincount", "facet.exists", f);
|
||||||
|
Collections.shuffle(msg, random());
|
||||||
|
assertQEx("checking global method or per field", msg.get(0),
|
||||||
|
req("q", "id:[* TO *]"
|
||||||
|
,"indent","on"
|
||||||
|
,"facet","true"
|
||||||
|
, random().nextBoolean() ? "facet.exists": "f."+f+".facet.exists", "true"
|
||||||
|
,"facet.field", f
|
||||||
|
, random().nextBoolean() ? "facet.mincount" : "f."+f+".facet.mincount" ,
|
||||||
|
"" + (2+random().nextInt(Integer.MAX_VALUE-2))
|
||||||
|
)
|
||||||
|
, ErrorCode.BAD_REQUEST);
|
||||||
|
|
||||||
|
assertQ("overriding per field",
|
||||||
|
req("q", "id:[* TO *]"
|
||||||
|
,"indent","on"
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"f."+f+".facet.exists", "false"
|
||||||
|
,"facet.field", f
|
||||||
|
,"facet.mincount",""+(2+random().nextInt(Integer.MAX_VALUE-2))
|
||||||
|
),
|
||||||
|
"//lst[@name='facet_fields']/lst[@name='"+f+"']");
|
||||||
|
|
||||||
|
assertQ("overriding per field",
|
||||||
|
req("q", "id:[* TO *]"
|
||||||
|
,"indent","on"
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", f
|
||||||
|
,"facet.mincount",""+(2+random().nextInt(Integer.MAX_VALUE-2))
|
||||||
|
,"f."+f+".facet.mincount", random().nextBoolean() ? "0":"1"
|
||||||
|
),
|
||||||
|
"//lst[@name='facet_fields']/lst[@name='"+f+"']");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void indexFacetPrefixSingleValued() {
|
static void indexFacetPrefixSingleValued() {
|
||||||
|
@ -2037,7 +2077,7 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("SOLR-8466 - facet.method=uif ignores facet.contains")
|
//@Ignore("SOLR-8466 - facet.method=uif ignores facet.contains")
|
||||||
public void testFacetContainsUif() {
|
public void testFacetContainsUif() {
|
||||||
doFacetContains("contains_s1", "contains_group_s1", "Astra", "BAst", "Ast", "facet.method", "uif");
|
doFacetContains("contains_s1", "contains_group_s1", "Astra", "BAst", "Ast", "facet.method", "uif");
|
||||||
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "uif", "facet.contains", "Ast");
|
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "uif", "facet.contains", "Ast");
|
||||||
|
@ -2063,6 +2103,7 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
|
||||||
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "enum", "facet.contains", "aSt", "facet.contains.ignoreCase", "true");
|
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "enum", "facet.contains", "aSt", "facet.contains.ignoreCase", "true");
|
||||||
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "fcs", "facet.contains", "asT", "facet.contains.ignoreCase", "true");
|
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "fcs", "facet.contains", "asT", "facet.contains.ignoreCase", "true");
|
||||||
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "fc", "facet.contains", "aST", "facet.contains.ignoreCase", "true");
|
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "fc", "facet.contains", "aST", "facet.contains.ignoreCase", "true");
|
||||||
|
doFacetExistsPrefix("contains_s1", null, "Astra", "facet.contains", "Ast");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void indexFacetPrefix(String idPrefix, String f, String termSuffix, String g) {
|
static void indexFacetPrefix(String idPrefix, String f, String termSuffix, String g) {
|
||||||
|
@ -2313,6 +2354,239 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void doFacetExistsPrefix(String f, String local, String termSuffix, String... params) {
|
||||||
|
String indent="on";
|
||||||
|
String pre = "//lst[@name='"+f+"']";
|
||||||
|
String lf = local==null ? f : local+f;
|
||||||
|
|
||||||
|
assertQ("test field facet.method",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent", indent
|
||||||
|
,"facet", "true"
|
||||||
|
,"f."+lf+".facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount", "0"
|
||||||
|
,"facet.offset", "0"
|
||||||
|
,"facet.limit", "100"
|
||||||
|
,"facet.sort", "count"
|
||||||
|
,"facet.prefix", "B"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=3]"
|
||||||
|
,pre+"/int[1][@name='B"+termSuffix+"'][.='1']"
|
||||||
|
,pre+"/int[2][@name='BB"+termSuffix+"'][.='1']"
|
||||||
|
,pre+"/int[3][@name='BBB"+termSuffix+"'][.='1']"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix middle, exact match first term",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","0"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","B"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=3]"
|
||||||
|
,pre+"/int[1][@name='B"+termSuffix+"'][.='1']"
|
||||||
|
,pre+"/int[2][@name='BB"+termSuffix+"'][.='1']"
|
||||||
|
,pre+"/int[3][@name='BBB"+termSuffix+"'][.='1']"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix middle, exact match first term, unsorted",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","0"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","index"
|
||||||
|
,"facet.prefix","B"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=3]"
|
||||||
|
,pre+"/int[1][@name='B"+termSuffix+"'][.='1']"
|
||||||
|
,pre+"/int[2][@name='BB"+termSuffix+"'][.='1']"
|
||||||
|
,pre+"/int[3][@name='BBB"+termSuffix+"'][.='1']"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix middle, paging",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","1"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","B"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=2]"
|
||||||
|
,pre+"/int[1][@name='BB"+termSuffix+"'][.='1']"
|
||||||
|
,pre+"/int[2][@name='BBB"+termSuffix+"'][.='1']"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix middle, paging",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","1"
|
||||||
|
,"facet.limit","1"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","B"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=1]"
|
||||||
|
,pre+"/int[1][@name='BB"+termSuffix+"'][.='1']"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix end, not exact match",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","0"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","C"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=2]"
|
||||||
|
,pre+"/int[1][@name='CC"+termSuffix+"'][.='1']"
|
||||||
|
,pre+"/int[2][@name='CCC"+termSuffix+"'][.='1']"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix end, exact match",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","0"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","CC"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=2]"
|
||||||
|
,pre+"/int[1][@name='CC"+termSuffix+"'][.='1']"
|
||||||
|
,pre+"/int[2][@name='CCC"+termSuffix+"'][.='1']"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix past end",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","0"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","X"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=0]"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix past end",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","1"
|
||||||
|
,"facet.limit","-1"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","X"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=0]"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix at start, exact match",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","0"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","AAA"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=1]"
|
||||||
|
,pre+"/int[1][@name='AAA"+termSuffix+"'][.='1']"
|
||||||
|
);
|
||||||
|
assertQ("test facet.prefix at Start, not exact match",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","0"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","AA"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=1]"
|
||||||
|
,pre+"/int[1][@name='AAA"+termSuffix+"'][.='1']"
|
||||||
|
);
|
||||||
|
assertQ("test facet.prefix before start",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","0"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","999"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=0]"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertQ("test facet.prefix before start",
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","0"
|
||||||
|
,"facet.offset","2"
|
||||||
|
,"facet.limit","100"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","999"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=0]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// test offset beyond what is collected internally in queue
|
||||||
|
assertQ(
|
||||||
|
req(params, "q", "id:[* TO *]"
|
||||||
|
,"indent",indent
|
||||||
|
,"facet","true"
|
||||||
|
,"facet.exists", "true"
|
||||||
|
,"facet.field", lf
|
||||||
|
,"facet.mincount","1"
|
||||||
|
,"facet.offset","5"
|
||||||
|
,"facet.limit","10"
|
||||||
|
,"facet.sort","count"
|
||||||
|
,"facet.prefix","CC"
|
||||||
|
)
|
||||||
|
,"*[count(//lst[@name='facet_fields']/lst/int)=0]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public void doFacetContains(String f, String g, String termSuffix, String contains, String groupContains, String... params) {
|
public void doFacetContains(String f, String g, String termSuffix, String contains, String groupContains, String... params) {
|
||||||
String indent="on";
|
String indent="on";
|
||||||
String pre = "//lst[@name='"+f+"']";
|
String pre = "//lst[@name='"+f+"']";
|
||||||
|
|
|
@ -185,6 +185,14 @@ public interface FacetParams {
|
||||||
* only use the filterCache for terms with a df >= to this parameter.
|
* only use the filterCache for terms with a df >= to this parameter.
|
||||||
*/
|
*/
|
||||||
public static final String FACET_ENUM_CACHE_MINDF = FACET + ".enum.cache.minDf";
|
public static final String FACET_ENUM_CACHE_MINDF = FACET + ".enum.cache.minDf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean parameter that caps the facet counts at 1.
|
||||||
|
* With this set, a returned count will only be 0 or 1.
|
||||||
|
* For apps that don't need the count, this should be an optimization
|
||||||
|
*/
|
||||||
|
public static final String FACET_EXISTS = FACET+".exists";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any field whose terms the user wants to enumerate over for
|
* Any field whose terms the user wants to enumerate over for
|
||||||
* Facet Contraint Counts (multi-value)
|
* Facet Contraint Counts (multi-value)
|
||||||
|
|
Loading…
Reference in New Issue