SOLR-9912: Add facet.excludeTerms parameter support. (Jonny Marks, David Smiley, Christine Poerschke)

This commit is contained in:
Christine Poerschke 2017-02-09 14:55:06 +00:00
parent 55afc2031c
commit 0b817e6e49
4 changed files with 199 additions and 6 deletions

View File

@ -130,6 +130,8 @@ New Features
* SOLR-9997: Enable configuring SolrHttpClientBuilder via java system property. (Hrishikesh Gadre via Mark Miller) * SOLR-9997: Enable configuring SolrHttpClientBuilder via java system property. (Hrishikesh Gadre via Mark Miller)
* SOLR-9912: Add facet.excludeTerms parameter support. (Jonny Marks, David Smiley, Christine Poerschke)
Bug Fixes Bug Fixes
---------------------- ----------------------

View File

@ -21,9 +21,12 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -343,15 +346,46 @@ public class SimpleFacets {
ENUM, FC, FCS, UIF; ENUM, FC, FCS, UIF;
} }
protected Predicate<BytesRef> newBytesRefFilter(String field, SolrParams params) { protected Predicate<BytesRef> newExcludeBytesRefFilter(String field, SolrParams params) {
final String contains = params.getFieldParam(field, FacetParams.FACET_CONTAINS); final String exclude = params.getFieldParam(field, FacetParams.FACET_EXCLUDETERMS);
final boolean ignoreCase = params.getFieldBool(field, FacetParams.FACET_CONTAINS_IGNORE_CASE, false); if (exclude == null) {
if (contains == null) {
return null; return null;
} }
return new SubstringBytesRefFilter(contains, ignoreCase); final Set<String> excludeTerms = new HashSet<>(StrUtils.splitSmart(exclude, ",", true));
return new Predicate<BytesRef>() {
@Override
public boolean test(BytesRef bytesRef) {
return !excludeTerms.contains(bytesRef.utf8ToString());
}
};
}
protected Predicate<BytesRef> newBytesRefFilter(String field, SolrParams params) {
final String contains = params.getFieldParam(field, FacetParams.FACET_CONTAINS);
final Predicate<BytesRef> containsFilter;
if (contains != null) {
final boolean containsIgnoreCase = params.getFieldBool(field, FacetParams.FACET_CONTAINS_IGNORE_CASE, false);
containsFilter = new SubstringBytesRefFilter(contains, containsIgnoreCase);
} else {
containsFilter = null;
}
final Predicate<BytesRef> excludeFilter = newExcludeBytesRefFilter(field, params);
if (containsFilter == null && excludeFilter == null) {
return null;
}
if (containsFilter != null && excludeFilter == null) {
return containsFilter;
} else if (containsFilter == null && excludeFilter != null) {
return excludeFilter;
}
return containsFilter.and(excludeFilter);
} }
/** /**

View File

@ -2081,6 +2081,158 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
doFacetPrefix("tt_s1", "{!threads=-1}", "", "facet.method","fcs"); // default / unlimited threads doFacetPrefix("tt_s1", "{!threads=-1}", "", "facet.method","fcs"); // default / unlimited threads
doFacetPrefix("tt_s1", "{!threads=2}", "", "facet.method","fcs"); // specific number of threads doFacetPrefix("tt_s1", "{!threads=2}", "", "facet.method","fcs"); // specific number of threads
} }
@Test
public void testFacetExclude() {
for (String method : new String[] {"enum", "fcs", "fc", "uif"}) {
doFacetExclude("contains_s1", "contains_group_s1", "Astra", "facet.method", method);
}
}
private void doFacetExclude(String f, String g, String termSuffix, String... params) {
String indent="on";
String pre = "//lst[@name='"+f+"']";
final SolrQueryRequest req = req(params, "q", "id:[* TO *]"
,"indent",indent
,"facet","true"
,"facet.field", f
,"facet.mincount","0"
,"facet.offset","0"
,"facet.limit","100"
,"facet.sort","count"
,"facet.excludeTerms","B,BBB"+termSuffix
);
assertQ("test facet.exclude",
req
,"*[count(//lst[@name='facet_fields']/lst/int)=10]"
,pre+"/int[1][@name='BBB'][.='3']"
,pre+"/int[2][@name='CCC'][.='3']"
,pre+"/int[3][@name='CCC"+termSuffix+"'][.='3']"
,pre+"/int[4][@name='BB'][.='2']"
,pre+"/int[5][@name='BB"+termSuffix+"'][.='2']"
,pre+"/int[6][@name='CC'][.='2']"
,pre+"/int[7][@name='CC"+termSuffix+"'][.='2']"
,pre+"/int[8][@name='AAA'][.='1']"
,pre+"/int[9][@name='AAA"+termSuffix+"'][.='1']"
,pre+"/int[10][@name='B"+termSuffix+"'][.='1']"
);
final SolrQueryRequest groupReq = req(params, "q", "id:[* TO *]"
,"indent",indent
,"facet","true"
,"facet.field", f
,"facet.mincount","0"
,"facet.offset","0"
,"facet.limit","100"
,"facet.sort","count"
,"facet.excludeTerms","B,BBB"+termSuffix
,"group","true"
,"group.field",g
,"group.facet","true"
);
assertQ("test facet.exclude for grouped facets",
groupReq
,"*[count(//lst[@name='facet_fields']/lst/int)=10]"
,pre+"/int[1][@name='CCC'][.='3']"
,pre+"/int[2][@name='CCC"+termSuffix+"'][.='3']"
,pre+"/int[3][@name='BBB'][.='2']"
,pre+"/int[4][@name='AAA'][.='1']"
,pre+"/int[5][@name='AAA"+termSuffix+"'][.='1']"
,pre+"/int[6][@name='B"+termSuffix+"'][.='1']"
,pre+"/int[7][@name='BB'][.='1']"
,pre+"/int[8][@name='BB"+termSuffix+"'][.='1']"
,pre+"/int[9][@name='CC'][.='1']"
,pre+"/int[10][@name='CC"+termSuffix+"'][.='1']"
);
}
@Test
public void testFacetContainsAndExclude() {
for (String method : new String[] {"enum", "fcs", "fc", "uif"}) {
String contains = "BAst";
String groupContains = "Ast";
final boolean ignoreCase = random().nextBoolean();
if (ignoreCase) {
contains = randomizeStringCasing(contains);
groupContains = randomizeStringCasing(groupContains);
doFacetContainsAndExclude("contains_s1", "contains_group_s1", "Astra", contains, groupContains, "facet.method", method, "facet.contains.ignoreCase", "true");
} else {
doFacetContainsAndExclude("contains_s1", "contains_group_s1", "Astra", contains, groupContains, "facet.method", method);
}
}
}
private String randomizeStringCasing(String str) {
final char[] characters = str.toCharArray();
for (int i = 0; i != characters.length; ++i) {
final boolean switchCase = random().nextBoolean();
if (!switchCase) {
continue;
}
final char c = str.charAt(i);
if (Character.isUpperCase(c)) {
characters[i] = Character.toLowerCase(c);
} else {
characters[i] = Character.toUpperCase(c);
}
}
return new String(characters);
}
private void doFacetContainsAndExclude(String f, String g, String termSuffix, String contains, String groupContains, String... params) {
String indent="on";
String pre = "//lst[@name='"+f+"']";
final SolrQueryRequest req = req(params, "q", "id:[* TO *]"
,"indent",indent
,"facet","true"
,"facet.field", f
,"facet.mincount","0"
,"facet.offset","0"
,"facet.limit","100"
,"facet.sort","count"
,"facet.contains",contains
,"facet.excludeTerms","BBB"+termSuffix
);
assertQ("test facet.contains with facet.exclude",
req
,"*[count(//lst[@name='facet_fields']/lst/int)=2]"
,pre+"/int[1][@name='BB"+termSuffix+"'][.='2']"
,pre+"/int[2][@name='B"+termSuffix+"'][.='1']"
);
final SolrQueryRequest groupReq = req(params, "q", "id:[* TO *]"
,"indent",indent
,"facet","true"
,"facet.field", f
,"facet.mincount","0"
,"facet.offset","0"
,"facet.limit","100"
,"facet.sort","count"
,"facet.contains",groupContains
,"facet.excludeTerms","AAA"+termSuffix
,"group","true"
,"group.field",g
,"group.facet","true"
);
assertQ("test facet.contains with facet.exclude for grouped facets",
groupReq
,"*[count(//lst[@name='facet_fields']/lst/int)=5]"
,pre+"/int[1][@name='CCC"+termSuffix+"'][.='3']"
,pre+"/int[2][@name='BBB"+termSuffix+"'][.='2']"
,pre+"/int[3][@name='B"+termSuffix+"'][.='1']"
,pre+"/int[4][@name='BB"+termSuffix+"'][.='1']"
,pre+"/int[5][@name='CC"+termSuffix+"'][.='1']"
);
}
@Test @Test
//@Ignore("SOLR-8466 - facet.method=uif ignores facet.contains") //@Ignore("SOLR-8466 - facet.method=uif ignores facet.contains")

View File

@ -180,6 +180,11 @@ public interface FacetParams {
*/ */
public static final String FACET_CONTAINS_IGNORE_CASE = FACET_CONTAINS + ".ignoreCase"; public static final String FACET_CONTAINS_IGNORE_CASE = FACET_CONTAINS + ".ignoreCase";
/**
* Only return constraints of a facet field excluding the given string.
*/
public static final String FACET_EXCLUDETERMS = FACET + ".excludeTerms";
/** /**
* When faceting by enumerating the terms in a field, * When faceting by enumerating the terms in a field,
* only use the filterCache for terms with a df &gt;= to this parameter. * only use the filterCache for terms with a df &gt;= to this parameter.