| /* |
| * 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.cloud; |
| |
| import java.io.IOException; |
| import java.lang.invoke.MethodHandles; |
| import java.util.Arrays; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.lucene.util.TestUtil; |
| import org.apache.solr.SolrTestCaseJ4.SuppressSSL; |
| import org.apache.solr.client.solrj.SolrServerException; |
| import org.apache.solr.client.solrj.response.FieldStatsInfo; |
| import org.apache.solr.client.solrj.response.PivotField; |
| import org.apache.solr.client.solrj.response.QueryResponse; |
| import org.apache.solr.common.SolrInputDocument; |
| import org.apache.solr.common.params.FacetParams; |
| import org.apache.solr.common.params.ModifiableSolrParams; |
| import org.apache.solr.common.params.SolrParams; |
| import org.apache.solr.common.params.StatsParams; |
| import org.apache.solr.common.util.NamedList; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static org.apache.solr.common.params.FacetParams.FACET; |
| import static org.apache.solr.common.params.FacetParams.FACET_LIMIT; |
| import static org.apache.solr.common.params.FacetParams.FACET_MISSING; |
| import static org.apache.solr.common.params.FacetParams.FACET_OFFSET; |
| import static org.apache.solr.common.params.FacetParams.FACET_OVERREQUEST_COUNT; |
| import static org.apache.solr.common.params.FacetParams.FACET_OVERREQUEST_RATIO; |
| import static org.apache.solr.common.params.FacetParams.FACET_PIVOT; |
| import static org.apache.solr.common.params.FacetParams.FACET_PIVOT_MINCOUNT; |
| import static org.apache.solr.common.params.FacetParams.FACET_SORT; |
| |
| /** |
| * <p> |
| * Randomized testing of Pivot Faceting using SolrCloud. |
| * </p> |
| * <p> |
| * After indexing a bunch of random docs, picks some random fields to pivot facet on, |
| * and then confirms that the resulting counts match the results of filtering on those |
| * values. This gives us strong assertions on the correctness of the total counts for |
| * each pivot value, but no assertions that the correct "top" counts were chosen. |
| * </p> |
| * <p> |
| * NOTE: this test ignores the control collection and only deals with the |
| * CloudSolrServer - this is because the randomized field values make it very easy for |
| * the term stats to miss values even with the overrequest. |
| * (because so many values will tie for "1"). What we care about here is |
| * that the counts we get back are correct and match what we get when filtering on those |
| * constraints. |
| * </p> |
| * |
| * |
| * |
| */ |
| @SuppressSSL // Too Slow |
| public class TestCloudPivotFacet extends AbstractFullDistribZkTestBase { |
| |
| private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| // because floating point addition can depende on the order of operations, we ignore |
| // any stats that can be lossy -- the purpose of testing stats here is just to sanity check |
| // that the basic hooks between pivot faceting and stats.field work, and these let us do that |
| private static final String USE_STATS = "count=true missing=true min=true max=true"; |
| |
| // param used by test purely for tracing & validation |
| private static String TRACE_MIN = "_test_min"; |
| // param used by test purely for tracing & validation |
| private static String TRACE_MISS = "_test_miss"; |
| // param used by test purely for tracing & validation |
| private static String TRACE_SORT = "_test_sort"; |
| |
| public TestCloudPivotFacet() { |
| // we need DVs on point fields to compute stats & facets |
| if (Boolean.getBoolean(NUMERIC_POINTS_SYSPROP)) System.setProperty(NUMERIC_DOCVALUES_SYSPROP,"true"); |
| } |
| |
| /** |
| * Controls the odds of any given doc having a value in any given field -- as this gets lower, |
| * the counts for "facet.missing" pivots should increase. |
| * @see #useField() |
| */ |
| private static int useFieldRandomizedFactor = -1; |
| |
| @BeforeClass |
| public static void initUseFieldRandomizedFactor() { |
| useFieldRandomizedFactor = TestUtil.nextInt(random(), 2, 30); |
| log.info("init'ing useFieldRandomizedFactor = {}", useFieldRandomizedFactor); |
| } |
| |
| @Test |
| //commented 2-Aug-2018 @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 28-June-2018 |
| public void test() throws Exception { |
| |
| waitForThingsToLevelOut(30000); // TODO: why would we have to wait? |
| // |
| handle.clear(); |
| handle.put("QTime", SKIPVAL); |
| handle.put("timestamp", SKIPVAL); |
| |
| final Set<String> fieldNameSet = new HashSet<>(); |
| |
| // build up a randomized index |
| final int numDocs = atLeast(500); |
| log.info("numDocs: {}", numDocs); |
| |
| for (int i = 1; i <= numDocs; i++) { |
| SolrInputDocument doc = buildRandomDocument(i); |
| |
| // not efficient, but it guarantees that even if people change buildRandomDocument |
| // we'll always have the full list of fields w/o needing to keep code in sync |
| fieldNameSet.addAll(doc.getFieldNames()); |
| |
| cloudClient.add(doc); |
| } |
| cloudClient.commit(); |
| |
| fieldNameSet.remove("id"); |
| assertTrue("WTF, bogus field exists?", fieldNameSet.add("bogus_not_in_any_doc_s")); |
| |
| final String[] fieldNames = fieldNameSet.toArray(new String[fieldNameSet.size()]); |
| Arrays.sort(fieldNames); // need determinism when picking random fields |
| |
| for (int i = 0; i < 5; i++) { |
| |
| String q = "*:*"; |
| if (random().nextBoolean()) { |
| q = "id:[* TO " + TestUtil.nextInt(random(),300,numDocs) + "]"; |
| } |
| ModifiableSolrParams baseP = params("rows", "0", "q", q); |
| |
| if (random().nextBoolean()) { |
| baseP.add("fq", "id:[* TO " + TestUtil.nextInt(random(),200,numDocs) + "]"); |
| } |
| |
| final boolean stats = random().nextBoolean(); |
| if (stats) { |
| baseP.add(StatsParams.STATS, "true"); |
| |
| // if we are doing stats, then always generated the same # of STATS_FIELD |
| // params, using multiple tags from a fixed set, but with diff fieldName values. |
| // later, each pivot will randomly pick a tag. |
| baseP.add(StatsParams.STATS_FIELD, "{!key=sk1 tag=st1,st2 "+USE_STATS+"}" + |
| pickRandomStatsFields(fieldNames)); |
| baseP.add(StatsParams.STATS_FIELD, "{!key=sk2 tag=st2,st3 "+USE_STATS+"}" + |
| pickRandomStatsFields(fieldNames)); |
| baseP.add(StatsParams.STATS_FIELD, "{!key=sk3 tag=st3,st4 "+USE_STATS+"}" + |
| pickRandomStatsFields(fieldNames)); |
| // NOTE: there's a chance that some of those stats field names |
| // will be the same, but if so, all the better to test that edge case |
| } |
| |
| ModifiableSolrParams pivotP = params(FACET,"true"); |
| |
| // put our FACET_PIVOT params in a set in case we just happen to pick the same one twice |
| LinkedHashSet<String> pivotParamValues = new LinkedHashSet<String>(); |
| pivotParamValues.add(buildPivotParamValue(buildRandomPivot(fieldNames))); |
| |
| if (random().nextBoolean()) { |
| pivotParamValues.add(buildPivotParamValue(buildRandomPivot(fieldNames))); |
| } |
| pivotP.set(FACET_PIVOT, pivotParamValues.toArray(new String[pivotParamValues.size()])); |
| |
| // keep limit low - lots of unique values, and lots of depth in pivots |
| pivotP.add(FACET_LIMIT, ""+TestUtil.nextInt(random(),1,17)); |
| |
| // sometimes use an offset |
| if (random().nextBoolean()) { |
| pivotP.add(FACET_OFFSET, ""+TestUtil.nextInt(random(),0,7)); |
| } |
| |
| if (random().nextBoolean()) { |
| String min = ""+TestUtil.nextInt(random(),0,numDocs+10); |
| pivotP.add(FACET_PIVOT_MINCOUNT, min); |
| // trace param for validation |
| baseP.add(TRACE_MIN, min); |
| } |
| |
| if (random().nextBoolean()) { |
| String missing = ""+random().nextBoolean(); |
| pivotP.add(FACET_MISSING, missing); |
| // trace param for validation |
| baseP.add(TRACE_MISS, missing); |
| } |
| |
| if (random().nextBoolean()) { |
| String sort = random().nextBoolean() ? "index" : "count"; |
| pivotP.add(FACET_SORT, sort); |
| // trace param for validation |
| baseP.add(TRACE_SORT, sort); |
| } |
| |
| // overrequest |
| // |
| // NOTE: since this test focuses on accuracy of refinement, and doesn't do |
| // control collection comparisons, there isn't a lot of need for excessive |
| // overrequesting -- we focus here on trying to exercise the various edge cases |
| // involved as different values are used with overrequest |
| if (0 == TestUtil.nextInt(random(),0,4)) { |
| // we want a decent chance of no overrequest at all |
| pivotP.add(FACET_OVERREQUEST_COUNT, "0"); |
| pivotP.add(FACET_OVERREQUEST_RATIO, "0"); |
| } else { |
| if (random().nextBoolean()) { |
| pivotP.add(FACET_OVERREQUEST_COUNT, ""+TestUtil.nextInt(random(),0,5)); |
| } |
| if (random().nextBoolean()) { |
| // sometimes give a ratio less then 1, code should be smart enough to deal |
| float ratio = 0.5F + random().nextFloat(); |
| // sometimes go negative |
| if (random().nextBoolean()) { |
| ratio *= -1; |
| } |
| pivotP.add(FACET_OVERREQUEST_RATIO, ""+ratio); |
| } |
| } |
| |
| assertPivotCountsAreCorrect(baseP, pivotP); |
| } |
| } |
| |
| /** |
| * Given some query params, executes the request against the cloudClient and |
| * then walks the pivot facet values in the response, treating each one as a |
| * filter query to assert the pivot counts are correct. |
| */ |
| private void assertPivotCountsAreCorrect(SolrParams baseParams, |
| SolrParams pivotParams) |
| throws SolrServerException { |
| |
| SolrParams initParams = SolrParams.wrapAppended(pivotParams, baseParams); |
| |
| log.info("Doing full run: {}", initParams); |
| countNumFoundChecks = 0; |
| |
| NamedList<List<PivotField>> pivots = null; |
| try { |
| QueryResponse initResponse = cloudClient.query(initParams); |
| pivots = initResponse.getFacetPivot(); |
| assertNotNull(initParams + " has null pivots?", pivots); |
| assertEquals(initParams + " num pivots", |
| initParams.getParams("facet.pivot").length, pivots.size()); |
| } catch (Exception e) { |
| throw new RuntimeException("init query failed: " + initParams + ": " + |
| e.getMessage(), e); |
| } |
| try { |
| for (Map.Entry<String,List<PivotField>> pivot : pivots) { |
| final String pivotKey = pivot.getKey(); |
| // :HACK: for counting the max possible pivot depth |
| final int maxDepth = 1 + pivotKey.length() - pivotKey.replace(",","").length(); |
| |
| assertTraceOk(pivotKey, baseParams, pivot.getValue()); |
| |
| // NOTE: we can't make any assumptions/assertions about the number of |
| // constraints here because of the random data - which means if pivotting is |
| // completely broken and there are no constrains this loop could be a No-Op |
| // but in that case we just have to trust that DistributedFacetPivotTest |
| // will catch it. |
| for (PivotField constraint : pivot.getValue()) { |
| int depth = assertPivotCountsAreCorrect(pivotKey, baseParams, constraint); |
| |
| // we can't assert that the depth reached is the same as the depth requested |
| // because the fq and/or mincount may have pruned the tree too much |
| assertTrue("went too deep: "+depth+": " + pivotKey + " ==> " + pivot, |
| depth <= maxDepth); |
| |
| } |
| } |
| } catch (AssertionError e) { |
| throw new AssertionError(initParams + " ==> " + e.getMessage(), e); |
| } finally { |
| log.info("Ending full run (countNumFoundChecks={}): {}", |
| countNumFoundChecks, initParams); |
| } |
| } |
| |
| /** |
| * Recursive Helper method for asserting that pivot constraint counts match |
| * results when filtering on those constraints. Returns the recursive depth reached |
| * (for sanity checking) |
| */ |
| private int assertPivotCountsAreCorrect(String pivotName, |
| SolrParams baseParams, |
| PivotField constraint) |
| throws SolrServerException { |
| |
| SolrParams p = SolrParams.wrapAppended(baseParams, |
| params("fq", buildFilter(constraint))); |
| List<PivotField> subPivots = null; |
| try { |
| assertPivotData(pivotName, constraint, p); |
| subPivots = constraint.getPivot(); |
| } catch (Exception e) { |
| throw new RuntimeException(pivotName + ": count query failed: " + p + ": " + |
| e.getMessage(), e); |
| } |
| int depth = 0; |
| if (null != subPivots) { |
| assertTraceOk(pivotName, baseParams, subPivots); |
| |
| for (PivotField subPivot : subPivots) { |
| depth = assertPivotCountsAreCorrect(pivotName, p, subPivot); |
| } |
| } |
| return depth + 1; |
| } |
| |
| /** |
| * Executes a query and compares the results with the data available in the |
| * {@link PivotField} constraint -- this method is not recursive, and doesn't |
| * check anything about the sub-pivots (if any). |
| * |
| * @param pivotName pivot name |
| * @param constraint filters on pivot |
| * @param params base solr parameters |
| */ |
| private void assertPivotData(String pivotName, PivotField constraint, SolrParams params) |
| throws SolrServerException, IOException { |
| |
| SolrParams p = SolrParams.wrapDefaults(params("rows","0"), params); |
| QueryResponse res = cloudClient.query(p); |
| String msg = pivotName + ": " + p; |
| |
| assertNumFound(msg, constraint.getCount(), res); |
| |
| if ( p.getBool(StatsParams.STATS, false) ) { |
| // only check stats if stats expected |
| assertPivotStats(msg, constraint, res); |
| } |
| } |
| |
| /** |
| * Compare top level stats in response with stats from pivot constraint |
| */ |
| private void assertPivotStats(String message, PivotField constraint, QueryResponse response) { |
| |
| if (null == constraint.getFieldStatsInfo()) { |
| // no stats for this pivot, nothing to check |
| |
| // TODO: use a trace param to know if/how-many to expect ? |
| log.info("No stats to check for => {}", message); |
| return; |
| } |
| |
| Map<String, FieldStatsInfo> actualFieldStatsInfoMap = response.getFieldStatsInfo(); |
| |
| for (FieldStatsInfo pivotStats : constraint.getFieldStatsInfo().values()) { |
| String statsKey = pivotStats.getName(); |
| |
| FieldStatsInfo actualStats = actualFieldStatsInfoMap.get(statsKey); |
| |
| if (actualStats == null) { |
| // handle case for not found stats (using stats query) |
| // |
| // these has to be a special case check due to the legacy behavior of "top level" |
| // StatsComponent results being "null" (and not even included in the |
| // getFieldStatsInfo() Map due to specila SolrJ logic) |
| |
| log.info("Requested stats missing in verification query, pivot stats: {}", pivotStats); |
| assertEquals("Special Count", 0L, pivotStats.getCount().longValue()); |
| assertEquals("Special Missing", |
| constraint.getCount(), pivotStats.getMissing().longValue()); |
| |
| } else { |
| // regular stats, compare everything... |
| |
| assert actualStats != null; |
| try { |
| String msg = " of " + statsKey; |
| |
| // no wiggle room, these should always be exactly equals, regardless of field type |
| assertEquals("Count" + msg, pivotStats.getCount(), actualStats.getCount()); |
| assertEquals("Missing" + msg, pivotStats.getMissing(), actualStats.getMissing()); |
| assertEquals("Min" + msg, pivotStats.getMin(), actualStats.getMin()); |
| assertEquals("Max" + msg, pivotStats.getMax(), actualStats.getMax()); |
| |
| } catch (AssertionError e) { |
| throw new AssertionError("Stats: Pivot[" + pivotStats + "] <==> Actual[" + actualStats + "] => " + message, e); |
| } |
| } |
| } |
| |
| if (constraint.getFieldStatsInfo().containsKey("sk2")) { // cheeseball hack |
| // if "sk2" was one of hte stats we computed, then we must have also seen |
| // sk1 or sk3 because of the way the tags are fixed |
| assertEquals("had stats sk2, but not another stat?", |
| 2, constraint.getFieldStatsInfo().size()); |
| } else { |
| // if we did not see "sk2", then 1 of the others must be alone |
| assertEquals("only expected 1 stat", |
| 1, constraint.getFieldStatsInfo().size()); |
| assertTrue("not sk1 or sk3", |
| constraint.getFieldStatsInfo().containsKey("sk1") || |
| constraint.getFieldStatsInfo().containsKey("sk3")); |
| } |
| |
| } |
| |
| /** |
| * Verify that the PivotFields we're lookin at doesn't violate any of the expected |
| * behaviors based on the <code>TRACE_*</code> params found in the base params |
| */ |
| private void assertTraceOk(String pivotName, SolrParams baseParams, List<PivotField> constraints) { |
| if (null == constraints || 0 == constraints.size()) { |
| return; |
| } |
| final int maxIdx = constraints.size() - 1; |
| |
| final int min = baseParams.getInt(TRACE_MIN, -1); |
| final boolean expectMissing = baseParams.getBool(TRACE_MISS, false); |
| final boolean checkCount = "count".equals(baseParams.get(TRACE_SORT, "count")); |
| |
| int prevCount = Integer.MAX_VALUE; |
| |
| for (int i = 0; i <= maxIdx; i++) { |
| final PivotField constraint = constraints.get(i); |
| final int count = constraint.getCount(); |
| |
| if (0 < min) { |
| assertTrue(pivotName + ": val #"+i +" of " + maxIdx + |
| ": count("+count+") < facet.mincount("+min+"): " + constraint, |
| min <= count); |
| } |
| // missing value must always come last, but only if facet.missing was used |
| // and may not exist at all (mincount, none missing for this sub-facet, etc...) |
| if ((i < maxIdx) || (!expectMissing)) { |
| assertNotNull(pivotName + ": val #"+i +" of " + maxIdx + |
| " has null value: " + constraint, |
| constraint.getValue()); |
| } |
| // if we are expecting count based sort, then the count of each constraint |
| // must be lt-or-eq the count that came before -- or it must be the last value and |
| // be "missing" |
| if (checkCount) { |
| assertTrue(pivotName + ": val #"+i +" of" + maxIdx + |
| ": count("+count+") > prevCount("+prevCount+"): " + constraint, |
| ((count <= prevCount) |
| || (expectMissing && i == maxIdx && null == constraint.getValue()))); |
| prevCount = count; |
| } |
| } |
| } |
| |
| /** |
| * Given a PivotField constraint, generate a query for the field+value |
| * for use in an <code>fq</code> to verify the constraint count |
| */ |
| private static String buildFilter(PivotField constraint) { |
| Object value = constraint.getValue(); |
| if (null == value) { |
| // facet.missing, exclude any indexed term |
| return "-" + constraint.getField() + ":[* TO *]"; |
| } |
| // otherwise, build up a term filter... |
| String prefix = "{!term f=" + constraint.getField() + "}"; |
| if (value instanceof Date) { |
| return prefix + ((Date) value).toInstant(); |
| } else { |
| return prefix + value; |
| } |
| } |
| |
| |
| /** |
| * Creates a random facet.pivot param string using some of the specified fieldNames |
| */ |
| private static String buildRandomPivot(String[] fieldNames) { |
| final int depth = TestUtil.nextInt(random(), 1, 3); |
| String [] fields = new String[depth]; |
| for (int i = 0; i < depth; i++) { |
| // yes this means we might use the same field twice |
| // makes it a robust test (especially for multi-valued fields) |
| fields[i] = fieldNames[TestUtil.nextInt(random(),0,fieldNames.length-1)]; |
| } |
| return String.join(",", fields); |
| } |
| |
| /** |
| * Picks a random field to use for Stats |
| */ |
| private static String pickRandomStatsFields(String[] fieldNames) { |
| // we need to skip boolean fields when computing stats |
| String fieldName; |
| do { |
| fieldName = fieldNames[TestUtil.nextInt(random(),0,fieldNames.length-1)]; |
| } |
| while(fieldName.endsWith("_b") || fieldName.endsWith("_b1")) ; |
| |
| return fieldName; |
| } |
| |
| /** |
| * Generates a random {@link FacetParams#FACET_PIVOT} value w/ local params |
| * using the specified pivotValue. |
| */ |
| private static String buildPivotParamValue(String pivotValue) { |
| // randomly decide which stat tag to use |
| |
| // if this is 0, or stats aren't enabled, we'll be asking for a tag that doesn't exist |
| // ...which should be fine (just like excluding a tagged fq that doesn't exist) |
| final int statTag = TestUtil.nextInt(random(), -1, 4); |
| |
| if (0 <= statTag) { |
| // only use 1 tag name in the 'stats' localparam - see SOLR-6663 |
| return "{!stats=st"+statTag+"}" + pivotValue; |
| } else { |
| // statTag < 0 == sanity check the case of a pivot w/o any stats |
| return pivotValue; |
| } |
| } |
| |
| /** |
| * Creates a document with randomized field values, some of which be missing values, |
| * some of which will be multi-valued (per the schema) and some of which will be |
| * skewed so that small subsets of the ranges will be more common (resulting in an |
| * increased likelihood of duplicate values) |
| * |
| * @see #buildRandomPivot |
| */ |
| private static SolrInputDocument buildRandomDocument(int id) { |
| SolrInputDocument doc = sdoc("id", id); |
| // most fields are in most docs |
| // if field is in a doc, then "skewed" chance val is from a dense range |
| // (hopefully with lots of duplication) |
| for (String prefix : new String[] { "pivot_i", "pivot_ti" }) { |
| if (useField()) { |
| doc.addField(prefix+"1", skewed(TestUtil.nextInt(random(), 20, 50), |
| random().nextInt())); |
| |
| } |
| if (useField()) { |
| int numMulti = atLeast(1); |
| while (0 < numMulti--) { |
| doc.addField(prefix, skewed(TestUtil.nextInt(random(), 20, 50), |
| random().nextInt())); |
| } |
| } |
| } |
| for (String prefix : new String[] { "pivot_l", "pivot_tl" }) { |
| if (useField()) { |
| doc.addField(prefix+"1", skewed(TestUtil.nextInt(random(), 5000, 5100), |
| random().nextLong())); |
| } |
| if (useField()) { |
| int numMulti = atLeast(1); |
| while (0 < numMulti--) { |
| doc.addField(prefix, skewed(TestUtil.nextInt(random(), 5000, 5100), |
| random().nextLong())); |
| } |
| } |
| } |
| for (String prefix : new String[] { "pivot_f", "pivot_tf" }) { |
| if (useField()) { |
| doc.addField(prefix+"1", skewed(1.0F / random().nextInt(13), |
| random().nextFloat() * random().nextInt())); |
| } |
| if (useField()) { |
| int numMulti = atLeast(1); |
| while (0 < numMulti--) { |
| doc.addField(prefix, skewed(1.0F / random().nextInt(13), |
| random().nextFloat() * random().nextInt())); |
| } |
| } |
| } |
| for (String prefix : new String[] { "pivot_d", "pivot_td" }) { |
| if (useField()) { |
| doc.addField(prefix+"1", skewed(1.0D / random().nextInt(19), |
| random().nextDouble() * random().nextInt())); |
| } |
| if (useField()) { |
| int numMulti = atLeast(1); |
| while (0 < numMulti--) { |
| doc.addField(prefix, skewed(1.0D / random().nextInt(19), |
| random().nextDouble() * random().nextInt())); |
| } |
| } |
| } |
| for (String prefix : new String[] { "pivot_dt", "pivot_tdt" }) { |
| if (useField()) { |
| doc.addField(prefix+"1", skewed(randomSkewedDate(), randomDate())); |
| |
| } |
| if (useField()) { |
| int numMulti = atLeast(1); |
| while (0 < numMulti--) { |
| doc.addField(prefix, skewed(randomSkewedDate(), randomDate())); |
| |
| } |
| } |
| } |
| { |
| String prefix = "pivot_b"; |
| if (useField()) { |
| doc.addField(prefix+"1", random().nextBoolean() ? "t" : "f"); |
| } |
| if (useField()) { |
| int numMulti = atLeast(1); |
| while (0 < numMulti--) { |
| doc.addField(prefix, random().nextBoolean() ? "t" : "f"); |
| } |
| } |
| } |
| for (String prefix : new String[] { "pivot_x_s", "pivot_y_s", "pivot_z_s"}) { |
| if (useField()) { |
| doc.addField(prefix+"1", skewed(TestUtil.randomSimpleString(random(), 1, 1), |
| randomXmlUsableUnicodeString())); |
| } |
| if (useField()) { |
| int numMulti = atLeast(1); |
| while (0 < numMulti--) { |
| doc.addField(prefix, skewed(TestUtil.randomSimpleString(random(), 1, 1), |
| randomXmlUsableUnicodeString())); |
| } |
| } |
| } |
| |
| // |
| // for the remaining fields, make every doc have a value in a dense range |
| // |
| |
| for (String prefix : new String[] { "dense_pivot_x_s", "dense_pivot_y_s" }) { |
| if (useField()) { |
| doc.addField(prefix+"1", TestUtil.randomSimpleString(random(), 1, 1)); |
| } |
| if (useField()) { |
| int numMulti = atLeast(1); |
| while (0 < numMulti--) { |
| doc.addField(prefix, TestUtil.randomSimpleString(random(), 1, 1)); |
| } |
| } |
| } |
| for (String prefix : new String[] { "dense_pivot_i", "dense_pivot_ti" }) { |
| if (useField()) { |
| doc.addField(prefix+"1", TestUtil.nextInt(random(), 20, 50)); |
| } |
| if (useField()) { |
| int numMulti = atLeast(1); |
| while (0 < numMulti--) { |
| doc.addField(prefix, TestUtil.nextInt(random(), 20, 50)); |
| } |
| } |
| } |
| |
| return doc; |
| } |
| |
| /** |
| * Similar to usually() but we want it to happen just as often regardless |
| * of test multiplier and nightly status |
| * |
| * @see #useFieldRandomizedFactor |
| */ |
| private static boolean useField() { |
| assert 0 < useFieldRandomizedFactor; |
| return 0 != TestUtil.nextInt(random(), 0, useFieldRandomizedFactor); |
| } |
| |
| /** |
| * Asserts the number of docs found in the response |
| */ |
| private void assertNumFound(String msg, int expected, QueryResponse response) { |
| |
| countNumFoundChecks++; |
| |
| assertEquals(msg, expected, response.getResults().getNumFound()); |
| } |
| |
| /** |
| * @see #assertNumFound |
| * @see #assertPivotCountsAreCorrect(SolrParams,SolrParams) |
| */ |
| private int countNumFoundChecks = 0; |
| |
| } |