SOLR-14333: Implement toString in Collapse filter (#1371)

This commit is contained in:
Guna Sekhar Dora Kovvuru 2020-09-29 19:19:49 +05:30 committed by GitHub
parent 2b692ccb71
commit 1dba76c0d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 146 additions and 62 deletions

View File

@ -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.

View File

@ -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<BytesRef> 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) {

View File

@ -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<String> 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...
@ -205,6 +208,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<String> 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) ];
}