You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by tf...@apache.org on 2015/04/23 21:27:34 UTC
svn commit: r1675706 - in /lucene/dev/trunk/solr: ./
core/src/java/org/apache/solr/request/
core/src/test-files/solr/collection1/conf/ core/src/test/org/apache/solr/
core/src/test/org/apache/solr/request/
solrj/src/java/org/apache/solr/common/params/
Author: tflobbe
Date: Thu Apr 23 19:27:34 2015
New Revision: 1675706
URL: http://svn.apache.org/r1675706
Log:
SOLR-7406: Add facet.range.method parameter with options 'filter' and 'dv' for range faceting
Modified:
lucene/dev/trunk/solr/CHANGES.txt
lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema.xml
lucene/dev/trunk/solr/core/src/test/org/apache/solr/TestDistributedSearch.java
lucene/dev/trunk/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java
Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1675706&r1=1675705&r2=1675706&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Thu Apr 23 19:27:34 2015
@@ -114,6 +114,10 @@ New Features
* SOLR-7417: JSON Facet API - unique() is now implemented for numeric and date fields.
(yonik)
+* SOLR-7406: Add a new "facet.range.method" parameter to let users choose how to do range
+ faceting between an implementation based on filters (previous algorithm, using
+ "facet.range.method=filter") or DocValues ("facet.range.method=dv").
+ Input parameters and output of both methods are the same. (Tomás Fernández Löbbe)
Bug Fixes
----------------------
Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/IntervalFacets.java?rev=1675706&r1=1675705&r2=1675706&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/IntervalFacets.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/IntervalFacets.java Thu Apr 23 19:27:34 2015
@@ -104,6 +104,10 @@ public class IntervalFacets implements I
private final DocSet docs;
private final FacetInterval[] intervals;
+ /**
+ * Constructor that accepts un-parsed intervals using "interval faceting" syntax. See {@link IntervalFacets} for syntax.
+ * Intervals don't need to be in order.
+ */
public IntervalFacets(SchemaField schemaField, SolrIndexSearcher searcher, DocSet docs, String[] intervals, SolrParams params) throws SyntaxError, IOException {
this.schemaField = schemaField;
this.searcher = searcher;
@@ -111,6 +115,18 @@ public class IntervalFacets implements I
this.intervals = getSortedIntervals(intervals, params);
doCount();
}
+
+ /**
+ * Constructor that accepts an already constructed array of {@link FacetInterval} objects. This array needs to be sorted
+ * by start value in weakly ascending order. null values are not allowed in the array.
+ */
+ IntervalFacets(SchemaField schemaField, SolrIndexSearcher searcher, DocSet docs, FacetInterval[] intervals) throws IOException {
+ this.schemaField = schemaField;
+ this.searcher = searcher;
+ this.docs = docs;
+ this.intervals = intervals;
+ doCount();
+ }
private FacetInterval[] getSortedIntervals(String[] intervals, SolrParams params) throws SyntaxError {
FacetInterval[] sortedIntervals = new FacetInterval[intervals.length];
@@ -406,6 +422,14 @@ public class IntervalFacets implements I
*/
private int count;
+ /**
+ *
+ * Constructor that accepts un-parsed interval faceting syntax. See {@link IntervalFacets} for details
+ *
+ * @param schemaField schemaField for this range
+ * @param intervalStr String the interval. See {@link IntervalFacets} for syntax
+ * @param params SolrParams of this request, mostly used to get local params
+ */
FacetInterval(SchemaField schemaField, String intervalStr, SolrParams params) throws SyntaxError {
if (intervalStr == null) throw new SyntaxError("empty facet interval");
intervalStr = intervalStr.trim();
@@ -484,6 +508,31 @@ public class IntervalFacets implements I
}
/**
+ *
+ * Constructor that accepts already parsed values of start and end. This constructor
+ * can only be used with numeric field types.
+ *
+ * @param schemaField schemaField for this range
+ * @param startStr String representation of the start value of this interval. Can be a "*".
+ * @param endStr String representation of the end value of this interval. Can be a "*".
+ * @param includeLower Indicates weather this interval should include values equal to start
+ * @param includeUpper Indicates weather this interval should include values equal to end
+ * @param key String key of this interval
+ */
+ FacetInterval(SchemaField schemaField, String startStr, String endStr,
+ boolean includeLower, boolean includeUpper, String key) {
+ assert schemaField.getType().getNumericType() != null: "Only numeric fields supported with this constructor";
+ this.key = key;
+ this.startOpen = !includeLower;
+ this.endOpen = !includeUpper;
+ this.start = getLimitFromString(schemaField, startStr);
+ this.end = getLimitFromString(schemaField, endStr);
+ assert start == null || end == null || start.compareTo(end) < 0:
+ "Bad start/end limits: " + startStr + "/" + endStr;
+ setNumericLimits(schemaField);
+ }
+
+ /**
* Set startLimit and endLimit for numeric values. The limits in this case
* are going to be the <code>long</code> representation of the original
* value. <code>startLimit</code> will be incremented by one in case of the
@@ -554,6 +603,10 @@ public class IntervalFacets implements I
if (value.length() == 0) {
throw new SyntaxError("Empty interval limit");
}
+ return getLimitFromString(schemaField, value);
+ }
+
+ private BytesRef getLimitFromString(SchemaField schemaField, String value) {
if ("*".equals(value)) {
return null;
}
Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SimpleFacets.java?rev=1675706&r1=1675705&r2=1675706&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SimpleFacets.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SimpleFacets.java Thu Apr 23 19:27:34 2015
@@ -18,6 +18,7 @@
package org.apache.solr.request;
import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
import org.apache.lucene.index.Fields;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
@@ -46,6 +47,7 @@ import org.apache.solr.common.SolrExcept
import org.apache.solr.common.params.CommonParams;
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.GroupParams;
import org.apache.solr.common.params.RequiredSolrParams;
@@ -106,6 +108,8 @@ import java.util.concurrent.TimeUnit;
* to leverage any of its functionality.
*/
public class SimpleFacets {
+
+ private final static Logger log = Logger.getLogger(SimpleFacets.class);
/** The main set of documents all facet counts should be relative to */
protected DocSet docsOrig;
@@ -1083,6 +1087,15 @@ public class SimpleFacets {
parseParams(FacetParams.FACET_RANGE, facetRange);
String f = facetValue;
+ String methodStr = params.get(FacetParams.FACET_RANGE_METHOD);
+ FacetRangeMethod method = (methodStr==null?FacetRangeMethod.getDefault():FacetRangeMethod.get(methodStr));
+ boolean groupFacet = params.getBool(GroupParams.GROUP_FACET, false);
+ if (groupFacet && method.equals(FacetRangeMethod.DV)) {
+ // the user has explicitly selected the FacetRangeMethod.DV method
+ log.warn("Range facet method '" + FacetRangeMethod.DV + "' is not supported together with '" +
+ GroupParams.GROUP_FACET + "'. Will use method '" + FacetRangeMethod.FILTER + "' instead");
+ method = FacetRangeMethod.FILTER;
+ }
final SchemaField sf = schema.getField(f);
final FieldType ft = sf.getType();
@@ -1115,16 +1128,26 @@ public class SimpleFacets {
}
} else if (ft instanceof DateRangeField) {
calc = new DateRangeFieldEndpointCalculator(sf, null);
+ if (method.equals(FacetRangeMethod.DV)) {
+ // the user has explicitly selected the FacetRangeMethod.DV method
+ log.warn("Range facet method '" + FacetRangeMethod.DV + "' is not supported together with field type '" +
+ DateRangeField.class + "'. Will use method '" + FacetRangeMethod.FILTER + "' instead");
+ method = FacetRangeMethod.FILTER;
+ }
} else {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
"Unable to range facet on field:" + sf);
}
-
- resOuter.add(key, getFacetRangeCounts(sf, calc));
+ if (method.equals(FacetRangeMethod.DV)) {
+ assert ft instanceof TrieField;
+ resOuter.add(key, getFacetRangeCountsDocValues(sf, calc));
+ } else {
+ resOuter.add(key, getFacetRangeCounts(sf, calc));
+ }
}
- private <T extends Comparable<T>> NamedList getFacetRangeCounts
+ private <T extends Comparable<T>> NamedList<Object> getFacetRangeCounts
(final SchemaField sf,
final RangeEndpointCalculator<T> calc) throws IOException {
@@ -1251,6 +1274,183 @@ public class SimpleFacets {
return res;
}
+ private <T extends Comparable<T>> NamedList<Object> getFacetRangeCountsDocValues(final SchemaField sf,
+ final RangeEndpointCalculator<T> calc) throws IOException {
+
+ final String f = sf.getName();
+ final NamedList<Object> res = new SimpleOrderedMap<>();
+ final NamedList<Integer> counts = new NamedList<>();
+ res.add("counts", counts);
+
+ String globalStartS = required.getFieldParam(f,FacetParams.FACET_RANGE_START);
+ String globalEndS = required.getFieldParam(f,FacetParams.FACET_RANGE_END);
+
+ final T start = calc.getValue(globalStartS);
+ // not final, hardend may change this
+ T end = calc.getValue(globalEndS);
+ if (end.compareTo(start) < 0) {
+ throw new SolrException
+ (SolrException.ErrorCode.BAD_REQUEST,
+ "range facet 'end' comes before 'start': "+end+" < "+start);
+ }
+
+ final String gap = required.getFieldParam(f, FacetParams.FACET_RANGE_GAP);
+ // explicitly return the gap. compute this early so we are more
+ // likely to catch parse errors before attempting math
+ res.add("gap", calc.getGap(gap));
+
+ final int minCount = params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0);
+
+ final EnumSet<FacetRangeInclude> include = FacetRangeInclude.parseParam
+ (params.getFieldParams(f,FacetParams.FACET_RANGE_INCLUDE));
+ ArrayList<IntervalFacets.FacetInterval> intervals = new ArrayList<>();
+
+ final String[] othersP =
+ params.getFieldParams(f,FacetParams.FACET_RANGE_OTHER);
+
+ boolean includeBefore = false;
+ boolean includeBetween = false;
+ boolean includeAfter = false;
+
+ if (othersP != null && othersP.length > 0) {
+ Set<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
+ // Intervals must be in order (see IntervalFacets.getSortedIntervals), if "BEFORE" or
+ // "BETWEEN" are set, they must be added first
+ for (final String o : othersP) {
+ others.add(FacetRangeOther.get(o));
+ }
+ // no matter what other values are listed, we don't do
+ // anything if "none" is specified.
+ if (!others.contains(FacetRangeOther.NONE)) {
+
+ if (others.contains(FacetRangeOther.ALL) || others.contains(FacetRangeOther.BEFORE)) {
+ // We'll add an interval later in this position
+ intervals.add(null);
+ includeBefore = true;
+ }
+
+ if (others.contains(FacetRangeOther.ALL) || others.contains(FacetRangeOther.BETWEEN)) {
+ // We'll add an interval later in this position
+ intervals.add(null);
+ includeBetween = true;
+ }
+
+ if (others.contains(FacetRangeOther.ALL) || others.contains(FacetRangeOther.AFTER)) {
+ includeAfter = true;
+ }
+ }
+
+ }
+
+
+ T low = start;
+
+ while (low.compareTo(end) < 0) {
+ T high = calc.addGap(low, gap);
+ if (end.compareTo(high) < 0) {
+ if (params.getFieldBool(f,FacetParams.FACET_RANGE_HARD_END,false)) {
+ high = end;
+ } else {
+ end = high;
+ }
+ }
+ if (high.compareTo(low) < 0) {
+ throw new SolrException
+ (SolrException.ErrorCode.BAD_REQUEST,
+ "range facet infinite loop (is gap negative? did the math overflow?)");
+ }
+ if (high.compareTo(low) == 0) {
+ throw new SolrException
+ (SolrException.ErrorCode.BAD_REQUEST,
+ "range facet infinite loop: gap is either zero, or too small relative start/end and caused underflow: " + low + " + " + gap + " = " + high );
+ }
+
+ final boolean includeLower =
+ (include.contains(FacetRangeInclude.LOWER) ||
+ (include.contains(FacetRangeInclude.EDGE) &&
+ 0 == low.compareTo(start)));
+ final boolean includeUpper =
+ (include.contains(FacetRangeInclude.UPPER) ||
+ (include.contains(FacetRangeInclude.EDGE) &&
+ 0 == high.compareTo(end)));
+
+ final String lowS = calc.formatValue(low);
+ final String highS = calc.formatValue(high);
+
+ intervals.add(new IntervalFacets.FacetInterval(sf, lowS, highS, includeLower, includeUpper, lowS));
+
+ low = high;
+ }
+
+ if (includeBefore) {
+ // include upper bound if "outer" or if first gap doesn't already include it
+ intervals.set(0, new IntervalFacets.FacetInterval(sf, "*", globalStartS, true,
+ include.contains(FacetRangeInclude.OUTER) ||
+ (! (include.contains(FacetRangeInclude.LOWER) ||
+ include.contains(FacetRangeInclude.EDGE))), FacetRangeOther.BEFORE.toString()));
+ }
+
+ if (includeBetween) {
+ int intervalIndex = (includeBefore?1:0);
+ intervals.set(intervalIndex, new IntervalFacets.FacetInterval(sf, globalStartS, calc.formatValue(end),
+ include.contains(FacetRangeInclude.LOWER) ||
+ include.contains(FacetRangeInclude.EDGE),
+ include.contains(FacetRangeInclude.UPPER) ||
+ include.contains(FacetRangeInclude.EDGE),
+ FacetRangeOther.BETWEEN.toString()));
+ }
+
+ if (includeAfter) {
+ // include lower bound if "outer" or if last gap doesn't already include it
+ intervals.add(new IntervalFacets.FacetInterval(sf, calc.formatValue(end), "*",
+ (include.contains(FacetRangeInclude.OUTER) ||
+ (! (include.contains(FacetRangeInclude.UPPER) ||
+ include.contains(FacetRangeInclude.EDGE)))),
+ false, FacetRangeOther.AFTER.toString()));
+ }
+
+ IntervalFacets.FacetInterval[] intervalsArray = intervals.toArray(new IntervalFacets.FacetInterval[intervals.size()]);
+ // don't use the ArrayList anymore
+ intervals = null;
+
+ new IntervalFacets(sf, searcher, docs, intervalsArray);
+
+ int intervalIndex = 0;
+ int lastIntervalIndex = intervalsArray.length - 1;
+ // if the user requested "BEFORE", it will be the first of the intervals. Needs to be added to the
+ // response named list instead of with the counts
+ if (includeBefore) {
+ res.add(intervalsArray[intervalIndex].getKey(), intervalsArray[intervalIndex].getCount());
+ intervalIndex++;
+ }
+
+ // if the user requested "BETWEEN", it will be the first or second of the intervals (depending on if
+ // "BEFORE" was also requested). Needs to be added to the response named list instead of with the counts
+ if (includeBetween) {
+ res.add(intervalsArray[intervalIndex].getKey(), intervalsArray[intervalIndex].getCount());
+ intervalIndex++;
+ }
+
+ // if the user requested "AFTER", it will be the last of the intervals.
+ // Needs to be added to the response named list instead of with the counts
+ if (includeAfter) {
+ res.add(intervalsArray[lastIntervalIndex].getKey(), intervalsArray[lastIntervalIndex].getCount());
+ lastIntervalIndex--;
+ }
+ // now add all other intervals to the counts NL
+ while (intervalIndex <= lastIntervalIndex) {
+ FacetInterval interval = intervalsArray[intervalIndex];
+ if (interval.getCount() >= minCount) {
+ counts.add(interval.getKey(), interval.getCount());
+ }
+ intervalIndex++;
+ }
+
+ res.add("start", start);
+ res.add("end", end);
+ return res;
+}
+
/**
* Macro for getting the numDocs of range over docs
* @see SolrIndexSearcher#numDocs
Modified: lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema.xml
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema.xml?rev=1675706&r1=1675705&r2=1675706&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema.xml (original)
+++ lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema.xml Thu Apr 23 19:27:34 2015
@@ -640,6 +640,15 @@
<dynamicField name="*_ancestor" type="ancestor_path" indexed="true" stored="true" omitNorms="true" multiValued="true" />
<dynamicField name="*_sev_enum" type="severityType" indexed="true" stored="true" docValues="true" multiValued="true" />
+
+ <!-- With DocValues=true -->
+ <dynamicField name="*_i_dv" type="int" indexed="true" stored="true" docValues="true"/>
+ <dynamicField name="*_l_dv" type="long" indexed="true" stored="true" docValues="true"/>
+ <dynamicField name="*_f_dv" type="float" indexed="true" stored="true" docValues="true"/>
+ <dynamicField name="*_d_dv" type="double" indexed="true" stored="true" docValues="true"/>
+ <dynamicField name="*_dt_dv" type="date" indexed="true" stored="true" docValues="true"/>
+ <dynamicField name="*_f1_dv" type="float" indexed="true" stored="true" docValues="true" multiValued="false"/>
+
</fields>
<defaultSearchField>text</defaultSearchField>
@@ -663,6 +672,13 @@
<copyField source="id" dest="range_facet_l"/>
<copyField source="range_facet_f" dest="range_facet_d"/>
+ <copyField source="range_facet_f1" dest="range_facet_f1_dv"/>
+
+ <copyField source="id" dest="range_facet_l_dv"/>
+ <copyField source="id" dest="range_facet_i_dv"/>
+ <copyField source="range_facet_f" dest="range_facet_f_dv"/>
+ <copyField source="range_facet_f" dest="range_facet_d_dv"/>
+ <copyField source="bday" dest="range_facet_dt_dv"/>
<!-- dynamic destination -->
<copyField source="*_dynamic" dest="dynamic_*"/>
Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/TestDistributedSearch.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/TestDistributedSearch.java?rev=1675706&r1=1675705&r2=1675706&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/TestDistributedSearch.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/TestDistributedSearch.java Thu Apr 23 19:27:34 2015
@@ -46,6 +46,7 @@ import org.apache.solr.common.params.Mod
import org.apache.solr.common.params.ShardParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.StatsParams;
+import org.apache.solr.common.params.FacetParams.FacetRangeMethod;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.handler.component.ShardResponse;
import org.apache.solr.handler.component.StatsComponentTest.StatSetCombinations;
@@ -246,7 +247,17 @@ public class TestDistributedSearch exten
"facet.range",tlong,
"facet.range.start",200,
"facet.range.gap",100,
- "facet.range.end",900);
+ "facet.range.end",900,
+ "facet.range.method", FacetRangeMethod.FILTER);
+
+ // simple range facet on one field using dv method
+ query("q",facetQuery, "rows",100, "facet","true",
+ "facet.range",tlong,
+ "facet.range",tlong,
+ "facet.range.start",200,
+ "facet.range.gap",100,
+ "facet.range.end",900,
+ "facet.range.method", FacetRangeMethod.DV);
// range facet on multiple fields
query("q",facetQuery, "rows",100, "facet","true",
@@ -257,7 +268,9 @@ public class TestDistributedSearch exten
"facet.range.end",900,
"facet.range.start",200,
"facet.range.gap",100,
- "f."+tlong+".facet.range.end",900);
+ "f."+tlong+".facet.range.end",900,
+ "f."+i1+".facet.range.method", FacetRangeMethod.FILTER,
+ "f."+tlong+".facet.range.method", FacetRangeMethod.DV);
// range facet with "other" param
QueryResponse response = query("q",facetQuery, "rows",100, "facet","true",
Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java?rev=1675706&r1=1675705&r2=1675706&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java Thu Apr 23 19:27:34 2015
@@ -17,21 +17,31 @@
package org.apache.solr.request;
+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.schema.TrieDateField;
import org.apache.solr.util.TimeZoneUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import org.noggit.ObjectBuilder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
public class SimpleFacetsTest extends SolrTestCaseJ4 {
@@ -98,36 +108,60 @@ public class SimpleFacetsTest extends So
static void indexSimpleFacetCounts() {
add_doc("id", "42",
- "range_facet_f", "35.3",
+ "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"
+ "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_f", "28.789",
+ "range_facet_f1", "28.789",
"title", "Democratic Order of Planets",
- "foo_s","A", "foo_s","B"
+ "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_f", "15.97",
+ "range_facet_f1", "15.97",
"trait_s", "Tool",
"name", "The Zapper",
- "foo_s","A", "foo_s","B", "foo_s","C"
+ "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_f", "30.0",
+ "range_facet_f1", "30.0",
"trait_s", "Chauvinist",
"title", "25 star General",
- "foo_s","A", "foo_s","B"
+ "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_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"
+ "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","",
@@ -301,6 +335,31 @@ public class SimpleFacetsTest extends So
"//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
@@ -726,21 +785,32 @@ public class SimpleFacetsTest extends So
@Test
public void testTrieDateFacets() {
- helpTestDateFacets("bday", false);
+ helpTestDateFacets("bday", false, FacetRangeMethod.FILTER);
}
@Test
public void testTrieDateRangeFacets() {
- helpTestDateFacets("bday", true);
+ helpTestDateFacets("bday", true, FacetRangeMethod.FILTER);
+ }
+
+ @Test
+ public void testTrieDateFacetsDocValues() {
+ helpTestDateFacets("bday", false, FacetRangeMethod.DV);
+ }
+
+ @Test
+ public void testTrieDateRangeFacetsDocValues() {
+ helpTestDateFacets("bday", true, FacetRangeMethod.DV);
}
@Test
public void testDateRangeFieldFacets() {
- helpTestDateFacets("bday_drf", true);
+ helpTestDateFacets("bday_drf", true, FacetRangeMethod.FILTER);
}
private void helpTestDateFacets(final String fieldName,
- final boolean rangeMode) {
+ final boolean rangeMode,
+ final FacetRangeMethod rangeFacetMethod) {
final String p = rangeMode ? "facet.range" : "facet.date";
final String b = rangeMode ? "facet_ranges" : "facet_dates";
final String f = fieldName;
@@ -763,6 +833,7 @@ public class SimpleFacetsTest extends So
,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)="+(rangeMode ? 31 : 34)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']"
@@ -1351,13 +1422,27 @@ public class SimpleFacetsTest extends So
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");
+ helpTestNumericRangeFacetsDoubleOverflow("range_facet_d", FacetRangeMethod.FILTER);
+ }
+
+ @Test
+ public void testNumericRangeFacetsOverflowTrieDoubleDocValue() {
+ helpTestNumericRangeFacetsDoubleOverflow("range_facet_d", FacetRangeMethod.DV);
}
- private void helpTestNumericRangeFacetsDoubleOverflow(final String fieldName) {
+ 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 + "/../";
@@ -1372,6 +1457,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", start
,"facet.range.end", end
,"facet.range.gap", gap
@@ -1388,7 +1474,11 @@ public class SimpleFacetsTest extends So
,meta+"/int[@name='between'][.='6']"
);
}
- private void helpTestFractionalNumberRangeFacets(final String fieldName) {
+
+ 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']";
@@ -1399,6 +1489,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", "10"
,"facet.range.end", "50"
,"facet.range.gap", "10"
@@ -1421,6 +1512,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", "10"
,"facet.range.end", "50"
,"facet.range.gap", "10"
@@ -1443,6 +1535,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", "10"
,"facet.range.end", "50"
,"facet.range.gap", "10"
@@ -1466,6 +1559,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", "20"
,"facet.range.end", "50"
,"facet.range.gap", "10"
@@ -1488,6 +1582,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", "10"
,"facet.range.end", "30"
,"facet.range.gap", "10"
@@ -1509,6 +1604,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", "10"
,"facet.range.end", "30"
,"facet.range.gap", "10"
@@ -1530,6 +1626,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", "20"
,"facet.range.end", "40"
,"facet.range.gap", "10"
@@ -1551,6 +1648,7 @@ public class SimpleFacetsTest extends So
,"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"
@@ -1574,6 +1672,7 @@ public class SimpleFacetsTest extends So
,"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"
@@ -1599,13 +1698,28 @@ public class SimpleFacetsTest extends So
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");
+ helpTestNumericRangeFacetsLongOverflow("range_facet_l", FacetRangeMethod.FILTER);
+ }
+
+ @Test
+ public void testNumericRangeFacetsOverflowTrieLongDocValues() {
+ helpTestNumericRangeFacetsLongOverflow("range_facet_l", FacetRangeMethod.DV);
}
- private void helpTestNumericRangeFacetsLongOverflow(final String fieldName) {
+ 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 + "/../";
@@ -1620,6 +1734,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", start
,"facet.range.end", end
,"facet.range.gap", gap
@@ -1636,7 +1751,12 @@ public class SimpleFacetsTest extends So
,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
@@ -1650,6 +1770,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", "35"
,"facet.range.end", "50"
,"facet.range.gap", "5"
@@ -1671,6 +1792,7 @@ public class SimpleFacetsTest extends So
,"rows", "0"
,"facet", "true"
,"facet.range", f
+ ,"facet.range.method", method.toString()
,"facet.range.start", "35"
,"facet.range.end", "50"
,"facet.range.gap", "5"
@@ -2277,5 +2399,604 @@ public class SimpleFacetsTest extends So
public void testContainsIgnoreCase() {
assertTrue(SimpleFacets.contains("FooBar", "bar", true));
}
+
+ 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]"
+ );
+
+ 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()
+ )
+ ,"*[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));
+ }
+
+ }
+
+
+ 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, TrieDateField.formatExternal(dates[0]));
+ params.add(FacetParams.FACET_RANGE_END, TrieDateField.formatExternal(dates[1]));
+ 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()));
+ }
}
Modified: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java?rev=1675706&r1=1675705&r2=1675706&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java (original)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java Thu Apr 23 19:27:34 2015
@@ -281,7 +281,15 @@ public interface FacetParams {
* @see FacetRangeInclude
*/
public static final String FACET_RANGE_INCLUDE = FACET_RANGE + ".include";
-
+
+ /**
+ * String indicating the method to use to resolve range facets.
+ * <p>
+ * Can be overriden on a per field basis.
+ * @see FacetRangeMethod
+ */
+ public static final String FACET_RANGE_METHOD = FACET_RANGE + ".method";
+
/**
* Any field whose values the user wants to enumerate as explicit intervals of terms.
*/
@@ -410,6 +418,32 @@ public interface FacetParams {
return include;
}
}
+
+ /**
+ * An enumeration of the legal values for {@link #FACET_RANGE_METHOD}
+ * <ul>
+ * <li>filter = </li>
+ * <li>dv = </li>
+ * </ul>
+ * @see #FACET_RANGE_METHOD
+ */
+ public enum FacetRangeMethod {
+ FILTER, DV;
+ @Override
+ public String toString() { return super.toString().toLowerCase(Locale.ROOT); }
+ public static FacetRangeMethod get(String label) {
+ try {
+ return valueOf(label.toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException e) {
+ throw new SolrException
+ (SolrException.ErrorCode.BAD_REQUEST,
+ label+" is not a valid method for range faceting",e);
+ }
+ }
+ public static FacetRangeMethod getDefault() {
+ return FILTER;
+ }
+ }
}