/* * 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.request; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.params.FacetParams; import org.apache.solr.common.params.FacetParams.FacetRangeInclude; import org.apache.solr.common.params.FacetParams.FacetRangeMethod; import org.apache.solr.common.params.FacetParams.FacetRangeOther; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.schema.SchemaField; import org.apache.solr.util.TimeZoneUtils; import org.junit.BeforeClass; import org.junit.Test; import org.noggit.ObjectBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SimpleFacetsTest extends SolrTestCaseJ4 { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @BeforeClass public static void beforeClass() throws Exception { initCore("solrconfig.xml","schema.xml"); createIndex(); } static int random_commit_percent = 30; static int random_dupe_percent = 25; // some duplicates in the index to create deleted docs static void randomCommit(int percent_chance) { if (random().nextInt(100) <= percent_chance) assertU(commit()); } static ArrayList<String[]> pendingDocs = new ArrayList<>(); // committing randomly gives different looking segments each time static void add_doc(String... fieldsAndValues) { do { //do our own copy-field: List<String> fieldsAndValuesList = new ArrayList<>(Arrays.asList(fieldsAndValues)); int idx = fieldsAndValuesList.indexOf("a_tdt"); if (idx >= 0) { fieldsAndValuesList.add("a_drf"); fieldsAndValuesList.add(fieldsAndValuesList.get(idx + 1));//copy } idx = fieldsAndValuesList.indexOf("bday"); if (idx >= 0) { fieldsAndValuesList.add("bday_drf"); fieldsAndValuesList.add(fieldsAndValuesList.get(idx + 1));//copy } fieldsAndValues = fieldsAndValuesList.toArray(new String[fieldsAndValuesList.size()]); pendingDocs.add(fieldsAndValues); } while (random().nextInt(100) <= random_dupe_percent); // assertU(adoc(fieldsAndValues)); // randomCommit(random_commit_percent); } static void createIndex() throws Exception { doEmptyFacetCounts(); // try on empty index indexSimpleFacetCounts(); indexDateFacets(); indexFacetSingleValued(); indexFacetPrefixMultiValued(); indexFacetPrefixSingleValued(); indexFacetContains(); indexSimpleGroupedFacetCounts(); Collections.shuffle(pendingDocs, random()); for (String[] doc : pendingDocs) { assertU(adoc(doc)); randomCommit(random_commit_percent); } assertU(commit()); } static void indexSimpleFacetCounts() { add_doc("id", "42", "range_facet_f", "35.3", "range_facet_f1", "35.3", "trait_s", "Tool", "trait_s", "Obnoxious", "name", "Zapp Brannigan", "foo_s","A", "foo_s","B", "range_facet_mv_f", "1.0", "range_facet_mv_f", "2.5", "range_facet_mv_f", "3.7", "range_facet_mv_f", "3.3" ); add_doc("id", "43" , "range_facet_f", "28.789", "range_facet_f1", "28.789", "title", "Democratic Order of Planets", "foo_s","A", "foo_s","B", "range_facet_mv_f", "3.0", "range_facet_mv_f", "7.5", "range_facet_mv_f", "12.0" ); add_doc("id", "44", "range_facet_f", "15.97", "range_facet_f1", "15.97", "trait_s", "Tool", "name", "The Zapper", "foo_s","A", "foo_s","B", "foo_s","C", "range_facet_mv_f", "0.0", "range_facet_mv_f", "5", "range_facet_mv_f", "74" ); add_doc("id", "45", "range_facet_f", "30.0", "range_facet_f1", "30.0", "trait_s", "Chauvinist", "title", "25 star General", "foo_s","A", "foo_s","B", "range_facet_mv_f_f", "12.0", "range_facet_mv_f", "212.452", "range_facet_mv_f", "32.77", "range_facet_mv_f", "0.123" ); add_doc("id", "46", "range_facet_f", "20.0", "range_facet_f1", "20.0", "trait_s", "Obnoxious", "subject", "Defeated the pacifists of the Gandhi nebula", "foo_s","A", "foo_s","B", "range_facet_mv_f", "123.0", "range_facet_mv_f", "2.0", "range_facet_mv_f", "7.3", "range_facet_mv_f", "0.123" ); add_doc("id", "47", "range_facet_f", "28.62", "range_facet_f1", "28.62", "trait_s", "Pig", "text", "line up and fly directly at the enemy death cannons, clogging them with wreckage!", "zerolen_s","", "foo_s","A", "foo_s","B", "foo_s","C" ); add_doc("id", "101", "myfield_s", "foo"); add_doc("id", "102", "myfield_s", "bar"); } static void indexSimpleGroupedFacetCounts() { add_doc("id", "2000", "hotel_s1", "a", "airport_s1", "ams", "duration_i1", "5"); add_doc("id", "2001", "hotel_s1", "a", "airport_s1", "dus", "duration_i1", "10"); add_doc("id", "2002", "hotel_s1", "b", "airport_s1", "ams", "duration_i1", "10"); add_doc("id", "2003", "hotel_s1", "b", "airport_s1", "ams", "duration_i1", "5"); add_doc("id", "2004", "hotel_s1", "b", "airport_s1", "ams", "duration_i1", "5"); } public void testDefaultsAndAppends() throws Exception { // all defaults assertQ( req("indent","true", "q","*:*", "rows","0", "facet","true", "qt","/search-facet-def") // only one default facet.field ,"//lst[@name='facet_fields']/lst[@name='foo_s']" ,"count(//lst[@name='facet_fields']/lst[@name='foo_s'])=1" ,"count(//lst[@name='facet_fields']/lst)=1" // only one default facet.query ,"//lst[@name='facet_queries']/int[@name='foo_s:bar']" ,"count(//lst[@name='facet_queries']/int[@name='foo_s:bar'])=1" ,"count(//lst[@name='facet_queries']/int)=1" ); // override default & pre-pend to appends assertQ( req("indent","true", "q","*:*", "rows","0", "facet","true", "qt","/search-facet-def", "facet.field", "bar_s", "facet.query", "bar_s:yak" ) // override single default facet.field ,"//lst[@name='facet_fields']/lst[@name='bar_s']" ,"count(//lst[@name='facet_fields']/lst[@name='bar_s'])=1" ,"count(//lst[@name='facet_fields']/lst)=1" // add an additional facet.query ,"//lst[@name='facet_queries']/int[@name='foo_s:bar']" ,"//lst[@name='facet_queries']/int[@name='bar_s:yak']" ,"count(//lst[@name='facet_queries']/int[@name='foo_s:bar'])=1" ,"count(//lst[@name='facet_queries']/int[@name='bar_s:yak'])=1" ,"count(//lst[@name='facet_queries']/int)=2" ); } public void testInvariants() throws Exception { // no matter if we try to use facet.field or facet.query, results shouldn't change for (String ff : new String[] { "facet.field", "bogus" }) { for (String fq : new String[] { "facet.query", "bogus" }) { assertQ( req("indent","true", "q", "*:*", "rows","0", "facet","true", "qt","/search-facet-invariants", ff, "bar_s", fq, "bar_s:yak") // only one invariant facet.field ,"//lst[@name='facet_fields']/lst[@name='foo_s']" ,"count(//lst[@name='facet_fields']/lst[@name='foo_s'])=1" ,"count(//lst[@name='facet_fields']/lst)=1" // only one invariant facet.query ,"//lst[@name='facet_queries']/int[@name='foo_s:bar']" ,"count(//lst[@name='facet_queries']/int[@name='foo_s:bar'])=1" ,"count(//lst[@name='facet_queries']/int)=1" ); } } } @Test public void testCachingBigTerms() throws Exception { assertQ( req("indent","true", "q", "id:[42 TO 47]", "facet", "true", "facet.field", "foo_s" // big terms should cause foo_s:A to be cached ), "*[count(//doc)=6]" ); // now use the cached term as a filter to make sure deleted docs are accounted for assertQ( req("indent","true", "fl","id", "q", "foo_s:B", "facet", "true", "facet.field", "foo_s", "fq","foo_s:A" ), "*[count(//doc)=6]" ); } @Test public void testSimpleGroupedQueryRangeFacets() throws Exception { // for the purposes of our test data, it shouldn't matter // if we use facet.limit -100, -1, or 100 ... // our set of values is small enough either way testSimpleGroupedQueryRangeFacets("-100"); testSimpleGroupedQueryRangeFacets("-1"); testSimpleGroupedQueryRangeFacets("100"); } private void testSimpleGroupedQueryRangeFacets(String facetLimit) { assertQ( req( "q", "*:*", "fq", "id:[2000 TO 2004]", "group", "true", "group.facet", "true", "group.field", "hotel_s1", "facet", "true", "facet.limit", facetLimit, "facet.query", "airport_s1:ams" ), "//lst[@name='facet_queries']/int[@name='airport_s1:ams'][.='2']" ); /* Testing facet.query using tagged filter query and exclusion */ assertQ( req( "q", "*:*", "fq", "id:[2000 TO 2004]", "fq", "{!tag=dus}airport_s1:dus", "group", "true", "group.facet", "true", "group.field", "hotel_s1", "facet", "true", "facet.limit", facetLimit, "facet.query", "{!ex=dus}airport_s1:ams" ), "//lst[@name='facet_queries']/int[@name='{!ex=dus}airport_s1:ams'][.='2']" ); assertQ( req( "q", "*:*", "fq", "id:[2000 TO 2004]", "group", "true", "group.facet", "true", "group.field", "hotel_s1", "facet", "true", "facet.limit", facetLimit, "facet.range", "duration_i1", "facet.range.start", "5", "facet.range.end", "11", "facet.range.gap", "1" ), "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='5'][.='2']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='6'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='7'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='8'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='9'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='10'][.='2']" ); /* Testing facet.range using tagged filter query and exclusion */ assertQ( req( "q", "*:*", "fq", "id:[2000 TO 2004]", "fq", "{!tag=dus}airport_s1:dus", "group", "true", "group.facet", "true", "group.field", "hotel_s1", "facet", "true", "facet.limit", facetLimit, "facet.range", "{!ex=dus}duration_i1", "facet.range.start", "5", "facet.range.end", "11", "facet.range.gap", "1" ), "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='5'][.='2']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='6'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='7'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='8'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='9'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='10'][.='2']" ); // repeat the same query using DV method. This is not supported and the query should use filter method instead assertQ( req( "q", "*:*", "fq", "id:[2000 TO 2004]", "fq", "{!tag=dus}airport_s1:dus", "group", "true", "group.facet", "true", "group.field", "hotel_s1", "facet", "true", "facet.limit", facetLimit, "facet.range", "{!ex=dus}duration_i1", "facet.range.start", "5", "facet.range.end", "11", "facet.range.gap", "1", "facet.range.method", FacetRangeMethod.DV.toString() ), "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='5'][.='2']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='6'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='7'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='8'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='9'][.='0']", "//lst[@name='facet_ranges']/lst[@name='duration_i1']/lst[@name='counts']/int[@name='10'][.='2']" ); } @Test public void testSimpleGroupedFacets() throws Exception { // for the purposes of our test data, it shouldn't matter // if we use facet.limit -100, -1, or 100 ... // our set of values is small enough either way testSimpleGroupedFacets("100"); testSimpleGroupedFacets("-100"); testSimpleGroupedFacets("-5"); testSimpleGroupedFacets("-1"); } private void testSimpleGroupedFacets(String facetLimit) throws Exception { assertQ( "Return 5 docs with id range 1937 till 1940", req("id:[2000 TO 2004]"), "*[count(//doc)=5]" ); assertQ( "Return two facet counts for field airport_a and duration_i1", req( "q", "*:*", "fq", "id:[2000 TO 2004]", "group", "true", "group.facet", "true", "group.field", "hotel_s1", "facet", "true", "facet.limit", facetLimit, "facet.field", "airport_s1", "facet.field", "duration_i1" ), "//lst[@name='facet_fields']/lst[@name='airport_s1']", "*[count(//lst[@name='airport_s1']/int)=2]", "//lst[@name='airport_s1']/int[@name='ams'][.='2']", "//lst[@name='airport_s1']/int[@name='dus'][.='1']", "//lst[@name='facet_fields']/lst[@name='duration_i1']", "*[count(//lst[@name='duration_i1']/int)=2]", "//lst[@name='duration_i1']/int[@name='5'][.='2']", "//lst[@name='duration_i1']/int[@name='10'][.='2']" ); assertQ( "Return one facet count for field airport_a using facet.offset", req( "q", "*:*", "fq", "id:[2000 TO 2004]", "group", "true", "group.facet", "true", "group.field", "hotel_s1", "facet", "true", "facet.offset", "1", "facet.limit", facetLimit, "facet.field", "airport_s1" ), "//lst[@name='facet_fields']/lst[@name='airport_s1']", "*[count(//lst[@name='airport_s1']/int)=1]", "//lst[@name='airport_s1']/int[@name='dus'][.='1']" ); assertQ( "Return two facet counts for field airport_a with fq", req( "q", "*:*", "fq", "id:[2000 TO 2004]", "fq", "duration_i1:5", "group", "true", "group.facet", "true", "group.field", "hotel_s1", "facet", "true", "facet.limit", facetLimit, "facet.field", "airport_s1" ), "//lst[@name='facet_fields']/lst[@name='airport_s1']", "*[count(//lst[@name='airport_s1']/int)=2]", "//lst[@name='airport_s1']/int[@name='ams'][.='2']", "//lst[@name='airport_s1']/int[@name='dus'][.='0']" ); assertQ( "Return one facet count for field airport_s1 with prefix a", req( "q", "*:*", "fq", "id:[2000 TO 2004]", "group", "true", "group.facet", "true", "group.field", "hotel_s1", "facet", "true", "facet.field", "airport_s1", "facet.limit", facetLimit, "facet.prefix", "a" ), "//lst[@name='facet_fields']/lst[@name='airport_s1']", "*[count(//lst[@name='airport_s1']/int)=1]", "//lst[@name='airport_s1']/int[@name='ams'][.='2']" ); try { h.query( req( "q", "*:*", "fq", "id:[2000 TO 2004]", "group.facet", "true", "facet", "true", "facet.field", "airport_s1", "facet.prefix", "a" ) ); fail("Exception should have been thrown"); } catch (SolrException e) { assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code()); } } @Test public void testEmptyFacetCounts() throws Exception { doEmptyFacetCounts(); } // static so we can try both with and without an empty index static void doEmptyFacetCounts() throws Exception { doEmptyFacetCounts("empty_t", new String[]{null, "myprefix",""}); doEmptyFacetCounts("empty_i", new String[]{null}); doEmptyFacetCounts("empty_f", new String[]{null}); doEmptyFacetCounts("empty_s", new String[]{null, "myprefix",""}); doEmptyFacetCounts("empty_d", new String[]{null}); } static void doEmptyFacetCounts(String field, String[] prefixes) throws Exception { SchemaField sf = h.getCore().getLatestSchema().getField(field); String response = JQ(req("q", "*:*")); Map rsp = (Map) ObjectBuilder.fromJSON(response); Long numFound = (Long)(((Map)rsp.get("response")).get("numFound")); ModifiableSolrParams params = params("q","*:*", "rows","0", "facet","true", "facet.field","{!key=myalias}"+field); String[] methods = {null, "fc","enum","fcs", "uif"}; if (sf.multiValued() || sf.getType().multiValuedFieldCache()) { methods = new String[]{null, "fc","enum", "uif"}; } prefixes = prefixes==null ? new String[]{null} : prefixes; for (String method : methods) { if (method == null) { params.remove("facet.method"); } else { params.set("facet.method", method); } for (String prefix : prefixes) { if (prefix == null) { params.remove("facet.prefix"); } else { params.set("facet.prefix", prefix); } for (String missing : new String[] {null, "true"}) { if (missing == null) { params.remove("facet.missing"); } else { params.set("facet.missing", missing); } String expected = missing==null ? "[]" : "[null," + numFound + "]"; assertJQ(req(params), "/facet_counts/facet_fields/myalias==" + expected); } } } } @Test public void testSimpleFacetCounts() { assertQ("standard request handler returns all matches", req("id:[42 TO 47]"), "*[count(//doc)=6]" ); assertQ("filter results using fq", req("q","id:[42 TO 46]", "fq", "id:[43 TO 47]"), "*[count(//doc)=4]" ); assertQ("don't filter results using blank fq", req("q","id:[42 TO 46]", "fq", " "), "*[count(//doc)=5]" ); assertQ("filter results using multiple fq params", req("q","id:[42 TO 46]", "fq", "trait_s:Obnoxious", "fq", "id:[43 TO 47]"), "*[count(//doc)=1]" ); final String[] uifSwitch = new String[]{(random().nextBoolean() ? "":"f.trait_s.")+"facet.method", "uif"}; final String[] none = new String[]{}; for(String[] methodParam : new String[][]{ none, uifSwitch}){ assertQ("check counts for facet queries", req(methodParam ,"q", "id:[42 TO 47]" ,"facet", "true" ,"facet.query", "trait_s:Obnoxious" ,"facet.query", "id:[42 TO 45]" ,"facet.query", "id:[43 TO 47]" ,"facet.field", "trait_s" ) ,"*[count(//doc)=6]" ,"//lst[@name='facet_counts']/lst[@name='facet_queries']" ,"//lst[@name='facet_queries']/int[@name='trait_s:Obnoxious'][.='2']" ,"//lst[@name='facet_queries']/int[@name='id:[42 TO 45]'][.='4']" ,"//lst[@name='facet_queries']/int[@name='id:[43 TO 47]'][.='5']" ,"//lst[@name='facet_counts']/lst[@name='facet_fields']" ,"//lst[@name='facet_fields']/lst[@name='trait_s']" ,"*[count(//lst[@name='trait_s']/int)=4]" ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']" ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='2']" ,"//lst[@name='trait_s']/int[@name='Pig'][.='1']" ); assertQ("check multi-select facets with naming", req(methodParam, "q", "id:[42 TO 47]" ,"facet", "true" ,"facet.query", "{!ex=1}trait_s:Obnoxious" ,"facet.query", "{!ex=2 key=foo}id:[42 TO 45]" // tag=2 same as 1 ,"facet.query", "{!ex=3,4 key=bar}id:[43 TO 47]" // tag=3,4 don't exist ,"facet.field", "{!ex=3,1}trait_s" // 3,1 same as 1 ,"fq", "{!tag=1,2}id:47" // tagged as 1 and 2 ) ,"*[count(//doc)=1]" ,"//lst[@name='facet_counts']/lst[@name='facet_queries']" ,"//lst[@name='facet_queries']/int[@name='{!ex=1}trait_s:Obnoxious'][.='2']" ,"//lst[@name='facet_queries']/int[@name='foo'][.='4']" ,"//lst[@name='facet_queries']/int[@name='bar'][.='1']" ,"//lst[@name='facet_counts']/lst[@name='facet_fields']" ,"//lst[@name='facet_fields']/lst[@name='trait_s']" ,"*[count(//lst[@name='trait_s']/int)=4]" ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']" ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='2']" ,"//lst[@name='trait_s']/int[@name='Pig'][.='1']" ); } // test excluding main query assertQ(req("q", "{!tag=main}id:43" ,"facet", "true" ,"facet.query", "{!key=foo}id:42" ,"facet.query", "{!ex=main key=bar}id:42" // only matches when we exclude main query ) ,"//lst[@name='facet_queries']/int[@name='foo'][.='0']" ,"//lst[@name='facet_queries']/int[@name='bar'][.='1']" ); for(String[] methodParam : new String[][]{ none, uifSwitch}){ assertQ("check counts for applied facet queries using filtering (fq)", req(methodParam ,"q", "id:[42 TO 47]" ,"facet", "true" ,"fq", "id:[42 TO 45]" ,"facet.field", "trait_s" ,"facet.query", "id:[42 TO 45]" ,"facet.query", "id:[43 TO 47]" ) ,"*[count(//doc)=4]" ,"//lst[@name='facet_counts']/lst[@name='facet_queries']" ,"//lst[@name='facet_queries']/int[@name='id:[42 TO 45]'][.='4']" ,"//lst[@name='facet_queries']/int[@name='id:[43 TO 47]'][.='3']" ,"*[count(//lst[@name='trait_s']/int)=4]" ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']" ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='1']" ,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']" ,"//lst[@name='trait_s']/int[@name='Pig'][.='0']" ); assertQ("check counts with facet.zero=false&facet.missing=true using fq", req(methodParam ,"q", "id:[42 TO 47]" ,"facet", "true" ,"facet.zeros", "false" ,"f.trait_s.facet.missing", "true" ,"fq", "id:[42 TO 45]" ,"facet.field", "trait_s" ) ,"*[count(//doc)=4]" ,"*[count(//lst[@name='trait_s']/int)=4]" ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']" ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='1']" ,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']" ,"//lst[@name='trait_s']/int[not(@name)][.='1']" ); assertQ("check counts with facet.mincount=1&facet.missing=true using fq", req(methodParam ,"q", "id:[42 TO 47]" ,"facet", "true" ,"facet.mincount", "1" ,"f.trait_s.facet.missing", "true" ,"fq", "id:[42 TO 45]" ,"facet.field", "trait_s" ) ,"*[count(//doc)=4]" ,"*[count(//lst[@name='trait_s']/int)=4]" ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']" ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='1']" ,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']" ,"//lst[@name='trait_s']/int[not(@name)][.='1']" ); assertQ("check counts with facet.mincount=2&facet.missing=true using fq", req(methodParam ,"q", "id:[42 TO 47]" ,"facet", "true" ,"facet.mincount", "2" ,"f.trait_s.facet.missing", "true" ,"fq", "id:[42 TO 45]" ,"facet.field", "trait_s" ) ,"*[count(//doc)=4]" ,"*[count(//lst[@name='trait_s']/int)=2]" ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']" ,"//lst[@name='trait_s']/int[not(@name)][.='1']" ); assertQ("check sorted paging", req(methodParam ,"q", "id:[42 TO 47]" ,"facet", "true" ,"fq", "id:[42 TO 45]" ,"facet.field", "trait_s" ,"facet.mincount","0" ,"facet.offset","0" ,"facet.limit","4" ) ,"*[count(//lst[@name='trait_s']/int)=4]" ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']" ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='1']" ,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']" ,"//lst[@name='trait_s']/int[@name='Pig'][.='0']" ); // check that the default sort is by count assertQ("check sorted paging", req(methodParam, "q", "id:[42 TO 47]" ,"facet", "true" ,"fq", "id:[42 TO 45]" ,"facet.field", "trait_s" ,"facet.mincount","0" ,"facet.offset","0" ,"facet.limit","3" ) ,"*[count(//lst[@name='trait_s']/int)=3]" ,"//int[1][@name='Tool'][.='2']" ,"//int[2][@name='Chauvinist'][.='1']" ,"//int[3][@name='Obnoxious'][.='1']" ); // // check that legacy facet.sort=true/false works // assertQ(req(methodParam, "q", "id:[42 TO 47]" ,"facet", "true" ,"fq", "id:[42 TO 45]" ,"facet.field", "trait_s" ,"facet.mincount","0" ,"facet.offset","0" ,"facet.limit","3" ,"facet.sort","true" // true means sort-by-count ) ,"*[count(//lst[@name='trait_s']/int)=3]" ,"//int[1][@name='Tool'][.='2']" ,"//int[2][@name='Chauvinist'][.='1']" ,"//int[3][@name='Obnoxious'][.='1']" ); assertQ(req(methodParam, "q", "id:[42 TO 47]" ,"facet", "true" ,"fq", "id:[42 TO 45]" ,"facet.field", "trait_s" ,"facet.mincount","1" ,"facet.offset","0" ,"facet.limit","3" ,"facet.sort","false" // false means sort by index order ) ,"*[count(//lst[@name='trait_s']/int)=3]" ,"//int[1][@name='Chauvinist'][.='1']" ,"//int[2][@name='Obnoxious'][.='1']" ,"//int[3][@name='Tool'][.='2']" ); } for(String method : new String[]{ "fc","uif"}){ assertQ(req("q", "id:[42 TO 47]" ,"facet", "true" ,"fq", "id:[42 TO 45]" ,"facet.field", "zerolen_s" ,(random().nextBoolean() ? "":"f.zerolen_s.")+"facet.method", method ) ,"*[count(//lst[@name='zerolen_s']/int[@name=''])=1]" ); } assertQ("a facet.query that analyzes to no query shoud not NPE", req("q", "*:*", "facet", "true", "facet.query", "{!field key=k f=lengthfilt}a"),//2 char minimum "//lst[@name='facet_queries']/int[@name='k'][.='0']" ); } public static void indexDateFacets() { final String i = "id"; final String f = "bday"; final String ff = "a_tdt"; final String ooo = "00:00:00.000Z"; final String xxx = "15:15:15.155Z"; //note: add_doc duplicates bday to bday_drf and a_tdt to a_drf (date range field) add_doc(i, "201", f, "1976-07-04T12:08:56.235Z", ff, "1900-01-01T"+ooo); add_doc(i, "202", f, "1976-07-05T00:00:00.000Z", ff, "1976-07-01T"+ooo); add_doc(i, "203", f, "1976-07-15T00:07:57.890Z", ff, "1976-07-04T"+ooo); add_doc(i, "204", f, "1976-07-21T00:07:57.890Z", ff, "1976-07-05T"+ooo); add_doc(i, "205", f, "1976-07-13T12:12:25.255Z", ff, "1976-07-05T"+xxx); add_doc(i, "206", f, "1976-07-03T17:01:23.456Z", ff, "1976-07-07T"+ooo); add_doc(i, "207", f, "1976-07-12T12:12:25.255Z", ff, "1976-07-13T"+ooo); add_doc(i, "208", f, "1976-07-15T15:15:15.155Z", ff, "1976-07-13T"+xxx); add_doc(i, "209", f, "1907-07-12T13:13:23.235Z", ff, "1976-07-15T"+xxx); add_doc(i, "2010", f, "1976-07-03T11:02:45.678Z", ff, "2000-01-01T"+ooo); add_doc(i, "2011", f, "1907-07-12T12:12:25.255Z"); add_doc(i, "2012", f, "2007-07-30T07:07:07.070Z"); add_doc(i, "2013", f, "1976-07-30T22:22:22.222Z"); add_doc(i, "2014", f, "1976-07-05T22:22:22.222Z"); } @Test public void testTrieDateRangeFacets() { helpTestDateFacets("bday", FacetRangeMethod.FILTER); } @Test public void testTrieDateRangeFacetsDocValues() { helpTestDateFacets("bday", FacetRangeMethod.DV); } @Test public void testDateRangeFieldFacets() { helpTestDateFacets("bday_drf", FacetRangeMethod.FILTER); } private void helpTestDateFacets(final String fieldName, final FacetRangeMethod rangeFacetMethod) { final String p = "facet.range"; final String b = "facet_ranges"; final String f = fieldName; final String c = "/lst[@name='counts']"; final String pre = "//lst[@name='"+b+"']/lst[@name='"+f+"']" + c; final String meta = pre + "/../"; // range faceting defaults to including only lower endpoint // doc exists with value @ 00:00:00.000 on July5 final String jul4 = "[.='1' ]"; assertQ("check counts for month of facet by day", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-01T00:00:00.000Z+1MONTH" ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".method", rangeFacetMethod.toString() //This only applies to range faceting, won't be use for date faceting ) ,"*[count("+pre+"/int)=31]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z']" + jul4 ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-16T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-17T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-18T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-19T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-21T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-22T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-23T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-24T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-25T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-26T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-27T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-28T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-29T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-30T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-31T00:00:00Z'][.='0']" ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='11']" ); assertQ("check counts for month of facet by day with global mincount = 1", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-01T00:00:00.000Z+1MONTH" ,p+".gap", "+1DAY" ,p+".other", "all" ,"facet.mincount", "1" ) ,"*[count("+pre+"/int)=8]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z']" + jul4 ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-21T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-30T00:00:00Z'][.='1' ]" ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='11']" ); assertQ("check counts for month of facet by day with field mincount = 1", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-01T00:00:00.000Z+1MONTH" ,p+".gap", "+1DAY" ,p+".other", "all" ,"f." + f + ".facet.mincount", "2" ) ,"*[count("+pre+"/int)=3]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]" ,pre ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2' ]" ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='11']" ); assertQ("check before is not inclusive of upper bound by default", req("q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-05T00:00:00.000Z" ,p+".end", "1976-07-07T00:00:00.000Z" ,p+".gap", "+1DAY" ,p+".other", "all" ) ,"*[count("+pre+"/int)=2]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0']" ,meta+"/int[@name='before' ][.='5']" ); assertQ("check after is not inclusive of lower bound by default (for dates)", req("q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-03T00:00:00.000Z" ,p+".end", "1976-07-05T00:00:00.000Z" ,p+".gap", "+1DAY" ,p+".other", "all" ) ,"*[count("+pre+"/int)=2]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z']" + jul4 ,meta+"/int[@name='after' ][.='9']" ); assertQ("check hardend=false", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-13T00:00:00.000Z" ,p+".gap", "+5DAYS" ,p+".other", "all" ,p+".hardend","false" ) ,"*[count("+pre+"/int)=3]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='4' ]" ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='3']" ,meta+"/int[@name='between'][.='9']" ); assertQ("check hardend=true", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-13T00:00:00.000Z" ,p+".gap", "+5DAYS" ,p+".other", "all" ,p+".hardend","true" ) ,"*[count("+pre+"/int)=3]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='1' ]" ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='6']" ,meta+"/int[@name='between'][.='6']" ); //Fixed by SOLR-9080 related to the Gregorian Change Date assertQ("check BC era", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "-0200-01-01T00:00:00Z" // BC ,p+".end", "+0200-01-01T00:00:00Z" // AD ,p+".gap", "+100YEARS" ,p+".other", "all" ) ,pre+"/int[@name='-0200-01-01T00:00:00Z'][.='0']" ,pre+"/int[@name='-0100-01-01T00:00:00Z'][.='0']" ,pre+"/int[@name='0000-01-01T00:00:00Z'][.='0']" ,pre+"/int[@name='0100-01-01T00:00:00Z'][.='0']" ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='14']" ,meta+"/int[@name='between'][.='0']" ); } @Test public void testTrieDateRangeFacetsWithIncludeOption() { helpTestDateRangeFacetsWithIncludeOption("a_tdt"); } @Test public void testDateRangeFieldDateRangeFacetsWithIncludeOption() { helpTestDateRangeFacetsWithIncludeOption("a_drf"); } /** Similar to helpTestDateFacets, but for different fields with test data exactly on boundary marks */ private void helpTestDateRangeFacetsWithIncludeOption(final String fieldName) { final String p = "facet.range"; final String b = "facet_ranges"; final String f = fieldName; final String c = "/lst[@name='counts']"; final String pre = "//lst[@name='"+b+"']/lst[@name='"+f+"']" + c; final String meta = pre + "/../"; assertQ("checking counts for lower", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-16T00:00:00.000Z" ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "lower" ) // 15 days + pre+post+inner = 18 ,"*[count("+pre+"/int)=15]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]" // ,meta+"/int[@name='before' ][.='1']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='8']" ); assertQ("checking counts for upper", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-16T00:00:00.000Z" ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "upper" ) // 15 days + pre+post+inner = 18 ,"*[count("+pre+"/int)=15]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]" // ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='7']" ); assertQ("checking counts for lower & upper", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-16T00:00:00.000Z" ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "lower" ,p+".include", "upper" ) // 15 days + pre+post+inner = 18 ,"*[count("+pre+"/int)=15]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]" // ,meta+"/int[@name='before' ][.='1']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='8']" ); assertQ("checking counts for upper & edge", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-16T00:00:00.000Z" ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "upper" ,p+".include", "edge" ) // 15 days + pre+post+inner = 18 ,"*[count("+pre+"/int)=15]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]" // ,meta+"/int[@name='before' ][.='1']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='8']" ); assertQ("checking counts for upper & outer", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "upper" ,p+".include", "outer" ) // 12 days + pre+post+inner = 15 ,"*[count("+pre+"/int)=12]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" // ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='4']" ,meta+"/int[@name='between'][.='5']" ); assertQ("checking counts for lower & edge", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "lower" ,p+".include", "edge" ) // 12 days + pre+post+inner = 15 ,"*[count("+pre+"/int)=12]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" // ,meta+"/int[@name='before' ][.='1']" ,meta+"/int[@name='after' ][.='3']" ,meta+"/int[@name='between'][.='6']" ); assertQ("checking counts for lower & outer", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "lower" ,p+".include", "outer" ) // 12 days + pre+post+inner = 15 ,"*[count("+pre+"/int)=12]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='0']" // ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='4']" ,meta+"/int[@name='between'][.='5']" ); assertQ("checking counts for lower & edge & outer", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "lower" ,p+".include", "edge" ,p+".include", "outer" ) // 12 days + pre+post+inner = 15 ,"*[count("+pre+"/int)=12]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" // ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='4']" ,meta+"/int[@name='between'][.='6']" ); assertQ("checking counts for all", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,p, f ,p+".start", "1976-07-01T00:00:00.000Z" ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "all" ) // 12 days + pre+post+inner = 15 ,"*[count("+pre+"/int)=12]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" // ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='4']" ,meta+"/int[@name='between'][.='6']" ); } @Test public void testDateRangeFacetsWithTz() { helpTestDateRangeFacetsWithTz("a_tdt"); } private void helpTestDateRangeFacetsWithTz(final String fieldName) { final String p = "facet.range"; final String b = "facet_ranges"; final String f = fieldName; final String c = "/lst[@name='counts']"; final String pre = "//lst[@name='"+b+"']/lst[@name='"+f+"']" + c; final String meta = pre + "/../"; final String TZ = "America/Los_Angeles"; assumeTrue("Test requires JVM to know about about TZ: " + TZ, TimeZoneUtils.KNOWN_TIMEZONE_IDS.contains(TZ)); assertQ("checking facet counts for fixed now, using TZ: " + TZ, req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"NOW", "205078333000" // 1976-07-01T14:12:13.000Z ,"TZ", TZ ,p, f ,p+".start", "NOW/MONTH" ,p+".end", "NOW/MONTH+15DAYS" ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "lower" ) // 15 days + pre+post+inner = 18 ,"*[count("+pre+"/int)=15]" ,pre+"/int[@name='1976-07-01T07:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T07:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T07:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-04T07:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-05T07:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-06T07:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-07T07:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-08T07:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-09T07:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T07:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T07:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T07:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-13T07:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-14T07:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-15T07:00:00Z'][.='1' ]" // ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='7']" ); // NOTE: the counts should all be zero, what we really care about // is that the computed lower bounds take into account DST change assertQ("checking facet counts arround DST change for TZ: " + TZ, req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"NOW", "1288606136000" // 2010-11-01T10:08:56.235Z ,"TZ", TZ ,p, f ,p+".start", "NOW/MONTH" ,p+".end", "NOW/MONTH+15DAYS" ,p+".gap", "+1DAY" ,p+".other", "all" ,p+".include", "lower" ) // 15 days + pre+post+inner = 18 ,"*[count("+pre+"/int)=15]" ,pre+"/int[@name='2010-11-01T07:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-02T07:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-03T07:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-04T07:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-05T07:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-06T07:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-07T07:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-08T08:00:00Z'][.='0']" // BOOM! ,pre+"/int[@name='2010-11-09T08:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-10T08:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-11T08:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-12T08:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-13T08:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-14T08:00:00Z'][.='0']" ,pre+"/int[@name='2010-11-15T08:00:00Z'][.='0']" ); } @Test public void testNumericRangeFacetsTrieFloat() { helpTestFractionalNumberRangeFacets("range_facet_f"); } @Test public void testNumericRangeFacetsTrieDouble() { helpTestFractionalNumberRangeFacets("range_facet_d"); } @Test public void testNumericRangeFacetsTrieFloatDocValues() { helpTestFractionalNumberRangeFacets("range_facet_f", FacetRangeMethod.DV); } @Test public void testNumericRangeFacetsTrieDoubleDocValues() { helpTestFractionalNumberRangeFacets("range_facet_d", FacetRangeMethod.DV); } @Test public void testNumericRangeFacetsOverflowTrieDouble() { helpTestNumericRangeFacetsDoubleOverflow("range_facet_d", FacetRangeMethod.FILTER); } @Test public void testNumericRangeFacetsOverflowTrieDoubleDocValue() { helpTestNumericRangeFacetsDoubleOverflow("range_facet_d", FacetRangeMethod.DV); } private void helpTestNumericRangeFacetsDoubleOverflow(final String fieldName, final FacetRangeMethod method) { final String f = fieldName; final String pre = "//lst[@name='facet_ranges']/lst[@name='"+f+"']/lst[@name='counts']"; final String meta = pre + "/../"; String start = "0.0"; String gap = (new Double( (double)Float.MAX_VALUE )).toString(); String end = (new Double( ((double)Float.MAX_VALUE) * 3D )).toString(); String mid = (new Double( ((double)Float.MAX_VALUE) * 2D )).toString(); assertQ(f+": checking counts for lower", req( "q", "id:[30 TO 60]" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", start ,"facet.range.end", end ,"facet.range.gap", gap ,"facet.range.other", "all" ,"facet.range.include", "lower" ) ,"*[count("+pre+"/int)=3]" ,pre+"/int[@name='"+start+"'][.='6' ]" ,pre+"/int[@name='"+mid+"'][.='0' ]" // ,meta+"/double[@name='end' ][.='"+end+"']" ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='0']" ,meta+"/int[@name='between'][.='6']" ); } private void helpTestFractionalNumberRangeFacets(final String fieldName) { helpTestFractionalNumberRangeFacets(fieldName, FacetRangeMethod.FILTER); } private void helpTestFractionalNumberRangeFacets(final String fieldName, FacetRangeMethod method) { final String f = fieldName; final String pre = "//lst[@name='facet_ranges']/lst[@name='"+f+"']/lst[@name='counts']"; final String meta = pre + "/../"; assertQ(f+": checking counts for lower", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "10" ,"facet.range.end", "50" ,"facet.range.gap", "10" ,"facet.range.other", "all" ,"facet.range.include", "lower" ) ,"*[count("+pre+"/int)=4]" ,pre+"/int[@name='10.0'][.='1' ]" ,pre+"/int[@name='20.0'][.='3' ]" ,pre+"/int[@name='30.0'][.='2' ]" ,pre+"/int[@name='40.0'][.='0' ]" // ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='0']" ,meta+"/int[@name='between'][.='6']" ); assertQ(f + ":checking counts for upper", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "10" ,"facet.range.end", "50" ,"facet.range.gap", "10" ,"facet.range.other", "all" ,"facet.range.include", "upper" ) ,"*[count("+pre+"/int)=4]" ,pre+"/int[@name='10.0'][.='2' ]" ,pre+"/int[@name='20.0'][.='3' ]" ,pre+"/int[@name='30.0'][.='1' ]" ,pre+"/int[@name='40.0'][.='0' ]" // ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='0']" ,meta+"/int[@name='between'][.='6']" ); assertQ(f + ":checking counts for lower & upper", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "10" ,"facet.range.end", "50" ,"facet.range.gap", "10" ,"facet.range.other", "all" ,"facet.range.include", "upper" ,"facet.range.include", "lower" ) ,"*[count("+pre+"/int)=4]" ,pre+"/int[@name='10.0'][.='2' ]" ,pre+"/int[@name='20.0'][.='4' ]" ,pre+"/int[@name='30.0'][.='2' ]" ,pre+"/int[@name='40.0'][.='0' ]" // ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='0']" ,meta+"/int[@name='between'][.='6']" ); assertQ(f + ": checking counts for upper & edge", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "20" ,"facet.range.end", "50" ,"facet.range.gap", "10" ,"facet.range.other", "all" ,"facet.range.include", "upper" ,"facet.range.include", "edge" ) ,"*[count("+pre+"/int)=3]" ,pre+"/int[@name='20.0'][.='4' ]" ,pre+"/int[@name='30.0'][.='1' ]" ,pre+"/int[@name='40.0'][.='0' ]" // ,meta+"/int[@name='before' ][.='1']" ,meta+"/int[@name='after' ][.='0']" ,meta+"/int[@name='between'][.='5']" ); assertQ(f + ": checking counts for upper & outer", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "10" ,"facet.range.end", "30" ,"facet.range.gap", "10" ,"facet.range.other", "all" ,"facet.range.include", "upper" ,"facet.range.include", "outer" ) ,"*[count("+pre+"/int)=2]" ,pre+"/int[@name='10.0'][.='2' ]" ,pre+"/int[@name='20.0'][.='3' ]" // ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='2']" ,meta+"/int[@name='between'][.='5']" ); assertQ(f + ": checking counts for lower & edge", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "10" ,"facet.range.end", "30" ,"facet.range.gap", "10" ,"facet.range.other", "all" ,"facet.range.include", "lower" ,"facet.range.include", "edge" ) ,"*[count("+pre+"/int)=2]" ,pre+"/int[@name='10.0'][.='1' ]" ,pre+"/int[@name='20.0'][.='4' ]" // ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='5']" ); assertQ(f + ": checking counts for lower & outer", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "20" ,"facet.range.end", "40" ,"facet.range.gap", "10" ,"facet.range.other", "all" ,"facet.range.include", "lower" ,"facet.range.include", "outer" ) ,"*[count("+pre+"/int)=2]" ,pre+"/int[@name='20.0'][.='3' ]" ,pre+"/int[@name='30.0'][.='2' ]" // ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='0']" ,meta+"/int[@name='between'][.='5']" ); assertQ(f + ": checking counts for lower & edge & outer", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "20" ,"facet.range.end", "35.3" ,"facet.range.gap", "10" ,"facet.range.other", "all" ,"facet.range.hardend", "true" ,"facet.range.include", "lower" ,"facet.range.include", "edge" ,"facet.range.include", "outer" ) ,"*[count("+pre+"/int)=2]" ,pre+"/int[@name='20.0'][.='3' ]" ,pre+"/int[@name='30.0'][.='2' ]" // ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='5']" ); assertQ(f + ": checking counts for include all", req( "q", "*:*" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "20" ,"facet.range.end", "35.3" ,"facet.range.gap", "10" ,"facet.range.other", "all" ,"facet.range.hardend", "true" ,"facet.range.include", "all" ) ,"*[count("+pre+"/int)=2]" ,pre+"/int[@name='20.0'][.='4' ]" ,pre+"/int[@name='30.0'][.='2' ]" // ,meta+"/int[@name='before' ][.='2']" ,meta+"/int[@name='after' ][.='1']" ,meta+"/int[@name='between'][.='5']" ); } @Test public void testNumericRangeFacetsTrieInt() { helpTestWholeNumberRangeFacets("id"); } @Test public void testNumericRangeFacetsTrieLong() { helpTestWholeNumberRangeFacets("range_facet_l"); } @Test public void testNumericRangeFacetsTrieIntDocValues() { helpTestWholeNumberRangeFacets("id", FacetRangeMethod.DV); } @Test public void testNumericRangeFacetsTrieLongDocValues() { helpTestWholeNumberRangeFacets("range_facet_l", FacetRangeMethod.DV); } @Test public void testNumericRangeFacetsOverflowTrieLong() { helpTestNumericRangeFacetsLongOverflow("range_facet_l", FacetRangeMethod.FILTER); } @Test public void testNumericRangeFacetsOverflowTrieLongDocValues() { helpTestNumericRangeFacetsLongOverflow("range_facet_l", FacetRangeMethod.DV); } private void helpTestNumericRangeFacetsLongOverflow(final String fieldName, final FacetRangeMethod method) { final String f = fieldName; final String pre = "//lst[@name='facet_ranges']/lst[@name='"+f+"']/lst[@name='counts']"; final String meta = pre + "/../"; String start = "0"; String gap = (new Long( (long)Integer.MAX_VALUE )).toString(); String end = (new Long( ((long)Integer.MAX_VALUE) * 3L )).toString(); String mid = (new Long( ((long)Integer.MAX_VALUE) * 2L )).toString(); assertQ(f+": checking counts for lower", req( "q", "id:[30 TO 60]" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", start ,"facet.range.end", end ,"facet.range.gap", gap ,"facet.range.other", "all" ,"facet.range.include", "lower" ) ,"*[count("+pre+"/int)=3]" ,pre+"/int[@name='"+start+"'][.='6' ]" ,pre+"/int[@name='"+mid+"'][.='0' ]" // ,meta+"/long[@name='end' ][.='"+end+"']" ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='0']" ,meta+"/int[@name='between'][.='6']" ); } private void helpTestWholeNumberRangeFacets(final String fieldName) { helpTestWholeNumberRangeFacets(fieldName, FacetRangeMethod.FILTER); } private void helpTestWholeNumberRangeFacets(final String fieldName, FacetRangeMethod method) { // the float test covers a lot of the weird edge cases // here we just need some basic sanity checking of the parsing final String f = fieldName; final String pre = "//lst[@name='facet_ranges']/lst[@name='"+f+"']/lst[@name='counts']"; final String meta = pre + "/../"; assertQ(f+": checking counts for lower", req( "q", "id:[30 TO 60]" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "35" ,"facet.range.end", "50" ,"facet.range.gap", "5" ,"facet.range.other", "all" ,"facet.range.include", "lower" ) ,"*[count("+pre+"/int)=3]" ,pre+"/int[@name='35'][.='0' ]" ,pre+"/int[@name='40'][.='3' ]" ,pre+"/int[@name='45'][.='3' ]" // ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='0']" ,meta+"/int[@name='between'][.='6']" ); assertQ(f + ":checking counts for upper", req( "q", "id:[30 TO 60]" ,"rows", "0" ,"facet", "true" ,"facet.range", f ,"facet.range.method", method.toString() ,"facet.range.start", "35" ,"facet.range.end", "50" ,"facet.range.gap", "5" ,"facet.range.other", "all" ,"facet.range.include", "upper" ) ,"*[count("+pre+"/int)=3]" ,pre+"/int[@name='35'][.='0' ]" ,pre+"/int[@name='40'][.='4' ]" ,pre+"/int[@name='45'][.='2' ]" // ,meta+"/int[@name='before' ][.='0']" ,meta+"/int[@name='after' ][.='0']" ,meta+"/int[@name='between'][.='6']" ); } static void indexFacetSingleValued() { indexFacets("40","t_s1"); } @Test public void testFacetSingleValued() { doFacets("t_s1"); } @Test public void testFacetSingleValuedFcs() { doFacets("t_s1","facet.method","fcs"); } static void indexFacets(String idPrefix, String f) { add_doc("id", idPrefix+"1", f, "A"); add_doc("id", idPrefix+"2", f, "B"); add_doc("id", idPrefix+"3", f, "C"); add_doc("id", idPrefix+"4", f, "C"); add_doc("id", idPrefix+"5", f, "D"); add_doc("id", idPrefix+"6", f, "E"); add_doc("id", idPrefix+"7", f, "E"); add_doc("id", idPrefix+"8", f, "E"); add_doc("id", idPrefix+"9", f, "F"); add_doc("id", idPrefix+"10", f, "G"); add_doc("id", idPrefix+"11", f, "G"); add_doc("id", idPrefix+"12", f, "G"); add_doc("id", idPrefix+"13", f, "G"); add_doc("id", idPrefix+"14", f, "G"); } public void doFacets(String f, String... params) { String pre = "//lst[@name='"+f+"']"; String notc = "id:[* TO *] -"+f+":C"; assertQ("check counts for unlimited facet", req(params, "q", "id:[* TO *]", "indent","true" ,"facet", "true" ,"facet.field", f ) ,"*[count(//lst[@name='facet_fields']/lst/int)=7]" ,pre+"/int[@name='G'][.='5']" ,pre+"/int[@name='E'][.='3']" ,pre+"/int[@name='C'][.='2']" ,pre+"/int[@name='A'][.='1']" ,pre+"/int[@name='B'][.='1']" ,pre+"/int[@name='D'][.='1']" ,pre+"/int[@name='F'][.='1']" ); assertQ("check counts for facet with generous limit", req(params, "q", "id:[* TO *]" ,"facet", "true" ,"facet.limit", "100" ,"facet.field", f ) ,"*[count(//lst[@name='facet_fields']/lst/int)=7]" ,pre+"/int[1][@name='G'][.='5']" ,pre+"/int[2][@name='E'][.='3']" ,pre+"/int[3][@name='C'][.='2']" ,pre+"/int[@name='A'][.='1']" ,pre+"/int[@name='B'][.='1']" ,pre+"/int[@name='D'][.='1']" ,pre+"/int[@name='F'][.='1']" ); assertQ("check counts for limited facet", req(params, "q", "id:[* TO *]" ,"facet", "true" ,"facet.limit", "2" ,"facet.field", f ) ,"*[count(//lst[@name='facet_fields']/lst/int)=2]" ,pre+"/int[1][@name='G'][.='5']" ,pre+"/int[2][@name='E'][.='3']" ); assertQ("check offset", req(params, "q", "id:[* TO *]" ,"facet", "true" ,"facet.offset", "1" ,"facet.limit", "1" ,"facet.field", f ) ,"*[count(//lst[@name='facet_fields']/lst/int)=1]" ,pre+"/int[1][@name='E'][.='3']" ); assertQ("test sorted facet paging with zero (don't count in limit)", req(params, "q", "id:[* TO *]" ,"fq",notc ,"facet", "true" ,"facet.field", f ,"facet.mincount","1" ,"facet.offset","0" ,"facet.limit","6" ) ,"*[count(//lst[@name='facet_fields']/lst/int)=6]" ,pre+"/int[1][@name='G'][.='5']" ,pre+"/int[2][@name='E'][.='3']" ,pre+"/int[3][@name='A'][.='1']" ,pre+"/int[4][@name='B'][.='1']" ,pre+"/int[5][@name='D'][.='1']" ,pre+"/int[6][@name='F'][.='1']" ); assertQ("test sorted facet paging with zero (test offset correctness)", req(params, "q", "id:[* TO *]" ,"fq",notc ,"facet", "true" ,"facet.field", f ,"facet.mincount","1" ,"facet.offset","3" ,"facet.limit","2" ,"facet.sort","count" ) ,"*[count(//lst[@name='facet_fields']/lst/int)=2]" ,pre+"/int[1][@name='B'][.='1']" ,pre+"/int[2][@name='D'][.='1']" ); assertQ("test facet unsorted paging", req(params, "q", "id:[* TO *]" ,"fq",notc ,"facet", "true" ,"facet.field", f ,"facet.mincount","1" ,"facet.offset","0" ,"facet.limit","6" ,"facet.sort","index" ) ,"*[count(//lst[@name='facet_fields']/lst/int)=6]" ,pre+"/int[1][@name='A'][.='1']" ,pre+"/int[2][@name='B'][.='1']" ,pre+"/int[3][@name='D'][.='1']" ,pre+"/int[4][@name='E'][.='3']" ,pre+"/int[5][@name='F'][.='1']" ,pre+"/int[6][@name='G'][.='5']" ); assertQ("test facet unsorted paging", req(params, "q", "id:[* TO *]" ,"fq",notc ,"facet", "true" ,"facet.field", f ,"facet.mincount","1" ,"facet.offset","3" ,"facet.limit","2" ,"facet.sort","index" ) ,"*[count(//lst[@name='facet_fields']/lst/int)=2]" ,pre+"/int[1][@name='E'][.='3']" ,pre+"/int[2][@name='F'][.='1']" ); assertQ("test facet unsorted paging, mincount=2", req(params, "q", "id:[* TO *]" ,"fq",notc ,"facet", "true" ,"facet.field", f ,"facet.mincount","2" ,"facet.offset","1" ,"facet.limit","2" ,"facet.sort","index" ) ,"*[count(//lst[@name='facet_fields']/lst/int)=1]" ,pre+"/int[1][@name='G'][.='5']" ); } static void indexFacetPrefixMultiValued() { indexFacetPrefix("50","t_s","","ignore_s"); } @Test public void testFacetPrefixMultiValued() { doFacetPrefix("t_s", null, "", "facet.method","enum"); 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", "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() { indexFacetPrefix("60","tt_s1","","ignore_s"); } @Test public void testFacetPrefixSingleValued() { doFacetPrefix("tt_s1", null, ""); } @Test public void testFacetPrefixSingleValuedFcs() { doFacetPrefix("tt_s1", null, "", "facet.method","fcs"); doFacetPrefix("tt_s1", "{!threads=0}", "", "facet.method","fcs"); // direct execution doFacetPrefix("tt_s1", "{!threads=-1}", "", "facet.method","fcs"); // default / unlimited 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 //@Ignore("SOLR-8466 - facet.method=uif ignores facet.contains") public void testFacetContainsUif() { 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", "facet.contains.ignoreCase", "true"); } static void indexFacetContains() { indexFacetPrefix("70","contains_s1","","contains_group_s1"); indexFacetPrefix("80","contains_s1","Astra","contains_group_s1"); } @Test public void testFacetContains() { doFacetContains("contains_s1", "contains_group_s1", "Astra", "BAst", "Ast", "facet.method", "enum"); doFacetContains("contains_s1", "contains_group_s1", "Astra", "BAst", "Ast", "facet.method", "fcs"); doFacetContains("contains_s1", "contains_group_s1", "Astra", "BAst", "Ast", "facet.method", "fc"); doFacetContains("contains_s1", "contains_group_s1", "Astra", "bAst", "ast", "facet.method", "enum", "facet.contains.ignoreCase", "true"); doFacetContains("contains_s1", "contains_group_s1", "Astra", "baSt", "ast", "facet.method", "fcs", "facet.contains.ignoreCase", "true"); doFacetContains("contains_s1", "contains_group_s1", "Astra", "basT", "ast", "facet.method", "fc", "facet.contains.ignoreCase", "true"); doFacetPrefix("contains_s1", null, "Astra", "facet.method", "enum", "facet.contains", "Ast"); doFacetPrefix("contains_s1", null, "Astra", "facet.method", "fcs", "facet.contains", "Ast"); doFacetPrefix("contains_s1", null, "Astra", "facet.method", "fc", "facet.contains", "Ast"); 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", "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) { add_doc("id", idPrefix+"1", f, "AAA"+termSuffix, g, "A"); add_doc("id", idPrefix+"2", f, "B"+termSuffix, g, "A"); add_doc("id", idPrefix+"3", f, "BB"+termSuffix, g, "B"); add_doc("id", idPrefix+"4", f, "BB"+termSuffix, g, "B"); add_doc("id", idPrefix+"5", f, "BBB"+termSuffix, g, "B"); add_doc("id", idPrefix+"6", f, "BBB"+termSuffix, g, "B"); add_doc("id", idPrefix+"7", f, "BBB"+termSuffix, g, "C"); add_doc("id", idPrefix+"8", f, "CC"+termSuffix, g, "C"); add_doc("id", idPrefix+"9", f, "CC"+termSuffix, g, "C"); add_doc("id", idPrefix+"10", f, "CCC"+termSuffix, g, "C"); add_doc("id", idPrefix+"11", f, "CCC"+termSuffix, g, "D"); add_doc("id", idPrefix+"12", f, "CCC"+termSuffix, g, "E"); assertU(commit()); } public void doFacetPrefix(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 facet.prefix middle, exact match first term", req(params, "q", "id:[* TO *]" ,"indent",indent ,"facet","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='BBB"+termSuffix+"'][.='3']" ,pre+"/int[2][@name='BB"+termSuffix+"'][.='2']" ,pre+"/int[3][@name='B"+termSuffix+"'][.='1']" ); assertQ("test facet.prefix middle, exact match first term, unsorted", req(params, "q", "id:[* TO *]" ,"indent",indent ,"facet","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+"'][.='2']" ,pre+"/int[3][@name='BBB"+termSuffix+"'][.='3']" ); assertQ("test facet.prefix middle, paging", req(params, "q", "id:[* TO *]" ,"indent",indent ,"facet","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+"'][.='2']" ,pre+"/int[2][@name='B"+termSuffix+"'][.='1']" ); assertQ("test facet.prefix middle, paging", req(params, "q", "id:[* TO *]" ,"indent",indent ,"facet","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+"'][.='2']" ); assertQ("test facet.prefix middle, paging", req(params, "q", "id:[* TO *]" ,"indent",indent ,"facet","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+"'][.='2']" ); assertQ("test facet.prefix end, not exact match", req(params, "q", "id:[* TO *]" ,"indent",indent ,"facet","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='CCC"+termSuffix+"'][.='3']" ,pre+"/int[2][@name='CC"+termSuffix+"'][.='2']" ); assertQ("test facet.prefix end, exact match", req(params, "q", "id:[* TO *]" ,"indent",indent ,"facet","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='CCC"+termSuffix+"'][.='3']" ,pre+"/int[2][@name='CC"+termSuffix+"'][.='2']" ); assertQ("test facet.prefix past end", req(params, "q", "id:[* TO *]" ,"indent",indent ,"facet","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.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.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.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 at Start, not exact match", req(params, "q", "id:[* TO *]" ,"indent",indent ,"facet","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.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.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.field", lf ,"facet.mincount","3" ,"facet.offset","5" ,"facet.limit","10" ,"facet.sort","count" ,"facet.prefix","CC" ) ,"*[count(//lst[@name='facet_fields']/lst/int)=0]" ); } 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) { String indent="on"; String pre = "//lst[@name='"+f+"']"; assertQ("test facet.contains", 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 ) ,"*[count(//lst[@name='facet_fields']/lst/int)=3]" ,pre+"/int[1][@name='BBB"+termSuffix+"'][.='3']" ,pre+"/int[2][@name='BB"+termSuffix+"'][.='2']" ,pre+"/int[3][@name='B"+termSuffix+"'][.='1']" ); assertQ("test facet.contains for grouped facets", 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 ,"group","true" ,"group.field",g ,"group.facet","true" ) ,"*[count(//lst[@name='facet_fields']/lst/int)=6]" ,pre+"/int[1][@name='CCC"+termSuffix+"'][.='3']" ,pre+"/int[2][@name='BBB"+termSuffix+"'][.='2']" ,pre+"/int[3][@name='AAA"+termSuffix+"'][.='1']" ,pre+"/int[4][@name='B"+termSuffix+"'][.='1']" ,pre+"/int[5][@name='BB"+termSuffix+"'][.='1']" ,pre+"/int[6][@name='CC"+termSuffix+"'][.='1']" ); } /** * kind of an absurd test because if there is an infinite loop, it * would never finish -- but at least it ensures that <i>if</i> one of * these requests return, they return an error */ public void testRangeFacetInfiniteLoopDetection() { for (String field : new String[] {"foo_f", "foo_d", "foo_i"}) { assertQEx("no zero gap error: " + field, req("q", "*:*", "facet", "true", "facet.range", field, "facet.range.start", "23", "facet.range.gap", "0", "facet.range.end", "100"), 400); } String field = "foo_dt"; assertQEx("no zero gap error for facet.range: " + field, req("q", "*:*", "facet", "true", "facet.range", field, "facet.range.start", "NOW", "facet.range.gap", "+0DAYS", "facet.range.end", "NOW+10DAY"), 400); field = "foo_f"; assertQEx("no float underflow error: " + field, req("q", "*:*", "facet", "true", "facet.range", field, "facet.range.start", "100000000000", "facet.range.end", "100000086200", "facet.range.gap", "2160"), 400); field = "foo_d"; assertQEx("no double underflow error: " + field, req("q", "*:*", "facet", "true", "facet.range", field, "facet.range.start", "9900000000000", "facet.range.end", "9900000086200", "facet.range.gap", "0.0003"), 400); } public void testRangeQueryHardEndParamFilter() { doTestRangeQueryHardEndParam("range_facet_l", FacetRangeMethod.FILTER); } public void testRangeQueryHardEndParamDv() { doTestRangeQueryHardEndParam("range_facet_l", FacetRangeMethod.DV); } private void doTestRangeQueryHardEndParam(String field, FacetRangeMethod method) { assertQ("Test facet.range.hardend", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","5" ,"facet.range.hardend", "false" ,"facet.range.other", "after" ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=1]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int[@name='43'][.='5']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/long[@name='end'][.='48']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='0']" ); assertQ("Test facet.range.hardend", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","5" ,"facet.range.hardend", "true" ,"facet.range.other", "after" ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=1]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int[@name='43'][.='2']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/long[@name='end'][.='45']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='3']" ); } public void testRangeQueryOtherParamFilter() { doTestRangeQueryOtherParam("range_facet_l", FacetRangeMethod.FILTER); } public void testRangeQueryOtherParamDv() { doTestRangeQueryOtherParam("range_facet_l", FacetRangeMethod.DV); } private void doTestRangeQueryOtherParam(String field, FacetRangeMethod method) { assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other", FacetRangeOther.BEFORE.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='before'][.='1']" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='after'])=0]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='between'])=0]" ); assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other", FacetRangeOther.AFTER.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='3']" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='between'])=0]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='before'])=0]" ); assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other",FacetRangeOther.BETWEEN.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='after'])=0]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='before'])=0]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='between'][.='2']" ); assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other",FacetRangeOther.NONE.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='after'])=0]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='before'])=0]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='between'])=0]" ); // these should have equivilent behavior (multivalued 'other' param: top level vs local) for (SolrQueryRequest req : new SolrQueryRequest[] { req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other",FacetRangeOther.BEFORE.toString() ,"facet.range.other",FacetRangeOther.AFTER.toString()), req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", "{!facet.range.other=before facet.range.other=after}" + field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1") }) { assertQ("Test facet.range.other: " + req.toString(), req ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='between'])=0]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='3']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='before'][.='1']" ); } assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other",FacetRangeOther.BEFORE.toString() ,"facet.range.other",FacetRangeOther.AFTER.toString() ,"facet.range.other",FacetRangeOther.NONE.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='between'])=0]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='after'])=0]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='before'])=0]" ); assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other",FacetRangeOther.ALL.toString() ,"facet.range.include", FacetRangeInclude.LOWER.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='between'][.='2']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='3']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='before'][.='1']" ); assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other",FacetRangeOther.ALL.toString() ,"facet.range.include", FacetRangeInclude.UPPER.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='between'][.='2']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='2']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='before'][.='2']" ); assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other",FacetRangeOther.ALL.toString() ,"facet.range.include", FacetRangeInclude.EDGE.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='between'][.='3']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='2']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='before'][.='1']" ); assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other", FacetRangeOther.ALL.toString() ,"facet.range.include", FacetRangeInclude.OUTER.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=2]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='between'][.='1']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='3']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='before'][.='2']" ); assertQ("Test facet.range.other", req("q", "id:[12345 TO 12345]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","1" ,"facet.range.other", FacetRangeOther.ALL.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='between'][.='0']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='0']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='before'][.='0']" ); assertQ("Test facet.range.other", req("q", "id:[42 TO 47]" ,"facet","true" ,"fl","id," + field ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","10" ,"facet.range.other", FacetRangeOther.ALL.toString() ) ,"*[count(//lst[@name='facet_ranges']/lst)=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts'])=1]" ,"*[count(//lst[@name='facet_ranges']/lst[@name='" + field + "']/lst[@name='counts']/int)=1]" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='between'][.='5']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='after'][.='0']" ,"//lst[@name='facet_ranges']/lst[@name='" + field + "']/int[@name='before'][.='1']" ); } public void testRangeFacetingBadRequest() { String field = "range_facet_l"; ignoreException("."); try { for (FacetRangeMethod method:FacetRangeMethod.values()) { assertQEx("Test facet.range bad requests", "range facet 'end' comes before 'start'", req("q", "*:*" ,"facet","true" ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","45" ,"facet.range.end","43" ,"facet.range.gap","10" ), ErrorCode.BAD_REQUEST ); assertQEx("Test facet.range bad requests", "range facet infinite loop (is gap negative? did the math overflow?)", req("q", "*:*" ,"facet","true" ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","-1" ), ErrorCode.BAD_REQUEST ); assertQEx("Test facet.range bad requests", "range facet infinite loop: gap is either zero, or too small relative start/end and caused underflow", req("q", "*:*" ,"facet","true" ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","0" ), ErrorCode.BAD_REQUEST ); assertQEx("Test facet.range bad requests", "Missing required parameter", req("q", "*:*" ,"facet","true" ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.end","45" ,"facet.range.gap","5" ), ErrorCode.BAD_REQUEST ); assertQEx("Test facet.range bad requests", "Missing required parameter", req("q", "*:*" ,"facet","true" ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.gap","5" ), ErrorCode.BAD_REQUEST ); assertQEx("Test facet.range bad requests", "Missing required parameter", req("q", "*:*" ,"facet","true" ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ), ErrorCode.BAD_REQUEST ); assertQEx("Test facet.range bad requests", "Unable to range facet on field", req("q", "*:*" ,"facet","true" ,"facet.range", "contains_s1" ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","5" ), ErrorCode.BAD_REQUEST ); assertQEx("Test facet.range bad requests", "foo is not a valid method for range faceting", req("q", "*:*" ,"facet","true" ,"facet.range", field ,"facet.range.method", "foo" ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","5" ), ErrorCode.BAD_REQUEST ); assertQEx("Test facet.range bad requests", "foo is not a valid type of for range 'include' information", req("q", "*:*" ,"facet","true" ,"facet.range", field ,"facet.range.method", method.toString() ,"facet.range.start","43" ,"facet.range.end","45" ,"facet.range.gap","5" ,"facet.range.include", "foo" ), ErrorCode.BAD_REQUEST ); } } finally { resetExceptionIgnores(); } } @SuppressWarnings("unchecked") public void testRangeFacetFilterVsDocValuesRandom() throws Exception { for (int i = 0; i < atLeast(100); i++) { ModifiableSolrParams params = null; int fieldType = i%3; switch (fieldType) { case 0: params = getRandomParamsDate(); break; case 1: params = getRandomParamsInt(); break; case 2: params = getRandomParamsFloat(); break; } String field = params.get("facet.range"); params.add("q", getRandomQuery()); params.add("facet", "true"); if (random().nextBoolean()) { params.add("facet.range.method", FacetRangeMethod.FILTER.toString()); } NamedList<Object> rangeFacetsFilter; NamedList<Object> rangeFacetsDv; SolrQueryRequest req = req(params); log.info("Using Params: " + params); try { SolrQueryResponse rsp = h.queryAndResponse("standard", req); rangeFacetsFilter = (NamedList<Object>) ((NamedList<Object>) rsp.getValues().get("facet_counts")).get("facet_ranges"); } finally { req.close(); } params.add("facet.range.method", FacetRangeMethod.DV.toString()); req = req(params); try { SolrQueryResponse rsp = h.queryAndResponse("standard", req); rangeFacetsDv = (NamedList<Object>) ((NamedList<Object>) rsp.getValues().get("facet_counts")).get("facet_ranges"); } finally { req.close(); } assertNotNull(rangeFacetsFilter.get(field)); assertNotNull(rangeFacetsDv.get(field)); assertSameResults("Different results obtained when using 'filter' and 'dv' methods for Range Facets using params." + params + "\n" + "Filter:" + rangeFacetsFilter + "\n DV: " + rangeFacetsDv, (NamedList<Object>)rangeFacetsFilter.get(field), (NamedList<Object>)rangeFacetsDv.get(field)); } } public void testFacetPrefixWithFacetThreads() throws Exception { assertQ("Test facet.prefix with facet.thread", req("q", "id:[101 TO 102]" ,"facet","true" ,"facet.field", "{!key=key1 facet.prefix=foo}myfield_s" ,"facet.field", "{!key=key2 facet.prefix=bar}myfield_s" ,"facet.threads", "1" ) ,"*[count(//lst[@name='facet_fields']/lst[@name='key1']/int[@name='foo'])=1]" ,"*[count(//lst[@name='facet_fields']/lst[@name='key2']/int[@name='bar'])=1]" ); } private String getRandomQuery() { if (rarely()) { return "*:*"; } Integer[] values = new Integer[2]; values[0] = random().nextInt(3000); values[1] = random().nextInt(3000); Arrays.sort(values); return String.format(Locale.ROOT, "id: [%d TO %d]", values[0], values[1]); } private void assertSameResults(String message, NamedList<Object> rangeFacetsFilter, NamedList<Object> rangeFacetsDv) { assertEquals(message + " Different number of elements.", rangeFacetsFilter.size(), rangeFacetsDv.size()); for (Map.Entry<String, Object> entry:rangeFacetsFilter) { if (entry.getKey().equals("counts")) { continue; } Object value = rangeFacetsDv.get(entry.getKey()); if (value == null) { fail(message + " Element not found with 'dv' method: " + entry.getKey()); } assertEquals(message + "Different value for key " + entry.getKey(), entry.getValue(), value); } assertNotNull("Null counts: " + rangeFacetsFilter, rangeFacetsFilter.get("counts")); assertNotNull("Null counts: " + rangeFacetsDv, rangeFacetsDv.get("counts")); assertEquals(message + "Different counts", rangeFacetsFilter.get("counts"), rangeFacetsDv.get("counts")); } private ModifiableSolrParams getRandomParamsInt() { String field = new String[]{"range_facet_l_dv", "range_facet_i_dv", "range_facet_l", "duration_i1", "id"}[random().nextInt(5)]; ModifiableSolrParams params = new ModifiableSolrParams(); Integer[] values = new Integer[2]; do { values[0] = random().nextInt(3000); values[1] = random().nextInt(3000); } while (values[0].equals(values[1])); Arrays.sort(values); long gapNum = Math.max(1, random().nextInt(3000)); params.add(FacetParams.FACET_RANGE_START, String.valueOf(values[0])); params.add(FacetParams.FACET_RANGE_END, String.valueOf(values[1])); params.add(FacetParams.FACET_RANGE_GAP, String.format(Locale.ROOT, "+%d", gapNum)); addCommonRandomRangeParams(params); params.add(FacetParams.FACET_RANGE, field); return params; } private ModifiableSolrParams getRandomParamsFloat() { String field = new String[]{"range_facet_d_dv", "range_facet_f_dv", "range_facet_d", "range_facet_f", "range_facet_mv_f", "range_facet_f1", "range_facet_f1_dv"}[random().nextInt(7)]; ModifiableSolrParams params = new ModifiableSolrParams(); Float[] values = new Float[2]; do { values[0] = random().nextFloat() * 3000; values[1] = random().nextFloat() * 3000; } while (values[0].equals(values[1])); Arrays.sort(values); float gapNum = Math.max(1, random().nextFloat() * 3000); params.add(FacetParams.FACET_RANGE_START, String.valueOf(values[0])); params.add(FacetParams.FACET_RANGE_END, String.valueOf(values[1])); params.add(FacetParams.FACET_RANGE_GAP, String.format(Locale.ROOT, "+%f", gapNum)); addCommonRandomRangeParams(params); params.add(FacetParams.FACET_RANGE, field); return params; } private final static String[] DATE_GAP_UNITS = new String[]{"SECONDS", "MINUTES", "HOURS", "DAYS", "MONTHS", "YEARS"}; private ModifiableSolrParams getRandomParamsDate() { String field = new String[]{"range_facet_dt_dv", "a_tdt", "bday"}[random().nextInt(3)]; ModifiableSolrParams params = new ModifiableSolrParams(); Date[] dates = new Date[2]; do { dates[0] = new Date((long)(random().nextDouble()*(new Date().getTime()))); dates[1] = new Date((long)(random().nextDouble()*(new Date().getTime()))); } while (dates[0].equals(dates[1])); Arrays.sort(dates); long dateDiff = (dates[1].getTime() - dates[0].getTime())/1000; String gapUnit; if (dateDiff < 1000) { gapUnit = DATE_GAP_UNITS[random().nextInt(DATE_GAP_UNITS.length)]; } else if (dateDiff < 10000){ gapUnit = DATE_GAP_UNITS[1 + random().nextInt(DATE_GAP_UNITS.length - 1)]; } else if (dateDiff < 100000){ gapUnit = DATE_GAP_UNITS[2 + random().nextInt(DATE_GAP_UNITS.length - 2)]; } else if (dateDiff < 1000000){ gapUnit = DATE_GAP_UNITS[3 + random().nextInt(DATE_GAP_UNITS.length - 3)]; } else { gapUnit = DATE_GAP_UNITS[4 + random().nextInt(DATE_GAP_UNITS.length - 4)]; } int gapNum = random().nextInt(100) + 1; params.add(FacetParams.FACET_RANGE_START, dates[0].toInstant().toString()); params.add(FacetParams.FACET_RANGE_END, dates[1].toInstant().toString()); params.add(FacetParams.FACET_RANGE_GAP, String.format(Locale.ROOT, "+%d%s", gapNum, gapUnit)); addCommonRandomRangeParams(params); params.add(FacetParams.FACET_RANGE, field); return params; } private void addCommonRandomRangeParams(ModifiableSolrParams params) { for (int i = 0; i < random().nextInt(2); i++) { params.add(FacetParams.FACET_RANGE_OTHER, FacetRangeOther.values()[random().nextInt(FacetRangeOther.values().length)].toString()); } if (random().nextBoolean()) { params.add(FacetParams.FACET_RANGE_INCLUDE, FacetRangeInclude.values()[random().nextInt(FacetRangeInclude.values().length)].toString()); } if (random().nextBoolean()) { params.add(FacetParams.FACET_MINCOUNT, String.valueOf(random().nextInt(10))); } params.add(FacetParams.FACET_RANGE_HARD_END, String.valueOf(random().nextBoolean())); } }