/* * 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; import java.util.Arrays; import java.util.List; import org.apache.lucene.util.LuceneTestCase.Slow; import org.apache.solr.SolrTestCaseJ4.SuppressSSL; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.response.IntervalFacet.Count; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.params.ModifiableSolrParams; import org.junit.BeforeClass; import org.junit.Test; @Slow @SuppressSSL(bugUrl="https://issues.apache.org/jira/browse/SOLR-9182 - causes OOM") public class DistributedIntervalFacetingTest extends BaseDistributedSearchTestCase { @BeforeClass public static void beforeSuperClass() throws Exception { schemaString = "schema-distrib-interval-faceting.xml"; configString = "solrconfig-basic.xml"; } @Test public void test() throws Exception { del("*:*"); commit(); testRandom(); del("*:*"); commit(); testSolrJ(); } private void testSolrJ() throws Exception { indexr("id", "0", "test_i_dv", "0", "test_s_dv", "AAA"); indexr("id", "1", "test_i_dv", "1", "test_s_dv", "BBB"); indexr("id", "2", "test_i_dv", "2", "test_s_dv", "AAA"); indexr("id", "3", "test_i_dv", "3", "test_s_dv", "CCC"); commit(); QueryResponse response = controlClient.query(new SolrQuery("*:*")); assertEquals(4, response.getResults().getNumFound()); SolrQuery q = new SolrQuery("*:*"); String[] intervals = new String[]{"[0,1)","[1,2)", "[2,3)", "[3,*)"}; q.addIntervalFacets("test_i_dv", intervals); response = controlClient.query(q); assertEquals(1, response.getIntervalFacets().size()); assertEquals("test_i_dv", response.getIntervalFacets().get(0).getField()); assertEquals(4, response.getIntervalFacets().get(0).getIntervals().size()); for (int i = 0; i < response.getIntervalFacets().get(0).getIntervals().size(); i++) { Count count = response.getIntervalFacets().get(0).getIntervals().get(i); assertEquals(intervals[i], count.getKey()); assertEquals(1, count.getCount()); } q = new SolrQuery("*:*"); q.addIntervalFacets("test_i_dv", intervals); q.addIntervalFacets("test_s_dv", new String[]{"{!key='AAA'}[AAA,AAA]", "{!key='BBB'}[BBB,BBB]", "{!key='CCC'}[CCC,CCC]"}); response = controlClient.query(q); assertEquals(2, response.getIntervalFacets().size()); int stringIntervalIndex = "test_s_dv".equals(response.getIntervalFacets().get(0).getField())?0:1; assertEquals("test_i_dv", response.getIntervalFacets().get(1-stringIntervalIndex).getField()); assertEquals("test_s_dv", response.getIntervalFacets().get(stringIntervalIndex).getField()); for (int i = 0; i < response.getIntervalFacets().get(1-stringIntervalIndex).getIntervals().size(); i++) { Count count = response.getIntervalFacets().get(1-stringIntervalIndex).getIntervals().get(i); assertEquals(intervals[i], count.getKey()); assertEquals(1, count.getCount()); } List<Count> stringIntervals = response.getIntervalFacets().get(stringIntervalIndex).getIntervals(); assertEquals(3, stringIntervals.size()); assertEquals("AAA", stringIntervals.get(0).getKey()); assertEquals(2, stringIntervals.get(0).getCount()); assertEquals("BBB", stringIntervals.get(1).getKey()); assertEquals(1, stringIntervals.get(1).getCount()); assertEquals("CCC", stringIntervals.get(2).getKey()); assertEquals(1, stringIntervals.get(2).getCount()); } private void testRandom() throws Exception { // All field values will be a number between 0 and cardinality int cardinality = 1000000; // Fields to use for interval faceting String[] fields = new String[]{"test_s_dv", "test_i_dv", "test_l_dv", "test_f_dv", "test_d_dv", "test_ss_dv", "test_is_dv", "test_fs_dv", "test_ls_dv", "test_ds_dv"}; for (int i = 0; i < atLeast(500); i++) { if (random().nextInt(50) == 0) { //have some empty docs indexr("id", String.valueOf(i)); continue; } if (random().nextInt(100) == 0 && i > 0) { //delete some docs del("id:" + String.valueOf(i - 1)); } Object[] docFields = new Object[(random().nextInt(5)) * 10 + 12]; docFields[0] = "id"; docFields[1] = String.valueOf(i); docFields[2] = "test_s_dv"; docFields[3] = String.valueOf(random().nextInt(cardinality)); docFields[4] = "test_i_dv"; docFields[5] = String.valueOf(random().nextInt(cardinality)); docFields[6] = "test_l_dv"; docFields[7] = String.valueOf(random().nextInt(cardinality)); docFields[8] = "test_f_dv"; docFields[9] = String.valueOf(random().nextFloat() * cardinality); docFields[10] = "test_d_dv"; docFields[11] = String.valueOf(random().nextDouble() * cardinality); for (int j = 12; j < docFields.length; ) { docFields[j++] = "test_ss_dv"; docFields[j++] = String.valueOf(random().nextInt(cardinality)); docFields[j++] = "test_is_dv"; docFields[j++] = String.valueOf(random().nextInt(cardinality)); docFields[j++] = "test_ls_dv"; docFields[j++] = String.valueOf(random().nextInt(cardinality)); docFields[j++] = "test_fs_dv"; docFields[j++] = String.valueOf(random().nextFloat() * cardinality); docFields[j++] = "test_ds_dv"; docFields[j++] = String.valueOf(random().nextDouble() * cardinality); } indexr(docFields); if (random().nextInt(50) == 0) { commit(); } } commit(); handle.clear(); handle.put("QTime", SKIPVAL); handle.put("timestamp", SKIPVAL); handle.put("maxScore", SKIPVAL); for (int i = 0; i < atLeast(100); i++) { doTestQuery(cardinality, fields); } } /** * Executes one query using interval faceting and compares with the same query using * facet query with the same range */ private void doTestQuery(int cardinality, String[] fields) throws Exception { String[] startOptions = new String[]{"(", "["}; String[] endOptions = new String[]{")", "]"}; // the query should match some documents in most cases Integer[] qRange = getRandomRange(cardinality, "id"); ModifiableSolrParams params = new ModifiableSolrParams(); params.set("q", "id:[" + qRange[0] + " TO " + qRange[1] + "]"); params.set("facet", "true"); params.set("rows", "0"); String field = fields[random().nextInt(fields.length)]; //choose from any of the fields if (random().nextBoolean()) { params.set("facet.interval", field); } else { params.set("facet.interval", getFieldWithKey(field)); } // number of intervals for (int i = 0; i < 1 + random().nextInt(20); i++) { Integer[] interval = getRandomRange(cardinality, field); String open = startOptions[interval[0] % 2]; String close = endOptions[interval[1] % 2]; params.add("f." + field + ".facet.interval.set", open + interval[0] + "," + interval[1] + close); } query(params); } private String getFieldWithKey(String field) { return "{!key='_some_key_for_" + field + "_" + random().nextInt() + "'}" + field; } /** * Returns a random range. It's guaranteed that the first * number will be lower than the second, and both of them * between 0 (inclusive) and <code>max</code> (exclusive). * If the fieldName is "test_s_dv" or "test_ss_dv" (the * two fields used for Strings), the comparison will be done * alphabetically */ private Integer[] getRandomRange(int max, String fieldName) { Integer[] values = new Integer[2]; values[0] = random().nextInt(max); values[1] = random().nextInt(max); if ("test_s_dv".equals(fieldName) || "test_ss_dv".equals(fieldName)) { Arrays.sort(values, (o1, o2) -> String.valueOf(o1).compareTo(String.valueOf(o2))); } else { Arrays.sort(values); } return values; } }