diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index fb936ab80f6..77cb773658c 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -281,6 +281,10 @@ Other Changes * SOLR-14876: Upgrade to zookeeper 3.6.2 (odidev via Erick Erickson) +* SOLR-14333: Implement toString in Collapse filter so that proper parsed queries returned in debug response. + Also, Deprecate unused constants NULL_COLLAPSE, NULL_IGNORE, NULL_EXPAND, HINT_MULTI_DOCVALUES in collapse parser. + (Guna Sekhar Dora Kovvuru, Munendra S N, Mike Drob) + ================== 8.6.2 ================== Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release. diff --git a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java index 05ae26eb816..9a1e740bd8f 100644 --- a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java +++ b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java @@ -122,12 +122,57 @@ import static org.apache.solr.common.params.CommonParams.SORT; public class CollapsingQParserPlugin extends QParserPlugin { public static final String NAME = "collapse"; - public static final String NULL_COLLAPSE = "collapse"; - public static final String NULL_IGNORE = "ignore"; - public static final String NULL_EXPAND = "expand"; public static final String HINT_TOP_FC = "top_fc"; + + /** + * @deprecated use {@link NullPolicy} instead. + */ + @Deprecated + public static final String NULL_COLLAPSE = "collapse"; + @Deprecated + public static final String NULL_IGNORE = "ignore"; + @Deprecated + public static final String NULL_EXPAND = "expand"; + @Deprecated public static final String HINT_MULTI_DOCVALUES = "multi_docvalues"; + public enum NullPolicy { + IGNORE("ignore", 0), + COLLAPSE("collapse", 1), + EXPAND("expand", 2); + + private final String name; + private final int code; + + NullPolicy(String name, int code) { + this.name = name; + this.code = code; + } + + public String getName() { + return name; + } + + public int getCode() { + return code; + } + + public static NullPolicy fromString(String nullPolicy) { + if (StringUtils.isEmpty(nullPolicy)) { + return DEFAULT_POLICY; + } + switch (nullPolicy) { + case "ignore": return IGNORE; + case "collapse": return COLLAPSE; + case "expand": return EXPAND; + default: + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid nullPolicy: " + nullPolicy); + } + } + + static NullPolicy DEFAULT_POLICY = IGNORE; + } + public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest request) { return new CollapsingQParser(qstr, localParams, params, request); @@ -188,6 +233,11 @@ public class CollapsingQParserPlugin extends QParserPlugin { return 17 * (31 + selectorText.hashCode()) * (31 + type.hashCode()); } + @Override + public String toString(){ + return "GroupHeadSelector(selectorText=" + this.selectorText + ", type=" +this.type + ")"; + } + /** * returns a new GroupHeadSelector based on the specified local params */ @@ -221,11 +271,8 @@ public class CollapsingQParserPlugin extends QParserPlugin { public String hint; private boolean needsScores = true; private boolean needsScores4Collapsing = false; - private int nullPolicy; + private NullPolicy nullPolicy; private Set boosted; // ordered by "priority" - public static final int NULL_POLICY_IGNORE = 0; - public static final int NULL_POLICY_COLLAPSE = 1; - public static final int NULL_POLICY_EXPAND = 2; private int size; public String getField(){ @@ -254,7 +301,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { int hashCode = classHash(); hashCode = 31 * hashCode + collapseField.hashCode(); hashCode = 31 * hashCode + groupHeadSelector.hashCode(); - hashCode = 31 * hashCode + nullPolicy; + hashCode = 31 * hashCode + nullPolicy.hashCode(); return hashCode; } @@ -279,7 +326,12 @@ public class CollapsingQParserPlugin extends QParserPlugin { } public String toString(String s) { - return s; + return "CollapsingPostFilter(field=" + this.collapseField + + ", nullPolicy=" + this.nullPolicy.getName() + ", " + + this.groupHeadSelector + + (hint == null ? "": ", hint=" + this.hint) + + ", size=" + this.size + + ")"; } public CollapsingPostFilter(SolrParams localParams, SolrParams params, SolrQueryRequest request) { @@ -354,16 +406,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { } } - String nPolicy = localParams.get("nullPolicy", NULL_IGNORE); - if(nPolicy.equals(NULL_IGNORE)) { - this.nullPolicy = NULL_POLICY_IGNORE; - } else if (nPolicy.equals(NULL_COLLAPSE)) { - this.nullPolicy = NULL_POLICY_COLLAPSE; - } else if(nPolicy.equals((NULL_EXPAND))) { - this.nullPolicy = NULL_POLICY_EXPAND; - } else { - throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid nullPolicy:"+nPolicy); - } + this.nullPolicy = NullPolicy.fromString(localParams.get("nullPolicy")); } @SuppressWarnings({"unchecked"}) @@ -392,7 +435,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { return collectorFactory.getCollector(this.collapseField, this.groupHeadSelector, this.sortSpec, - this.nullPolicy, + this.nullPolicy.getCode(), this.hint, this.needsScores4Collapsing, this.needsScores, @@ -552,7 +595,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { this.ords = new IntIntDynamicMap(valueCount, -1); this.scores = new IntFloatDynamicMap(valueCount, -Float.MAX_VALUE); this.nullPolicy = nullPolicy; - if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + if(nullPolicy == NullPolicy.EXPAND.getCode()) { nullScores = new FloatArrayList(); } @@ -620,13 +663,13 @@ public class CollapsingQParserPlugin extends QParserPlugin { ords.put(ord, globalDoc); scores.put(ord, score); } - } else if(nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(nullPolicy == NullPolicy.COLLAPSE.getCode()) { float score = scorer.score(); if(score > nullScore) { nullScore = score; nullDoc = globalDoc; } - } else if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(nullPolicy == NullPolicy.EXPAND.getCode()) { collapsedSet.set(globalDoc); nullScores.add(scorer.score()); } @@ -716,9 +759,9 @@ public class CollapsingQParserPlugin extends QParserPlugin { dummy.score = scores.get(ord); } else if(boosts && mergeBoost.boost(docId)) { //Ignore so it doesn't mess up the null scoring. - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { dummy.score = nullScore; - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { dummy.score = nullScores.get(++index); } @@ -772,7 +815,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { this.collapsedSet = new FixedBitSet(maxDoc); this.nullValue = nullValue; this.nullPolicy = nullPolicy; - if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + if(nullPolicy == NullPolicy.EXPAND.getCode()) { nullScores = new FloatArrayList(); } this.cmap = new IntLongHashMap(size); @@ -839,13 +882,13 @@ public class CollapsingQParserPlugin extends QParserPlugin { long scoreDoc = (((long)Float.floatToRawIntBits(score))<<32)+globalDoc; cmap.indexInsert(idx, collapseValue, scoreDoc); } - } else if(nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(nullPolicy == NullPolicy.COLLAPSE.getCode()) { float score = scorer.score(); if(score > this.nullScore) { this.nullScore = score; this.nullDoc = globalDoc; } - } else if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(nullPolicy == NullPolicy.EXPAND.getCode()) { collapsedSet.set(globalDoc); nullScores.add(scorer.score()); } @@ -917,9 +960,9 @@ public class CollapsingQParserPlugin extends QParserPlugin { dummy.score = Float.intBitsToFloat((int)(scoreDoc>>32)); } else if(boosts && mergeBoost.boost(globalDoc)) { //Ignore so boosted documents don't mess up the null scoring policies. - } else if (nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if (nullPolicy == NullPolicy.COLLAPSE.getCode()) { dummy.score = nullScore; - } else if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(nullPolicy == NullPolicy.EXPAND.getCode()) { dummy.score = nullScores.get(nullScoreIndex++); } @@ -1114,9 +1157,9 @@ public class CollapsingQParserPlugin extends QParserPlugin { } else if (mergeBoost != null && mergeBoost.boost(globalDoc)) { //It's an elevated doc so no score is needed dummy.score = 0F; - } else if (nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if (nullPolicy == NullPolicy.COLLAPSE.getCode()) { dummy.score = nullScore; - } else if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(nullPolicy == NullPolicy.EXPAND.getCode()) { dummy.score = nullScores.get(nullScoreIndex++); } } @@ -1273,9 +1316,9 @@ public class CollapsingQParserPlugin extends QParserPlugin { } else if (mergeBoost != null && mergeBoost.boost(globalDoc)) { //Its an elevated doc so no score is needed dummy.score = 0F; - } else if (nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if (nullPolicy == NullPolicy.COLLAPSE.getCode()) { dummy.score = nullScore; - } else if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(nullPolicy == NullPolicy.EXPAND.getCode()) { dummy.score = nullScores.get(nullScoreIndex++); } } @@ -1538,7 +1581,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { if (this.needsScores) { this.scores = new IntFloatDynamicMap(valueCount, 0.0f); - if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + if(nullPolicy == NullPolicy.EXPAND.getCode()) { nullScores = new FloatArrayList(); } } @@ -1648,7 +1691,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { scores.put(ord, scorer.score()); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { if(comp.test(currentVal, nullVal)) { nullVal = currentVal; nullDoc = globalDoc; @@ -1656,7 +1699,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { nullScore = scorer.score(); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { this.collapsedSet.set(globalDoc); if(needsScores) { nullScores.add(scorer.score()); @@ -1729,7 +1772,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { scores.put(ord, scorer.score()); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { if(comp.test(currentVal, nullVal)) { nullVal = currentVal; nullDoc = globalDoc; @@ -1737,7 +1780,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { nullScore = scorer.score(); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { this.collapsedSet.set(globalDoc); if(needsScores) { nullScores.add(scorer.score()); @@ -1807,7 +1850,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { scores.put(ord, scorer.score()); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { if(comp.test(currentVal, nullVal)) { nullVal = currentVal; nullDoc = globalDoc; @@ -1815,7 +1858,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { nullScore = scorer.score(); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { this.collapsedSet.set(globalDoc); if(needsScores) { nullScores.add(scorer.score()); @@ -1900,7 +1943,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { scores.put(ord, score); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { if(comp.test(currentVal, nullVal)) { nullVal = currentVal; nullDoc = globalDoc; @@ -1911,7 +1954,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { nullScore = score; } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { this.collapsedSet.set(globalDoc); if(needsScores) { if (!needsScores4Collapsing) { @@ -2001,7 +2044,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { } } } - } else if (this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if (this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { if (-1 == nullDoc) { // we've never seen a doc with null collapse key yet, treat it as the null group head for now compareState.setNullGroupValues(contextDoc); @@ -2024,7 +2067,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { } } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { this.collapsedSet.set(globalDoc); if (needsScores) { if (!needsScores4Collapsing) { @@ -2094,7 +2137,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { if(needsScores) { this.scores = new IntFloatDynamicMap(size, 0.0f); - if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + if(nullPolicy == NullPolicy.EXPAND.getCode()) { nullScores = new FloatArrayList(); } } @@ -2236,7 +2279,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { scores.put(index, scorer.score()); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { if(comp.test(currentVal, nullCompVal)) { nullCompVal = currentVal; nullDoc = globalDoc; @@ -2244,7 +2287,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { nullScore = scorer.score(); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { this.collapsedSet.set(globalDoc); if(needsScores) { nullScores.add(scorer.score()); @@ -2329,7 +2372,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { scores.put(index, scorer.score()); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { if(comp.test(currentVal, nullCompVal)) { nullCompVal = currentVal; nullDoc = globalDoc; @@ -2337,7 +2380,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { nullScore = scorer.score(); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { this.collapsedSet.set(globalDoc); if(needsScores) { nullScores.add(scorer.score()); @@ -2445,7 +2488,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { scores.put(index, score); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { if(comp.test(currentVal, nullCompVal)) { nullCompVal = currentVal; nullDoc = globalDoc; @@ -2456,7 +2499,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { nullScore = score; } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { this.collapsedSet.set(globalDoc); if(needsScores) { if (!needsScores4Collapsing) { @@ -2556,7 +2599,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { scores.put(index, score); } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) { + } else if(this.nullPolicy == NullPolicy.COLLAPSE.getCode()) { if (-1 == nullDoc) { // we've never seen a doc with null collapse key yet, treat it as the null group head for now compareState.setNullGroupValues(contextDoc); @@ -2579,7 +2622,7 @@ public class CollapsingQParserPlugin extends QParserPlugin { } } } - } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) { + } else if(this.nullPolicy == NullPolicy.EXPAND.getCode()) { this.collapsedSet.set(globalDoc); if (needsScores) { if (!needsScores4Collapsing) { diff --git a/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java b/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java index 9149c194288..e384d84a278 100644 --- a/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java +++ b/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java @@ -16,9 +16,10 @@ */ package org.apache.solr.search; -import java.util.List; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.apache.lucene.util.TestUtil; import org.apache.solr.CursorPagingTest; @@ -27,14 +28,14 @@ import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.params.SolrParams; -import static org.apache.solr.search.CollapsingQParserPlugin.NULL_IGNORE; -import static org.apache.solr.search.CollapsingQParserPlugin.NULL_COLLAPSE; -import static org.apache.solr.search.CollapsingQParserPlugin.NULL_EXPAND; import org.junit.AfterClass; import org.junit.BeforeClass; +import static org.hamcrest.core.StringContains.containsString; + public class TestRandomCollapseQParserPlugin extends SolrTestCaseJ4 { /** Full SolrServer instance for arbitrary introspection of response data and adding fqs */ @@ -43,7 +44,9 @@ public class TestRandomCollapseQParserPlugin extends SolrTestCaseJ4 { public static List ALL_COLLAPSE_FIELD_NAMES; private static String[] NULL_POLICIES - = new String[] {NULL_IGNORE, NULL_COLLAPSE, NULL_EXPAND}; + = new String[] {CollapsingQParserPlugin.NullPolicy.IGNORE.getName(), + CollapsingQParserPlugin.NullPolicy.COLLAPSE.getName(), + CollapsingQParserPlugin.NullPolicy.EXPAND.getName()}; @BeforeClass public static void buildIndexAndClient() throws Exception { @@ -143,7 +146,7 @@ public class TestRandomCollapseQParserPlugin extends SolrTestCaseJ4 { "" : " size=" + TestUtil.nextInt(random(),1,10000); final String nullPolicy = randomNullPolicy(); - final String nullPs = NULL_IGNORE.equals(nullPolicy) + final String nullPs = nullPolicy.equals(CollapsingQParserPlugin.NullPolicy.IGNORE.getName()) // ignore is default, randomly be explicit about it ? (random().nextBoolean() ? "" : " nullPolicy=ignore") : (" nullPolicy=" + nullPolicy); @@ -162,14 +165,14 @@ public class TestRandomCollapseQParserPlugin extends SolrTestCaseJ4 { final Object collapseVal = doc.getFieldValue(collapseField); if (null == collapseVal) { - if (NULL_EXPAND.equals(nullPolicy)) { + if (nullPolicy.equals(CollapsingQParserPlugin.NullPolicy.EXPAND.getName())) { // nothing to check for this doc, it's in its own group continue; } assertFalse(groupHeadId + " has null collapseVal but nullPolicy==ignore; " + "mainP: " + mainP + ", collapseP: " + collapseP, - NULL_IGNORE.equals(nullPolicy)); + nullPolicy.equals(CollapsingQParserPlugin.NullPolicy.IGNORE.getName())); } // workaround for SOLR-8082... @@ -204,6 +207,40 @@ public class TestRandomCollapseQParserPlugin extends SolrTestCaseJ4 { } } } + + public void testParsedFilterQueryResponse() throws Exception { + String nullPolicy = randomNullPolicy(); + String groupHeadSort = "'_version_ asc'"; + String collapseSize = "5000"; + String collapseHint = "top_fc"; + String filterQuery = "{!collapse field=id sort=" + groupHeadSort + " nullPolicy=" + nullPolicy + " size=" + + collapseSize + " hint=" + collapseHint + "}"; + SolrParams solrParams = params("q", "*:*", "rows", "0", "debug", "true", "fq", filterQuery); + + QueryResponse response = SOLR.query(solrParams); + // Query name is occurring twice, this should be handled in QueryParsing.toString + String expectedParsedFilterString = "CollapsingPostFilter(CollapsingPostFilter(field=id, " + + "nullPolicy=" + nullPolicy + ", GroupHeadSelector(selectorText=" + groupHeadSort.substring(1, + groupHeadSort.length() - 1) + ", type=SORT" + + "), hint=" + collapseHint + ", size=" + collapseSize + "))"; + List expectedParsedFilterQuery = Collections.singletonList(expectedParsedFilterString); + assertEquals(expectedParsedFilterQuery, response.getDebugMap().get("parsed_filter_queries")); + assertEquals(Collections.singletonList(filterQuery), response.getDebugMap().get("filter_queries")); + } + + public void testNullPolicy() { + String nullPolicy = "xyz"; + String groupHeadSort = "'_version_ asc'"; + String filterQuery = "{!collapse field=id sort=" + groupHeadSort + " nullPolicy=" + nullPolicy + "}"; + SolrParams solrParams = params("q", "*:*", "fq", filterQuery); + + SolrException e = expectThrows(SolrException.class, () -> SOLR.query(solrParams)); + assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code()); + assertThat(e.getMessage(), containsString("Invalid nullPolicy: " + nullPolicy)); + + // valid nullPolicy + assertQ(req("q", "*:*", "fq", "{!collapse field=id nullPolicy=" + randomNullPolicy() + "}")); + } private String randomNullPolicy() { return NULL_POLICIES[ TestUtil.nextInt(random(), 0, NULL_POLICIES.length-1) ];