diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index f81ff75459f..50639ab6dfc 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -79,6 +79,13 @@ Jetty 9.3.8.v20160314 Detailed Change List ---------------------- +New Features +---------------------- +* SOLR-9681: FacetModule / JSON Facet API added the ability to add filters directly to + any facet command. The filters are applied after any domain change operations. + Example: { type:terms, field:category, filter:"user:yonik" } + (yonik) + Other Changes ---------------------- diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetProcessor.java b/solr/core/src/java/org/apache/solr/search/facet/FacetProcessor.java index fa26319e812..e6104782c08 100644 --- a/solr/core/src/java/org/apache/solr/search/facet/FacetProcessor.java +++ b/solr/core/src/java/org/apache/solr/search/facet/FacetProcessor.java @@ -50,11 +50,13 @@ public abstract class FacetProcessor { FacetContext fcontext; FacetRequestT freq; + DocSet filter; // additional filters specified by "filter" // TODO: do these need to be on the context to support recomputing during multi-select? LinkedHashMap accMap; SlotAcc[] accs; CountSlotAcc countAcc; - /** factory method for invoking json facet framework as whole */ + /** factory method for invoking json facet framework as whole. + * Note: this is currently only used from SimpleFacets, not from JSON Facet API itself. */ public static FacetProcessor createProcessor(SolrQueryRequest req, Map params, DocSet docs){ FacetParser parser = new FacetTopParser(req); @@ -84,7 +86,38 @@ public abstract class FacetProcessor { } public void process() throws IOException { + // Check filters... if we do have filters they apply after domain changes. + // We still calculate them first because we can use it in a parent->child domain change. + handleFilters(); handleDomainChanges(); + if (filter != null) { + fcontext.base = fcontext.base.intersection( filter ); + } + } + + private void handleFilters() throws IOException { + if (freq.filters == null || freq.filters.isEmpty()) return; + + List qlist = new ArrayList<>(freq.filters.size()); + // TODO: prevent parsing filters each time! + for (Object rawFilter : freq.filters) { + Query symbolicFilter; + if (rawFilter instanceof String) { + QParser parser = null; + try { + parser = QParser.getParser((String)rawFilter, fcontext.req); + symbolicFilter = parser.getQuery(); + } catch (SyntaxError syntaxError) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, syntaxError); + } + } else { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Bad query (expected a string):" + rawFilter); + } + + qlist.add(symbolicFilter); + } + + this.filter = fcontext.searcher.getDocSet(qlist); } private void handleDomainChanges() throws IOException { diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java b/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java index 40ca686482a..d3c8722ed6c 100644 --- a/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java +++ b/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java @@ -16,6 +16,7 @@ */ package org.apache.solr.search.facet; +import java.util.ArrayList; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; @@ -77,7 +78,7 @@ public abstract class FacetRequest { protected Map facetStats; // per-bucket statistics protected Map subFacets; // per-bucket sub-facets - protected List filters; + protected List filters; protected boolean processEmpty; protected Domain domain; @@ -376,6 +377,16 @@ abstract class FacetParser { } + Object filterOrList = m.get("filter"); + if (filterOrList != null) { + if (filterOrList instanceof List) { + facet.filters = (List)filterOrList; + } else { + facet.filters = new ArrayList<>(1); + facet.filters.add(filterOrList); + } + } + } } diff --git a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java index 5527a3da16d..eafa1340513 100644 --- a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java +++ b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java @@ -1164,6 +1164,27 @@ public class TestJsonFacets extends SolrTestCaseHS { ); + // test filter + client.testJQ(params(p, "q", "*:*", "myfilt","${cat_s}:A" + , "json.facet", "{" + + "t:{${terms} type:terms, field:${cat_s}, filter:[]}" + // empty filter list + ",t_filt:{${terms} type:terms, field:${cat_s}, filter:'${cat_s}:B'}" + + ",t_filt2:{${terms} type:terms, field:${cat_s}, filter:'{!query v=$myfilt}'}" + // test access to qparser and other query parameters + ",t_filt3:{${terms} type:terms, field:${cat_s}, filter:['-id:1','-id:2']}" + + ",q:{type:query, q:'${cat_s}:B', filter:['-id:5']}" + // also tests a top-level negative filter + ",r:{type:range, field:${num_d}, start:-5, end:10, gap:5, filter:'-id:4'}" + + "}" + ) + , "facets=={ count:6, " + + "t :{ buckets:[ {val:B, count:3}, {val:A, count:2} ] }" + + ",t_filt :{ buckets:[ {val:B, count:3}] } " + + ",t_filt2:{ buckets:[ {val:A, count:2}] } " + + ",t_filt3:{ buckets:[ {val:B, count:2}, {val:A, count:1}] } " + + ",q:{count:2}" + + ",r:{buckets:[ {val:-5.0,count:1}, {val:0.0,count:1}, {val:5.0,count:0} ] }" + + "}" + ); + } @Test