SOLR-1240: Added support for generalized Numerical Range faceting on any numeric (or date) field. Note: this required modifying ConvertedLegacyTest to deal with an expectation about how many fields a specific document has (there are more now because of a copyField i added to the schema)

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@980555 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Chris M. Hostetter 2010-07-29 20:05:41 +00:00
parent d5ded69e49
commit adaeb656c8
7 changed files with 1079 additions and 261 deletions

View File

@ -214,6 +214,11 @@ New Features
* SOLR-1925: Add CSVResponseWriter (use wt=csv) that returns the list of documents * SOLR-1925: Add CSVResponseWriter (use wt=csv) that returns the list of documents
in CSV format. (Chris Mattmann, yonik) in CSV format. (Chris Mattmann, yonik)
* SOLR-1240: "Rnage Faceting has been added. This is a generalization
of the existing "Date Faceting" logic so that it now supports any
all stock numeric field types that support range queries in addition
to dates.
(Gijs Kunze, hossman)
Optimizations Optimizations
---------------------- ----------------------

View File

@ -153,36 +153,10 @@ public interface FacetParams {
* String indicating what "other" ranges should be computed for a * String indicating what "other" ranges should be computed for a
* date facet range (multi-value). * date facet range (multi-value).
* Can be overriden on a per field basis. * Can be overriden on a per field basis.
* @see FacetDateOther * @see FacetRangeOther
*/ */
public static final String FACET_DATE_OTHER = FACET_DATE + ".other"; public static final String FACET_DATE_OTHER = FACET_DATE + ".other";
/**
* An enumeration of the legal values for FACET_DATE_OTHER...
* <ul>
* <li>before = the count of matches before the start date</li>
* <li>after = the count of matches after the end date</li>
* <li>between = the count of all matches between start and end</li>
* <li>all = all of the above (default value)</li>
* <li>none = no additional info requested</li>
* </ul>
* @see #FACET_DATE_OTHER
* @see #FACET_DATE_INCLUDE
*/
public enum FacetDateOther {
BEFORE, AFTER, BETWEEN, ALL, NONE;
public String toString() { return super.toString().toLowerCase(Locale.ENGLISH); }
public static FacetDateOther get(String label) {
try {
return valueOf(label.toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException e) {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
label+" is not a valid type of 'other' date facet information",e);
}
}
}
/** /**
* <p> * <p>
* Multivalued string indicating what rules should be applied to determine * Multivalued string indicating what rules should be applied to determine
@ -195,12 +169,124 @@ public interface FacetParams {
* <p> * <p>
* Can be overriden on a per field basis. * Can be overriden on a per field basis.
* </p> * </p>
* @see FacetDateInclude * @see FacetRangeInclude
*/ */
public static final String FACET_DATE_INCLUDE = FACET_DATE + ".include"; public static final String FACET_DATE_INCLUDE = FACET_DATE + ".include";
/** /**
* An enumeration of the legal values for FACET_DATE_INCLUDE... * Any numerical field whose terms the user wants to enumerate over
* Facet Contraint Counts for selected ranges.
*/
public static final String FACET_RANGE = FACET + ".range";
/**
* Number indicating the starting point for a numerical range facet.
* Can be overriden on a per field basis.
*/
public static final String FACET_RANGE_START = FACET_RANGE + ".start";
/**
* Number indicating the ending point for a numerical range facet.
* Can be overriden on a per field basis.
*/
public static final String FACET_RANGE_END = FACET_RANGE + ".end";
/**
* Number indicating the interval of sub-ranges for a numerical
* facet range.
* Can be overriden on a per field basis.
*/
public static final String FACET_RANGE_GAP = FACET_RANGE + ".gap";
/**
* Boolean indicating how counts should be computed if the range
* between 'start' and 'end' is not evenly divisible by 'gap'. If
* this value is true, then all counts of ranges involving the 'end'
* point will use the exact endpoint specified -- this includes the
* 'between' and 'after' counts as well as the last range computed
* using the 'gap'. If the value is false, then 'gap' is used to
* compute the effective endpoint closest to the 'end' param which
* results in the range between 'start' and 'end' being evenly
* divisible by 'gap'.
* The default is false.
* Can be overriden on a per field basis.
*/
public static final String FACET_RANGE_HARD_END = FACET_RANGE + ".hardend";
/**
* String indicating what "other" ranges should be computed for a
* numerical range facet (multi-value).
* Can be overriden on a per field basis.
* @see FacetNumberOther
*/
public static final String FACET_RANGE_OTHER = FACET_RANGE + ".other";
/**
* String indicating whether ranges for numerical range faceting
* should be exclusive or inclusive. By default both the start and
* end point are inclusive.
* Can be overriden on a per field basis.
* @see FacetNumberExclusive
*/
/**
* <p>
* Multivalued string indicating what rules should be applied to determine
* when the the ranges generated for numeric faceting should be inclusive or
* exclusive of their end points.
* </p>
* <p>
* The default value if none are specified is: [lower,upper,edge]
* </p>
* <p>
* Can be overriden on a per field basis.
* </p>
* @see FacetRangeInclude
*/
public static final String FACET_RANGE_INCLUDE = FACET_RANGE + ".include";
/**
* An enumeration of the legal values for {@link #FACET_RANGE_OTHER} and {@link #FACET_DATE_OTHER} ...
* <ul>
* <li>before = the count of matches before the start</li>
* <li>after = the count of matches after the end</li>
* <li>between = the count of all matches between start and end</li>
* <li>all = all of the above (default value)</li>
* <li>none = no additional info requested</li>
* </ul>
* @see #FACET_RANGE_OTHER
* @see #FACET_DATE_OTHER
*/
public enum FacetRangeOther {
BEFORE, AFTER, BETWEEN, ALL, NONE;
public String toString() { return super.toString().toLowerCase(); }
public static FacetRangeOther get(String label) {
try {
return valueOf(label.toUpperCase());
} catch (IllegalArgumentException e) {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
label+" is not a valid type of 'other' range facet information",e);
}
}
}
/**
* @deprecated Use {@link FacetRangeOther}
*/
@Deprecated
public enum FacetDateOther {
BEFORE, AFTER, BETWEEN, ALL, NONE;
public String toString() { return super.toString().toLowerCase(); }
public static FacetDateOther get(String label) {
try {
return valueOf(label.toUpperCase());
} catch (IllegalArgumentException e) {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
label+" is not a valid type of 'other' range facet information",e);
}
}
}
/**
* An enumeration of the legal values for {@link #FACET_DATE_INCLUDE} and {@link #FACET_RANGE_INCLUDE}
*
* <ul> * <ul>
* <li>lower = all gap based ranges include their lower bound</li> * <li>lower = all gap based ranges include their lower bound</li>
* <li>upper = all gap based ranges include their upper bound</li> * <li>upper = all gap based ranges include their upper bound</li>
@ -208,43 +294,45 @@ public interface FacetParams {
* for the first one, upper for the last one) even if the corresponding * for the first one, upper for the last one) even if the corresponding
* upper/lower option is not specified * upper/lower option is not specified
* </li> * </li>
* <li>outer = the FacetDateOther.BEFORE and FacetDateOther.AFTER ranges * <li>outer = the BEFORE and AFTER ranges
* should be inclusive of their bounds, even if the first or last ranges * should be inclusive of their bounds, even if the first or last ranges
* already include those boundaries. * already include those boundaries.
* </li> * </li>
* <li>all = shorthand for lower, upper, edge, and outer</li> * <li>all = shorthand for lower, upper, edge, and outer</li>
* </ul> * </ul>
* @see #FACET_DATE_INCLUDE * @see #FACET_DATE_INCLUDE
* @see #FACET_RANGE_INCLUDE
*/ */
public enum FacetDateInclude { public enum FacetRangeInclude {
ALL, LOWER, UPPER, EDGE, OUTER; ALL, LOWER, UPPER, EDGE, OUTER;
public String toString() { return super.toString().toLowerCase(Locale.ENGLISH); } public String toString() { return super.toString().toLowerCase(Locale.ENGLISH); }
public static FacetDateInclude get(String label) { public static FacetRangeInclude get(String label) {
try { try {
return valueOf(label.toUpperCase(Locale.ENGLISH)); return valueOf(label.toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new SolrException throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST, (SolrException.ErrorCode.BAD_REQUEST,
label+" is not a valid type of for "+FACET_DATE_INCLUDE+" information",e); label+" is not a valid type of for range 'include' information",e);
} }
} }
/** /**
* Convinience method for parsing the param value according to the correct semantics. * Convinience method for parsing the param value according to the
* correct semantics.
*/ */
public static EnumSet<FacetDateInclude> parseParam(final String[] param) { public static EnumSet<FacetRangeInclude> parseParam(final String[] param) {
// short circut for default behavior // short circut for default behavior
if (null == param || 0 == param.length ) if (null == param || 0 == param.length )
return EnumSet.of(LOWER, UPPER, EDGE); return EnumSet.of(LOWER, UPPER, EDGE);
// build up set containing whatever is specified // build up set containing whatever is specified
final EnumSet<FacetDateInclude> include = EnumSet.noneOf(FacetDateInclude.class); final EnumSet<FacetRangeInclude> include = EnumSet.noneOf(FacetRangeInclude.class);
for (final String o : param) { for (final String o : param) {
include.add(FacetDateInclude.get(o)); include.add(FacetRangeInclude.get(o));
} }
// if set contains all, then we're back to short circuting // if set contains all, then we're back to short circuting
if (include.contains(FacetDateInclude.ALL)) if (include.contains(FacetRangeInclude.ALL))
return EnumSet.allOf(FacetDateInclude.class); return EnumSet.allOf(FacetRangeInclude.class);
// use whatever we've got. // use whatever we've got.
return include; return include;

View File

@ -404,8 +404,9 @@ public class FacetComponent extends SearchComponent
} }
} }
// TODO: facet dates // TODO: facet dates & numbers
facet_counts.add("facet_dates", new SimpleOrderedMap()); facet_counts.add("facet_dates", new SimpleOrderedMap());
facet_counts.add("facet_ranges", new SimpleOrderedMap());
rb.rsp.add("facet_counts", facet_counts); rb.rsp.add("facet_counts", facet_counts);
@ -688,4 +689,4 @@ public class FacetComponent extends SearchComponent
return "{term="+name+",termNum="+termNum+",count="+count+"}"; return "{term="+name+",termNum="+termNum+",count="+count+"}";
} }
} }
} }

View File

@ -31,13 +31,14 @@ import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.params.RequiredSolrParams; import org.apache.solr.common.params.RequiredSolrParams;
import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.FacetParams.FacetDateOther; import org.apache.solr.common.params.FacetParams.FacetRangeOther;
import org.apache.solr.common.params.FacetParams.FacetDateInclude; import org.apache.solr.common.params.FacetParams.FacetRangeInclude;
import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.StrUtils; import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.SolrCore; import org.apache.solr.core.SolrCore;
import org.apache.solr.schema.*; import org.apache.solr.schema.*;
import org.apache.solr.schema.TrieField.TrieTypes;
import org.apache.solr.search.*; import org.apache.solr.search.*;
import org.apache.solr.util.BoundedTreeSet; import org.apache.solr.util.BoundedTreeSet;
import org.apache.solr.util.ByteUtils; import org.apache.solr.util.ByteUtils;
@ -63,11 +64,14 @@ public class SimpleFacets {
protected DocSet docs; protected DocSet docs;
/** Configuration params behavior should be driven by */ /** Configuration params behavior should be driven by */
protected SolrParams params; protected SolrParams params;
protected SolrParams required;
/** Searcher to use for all calculations */ /** Searcher to use for all calculations */
protected SolrIndexSearcher searcher; protected SolrIndexSearcher searcher;
protected SolrQueryRequest req; protected SolrQueryRequest req;
protected ResponseBuilder rb; protected ResponseBuilder rb;
public final Date NOW = new Date();
// per-facet values // per-facet values
SolrParams localParams; // localParams on this particular facet command SolrParams localParams; // localParams on this particular facet command
String facetValue; // the field to or query to facet on (minus local params) String facetValue; // the field to or query to facet on (minus local params)
@ -89,6 +93,7 @@ public class SimpleFacets {
this.searcher = req.getSearcher(); this.searcher = req.getSearcher();
this.base = this.docs = docs; this.base = this.docs = docs;
this.params = params; this.params = params;
this.required = new RequiredSolrParams(params);
this.rb = rb; this.rb = rb;
} }
@ -166,6 +171,7 @@ public class SimpleFacets {
* @see #getFacetQueryCounts * @see #getFacetQueryCounts
* @see #getFacetFieldCounts * @see #getFacetFieldCounts
* @see #getFacetDateCounts * @see #getFacetDateCounts
* @see #getFacetRangeCounts
* @see FacetParams#FACET * @see FacetParams#FACET
* @return a NamedList of Facet Count info or null * @return a NamedList of Facet Count info or null
*/ */
@ -181,7 +187,8 @@ public class SimpleFacets {
res.add("facet_queries", getFacetQueryCounts()); res.add("facet_queries", getFacetQueryCounts());
res.add("facet_fields", getFacetFieldCounts()); res.add("facet_fields", getFacetFieldCounts());
res.add("facet_dates", getFacetDateCounts()); res.add("facet_dates", getFacetDateCounts());
res.add("facet_ranges", getFacetRangeCounts());
} catch (Exception e) { } catch (Exception e) {
SolrException.logOnce(SolrCore.log, "Exception during facet counts", e); SolrException.logOnce(SolrCore.log, "Exception during facet counts", e);
res.add("exception", SolrException.toStr(e)); res.add("exception", SolrException.toStr(e));
@ -723,12 +730,10 @@ public class SimpleFacets {
* @see FacetParams#FACET_DATE * @see FacetParams#FACET_DATE
*/ */
public NamedList getFacetDateCounts() public NamedList getFacetDateCounts()
throws IOException, ParseException { throws IOException, ParseException {
final SolrParams required = new RequiredSolrParams(params);
final NamedList resOuter = new SimpleOrderedMap(); final NamedList resOuter = new SimpleOrderedMap();
final String[] fields = params.getParams(FacetParams.FACET_DATE); final String[] fields = params.getParams(FacetParams.FACET_DATE);
final Date NOW = new Date();
if (null == fields || 0 == fields.length) return resOuter; if (null == fields || 0 == fields.length) return resOuter;
@ -778,9 +783,9 @@ public class SimpleFacets {
final DateMathParser dmp = new DateMathParser(ft.UTC, Locale.US); final DateMathParser dmp = new DateMathParser(ft.UTC, Locale.US);
dmp.setNow(NOW); dmp.setNow(NOW);
int minCount = params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0); final int minCount = params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0);
final EnumSet<FacetDateInclude> include = FacetDateInclude.parseParam final EnumSet<FacetRangeInclude> include = FacetRangeInclude.parseParam
(params.getFieldParams(f,FacetParams.FACET_DATE_INCLUDE)); (params.getFieldParams(f,FacetParams.FACET_DATE_INCLUDE));
try { try {
@ -802,14 +807,14 @@ public class SimpleFacets {
(SolrException.ErrorCode.BAD_REQUEST, (SolrException.ErrorCode.BAD_REQUEST,
"date facet infinite loop (is gap negative?)"); "date facet infinite loop (is gap negative?)");
} }
boolean includeLower = final boolean includeLower =
(include.contains(FacetDateInclude.LOWER) || (include.contains(FacetRangeInclude.LOWER) ||
(include.contains(FacetDateInclude.EDGE) && low.equals(start))); (include.contains(FacetRangeInclude.EDGE) && low.equals(start)));
boolean includeUpper = final boolean includeUpper =
(include.contains(FacetDateInclude.UPPER) || (include.contains(FacetRangeInclude.UPPER) ||
(include.contains(FacetDateInclude.EDGE) && high.equals(end))); (include.contains(FacetRangeInclude.EDGE) && high.equals(end)));
int count = rangeCount(sf,low,high,includeLower,includeUpper); final int count = rangeCount(sf,low,high,includeLower,includeUpper);
if (count >= minCount) { if (count >= minCount) {
resInner.add(label, count); resInner.add(label, count);
} }
@ -821,49 +826,52 @@ public class SimpleFacets {
"date facet 'gap' is not a valid Date Math string: " + gap, e); "date facet 'gap' is not a valid Date Math string: " + gap, e);
} }
// explicitly return the gap and end so all the counts are meaningful // explicitly return the gap and end so all the counts
// (including before/after/between) are meaningful - even if mincount
// has removed the neighboring ranges
resInner.add("gap", gap); resInner.add("gap", gap);
resInner.add("start", start);
resInner.add("end", end); resInner.add("end", end);
final String[] othersP = final String[] othersP =
params.getFieldParams(f,FacetParams.FACET_DATE_OTHER); params.getFieldParams(f,FacetParams.FACET_DATE_OTHER);
if (null != othersP && 0 < othersP.length ) { if (null != othersP && 0 < othersP.length ) {
Set<FacetDateOther> others = EnumSet.noneOf(FacetDateOther.class); final Set<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
for (final String o : othersP) { for (final String o : othersP) {
others.add(FacetDateOther.get(o)); others.add(FacetRangeOther.get(o));
} }
// no matter what other values are listed, we don't do // no matter what other values are listed, we don't do
// anything if "none" is specified. // anything if "none" is specified.
if (! others.contains(FacetDateOther.NONE) ) { if (! others.contains(FacetRangeOther.NONE) ) {
boolean all = others.contains(FacetDateOther.ALL); boolean all = others.contains(FacetRangeOther.ALL);
if (all || others.contains(FacetDateOther.BEFORE)) { if (all || others.contains(FacetRangeOther.BEFORE)) {
// include upper bound if "outer" or if first gap doesn't already include it // include upper bound if "outer" or if first gap doesn't already include it
resInner.add(FacetDateOther.BEFORE.toString(), resInner.add(FacetRangeOther.BEFORE.toString(),
rangeCount(sf,null,start, rangeCount(sf,null,start,
false, false,
(include.contains(FacetDateInclude.OUTER) || (include.contains(FacetRangeInclude.OUTER) ||
(! (include.contains(FacetDateInclude.LOWER) || (! (include.contains(FacetRangeInclude.LOWER) ||
include.contains(FacetDateInclude.EDGE)))))); include.contains(FacetRangeInclude.EDGE))))));
} }
if (all || others.contains(FacetDateOther.AFTER)) { if (all || others.contains(FacetRangeOther.AFTER)) {
// include lower bound if "outer" or if last gap doesn't already include it // include lower bound if "outer" or if last gap doesn't already include it
resInner.add(FacetDateOther.AFTER.toString(), resInner.add(FacetRangeOther.AFTER.toString(),
rangeCount(sf,end,null, rangeCount(sf,end,null,
(include.contains(FacetDateInclude.OUTER) || (include.contains(FacetRangeInclude.OUTER) ||
(! (include.contains(FacetDateInclude.UPPER) || (! (include.contains(FacetRangeInclude.UPPER) ||
include.contains(FacetDateInclude.EDGE)))), include.contains(FacetRangeInclude.EDGE)))),
false)); false));
} }
if (all || others.contains(FacetDateOther.BETWEEN)) { if (all || others.contains(FacetRangeOther.BETWEEN)) {
resInner.add(FacetDateOther.BETWEEN.toString(), resInner.add(FacetRangeOther.BETWEEN.toString(),
rangeCount(sf,start,end, rangeCount(sf,start,end,
(include.contains(FacetDateInclude.LOWER) || (include.contains(FacetRangeInclude.LOWER) ||
include.contains(FacetDateInclude.EDGE)), include.contains(FacetRangeInclude.EDGE)),
(include.contains(FacetDateInclude.UPPER) || (include.contains(FacetRangeInclude.UPPER) ||
include.contains(FacetDateInclude.EDGE)))); include.contains(FacetRangeInclude.EDGE))));
} }
} }
} }
@ -872,6 +880,197 @@ public class SimpleFacets {
return resOuter; return resOuter;
} }
/**
* Returns a list of value constraints and the associated facet
* counts for each facet numerical field, range, and interval
* specified in the SolrParams
*
* @see FacetParams#FACET_RANGE
*/
public NamedList getFacetRangeCounts()
throws IOException, ParseException {
final NamedList resOuter = new SimpleOrderedMap();
final String[] fields = params.getParams(FacetParams.FACET_RANGE);
if (null == fields || 0 == fields.length) return resOuter;
final IndexSchema schema = searcher.getSchema();
for (String f : fields) {
parseParams(FacetParams.FACET_RANGE, f);
f = facetValue;
final SchemaField sf = schema.getField(f);
final FieldType ft = sf.getType();
RangeEndpointCalculator calc = null;
if (ft instanceof TrieField) {
final TrieField trie = (TrieField)ft;
switch (trie.getType()) {
case FLOAT:
calc = new FloatRangeEndpointCalculator(sf);
break;
case DOUBLE:
calc = new DoubleRangeEndpointCalculator(sf);
break;
case INTEGER:
calc = new IntegerRangeEndpointCalculator(sf);
break;
case LONG:
calc = new LongRangeEndpointCalculator(sf);
break;
default:
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
"Unable to range facet on tried field of unexpected type:" + f);
}
} else if (ft instanceof DateField) {
calc = new DateRangeEndpointCalculator(sf, NOW);
} else if (ft instanceof SortableIntField) {
calc = new IntegerRangeEndpointCalculator(sf);
} else if (ft instanceof SortableLongField) {
calc = new LongRangeEndpointCalculator(sf);
} else if (ft instanceof SortableFloatField) {
calc = new FloatRangeEndpointCalculator(sf);
} else if (ft instanceof SortableDoubleField) {
calc = new DoubleRangeEndpointCalculator(sf);
} else {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
"Unable to range facet on field:" + sf);
}
resOuter.add(key, getFacetRangeCounts(sf, calc));
}
return resOuter;
}
private <T extends Comparable<T>> NamedList getFacetRangeCounts
(final SchemaField sf,
final RangeEndpointCalculator<T> calc) throws IOException {
final String f = sf.getName();
final NamedList res = new SimpleOrderedMap();
final NamedList counts = new SimpleOrderedMap();
res.add("counts", counts);
final T start = calc.getValue(required.getFieldParam(f,FacetParams.FACET_RANGE_START));
// not final, hardend may change this
T end = calc.getValue(required.getFieldParam(f,FacetParams.FACET_RANGE_END));
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));
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?)");
}
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);
final int count = rangeCount(sf, lowS, highS,
includeLower,includeUpper);
if (count >= minCount) {
counts.add(lowS, count);
}
low = high;
}
// explicitly return the start and end so all the counts
// (including before/after/between) are meaningful - even if mincount
// has removed the neighboring ranges
res.add("start", start);
res.add("end", end);
final String[] othersP =
params.getFieldParams(f,FacetParams.FACET_RANGE_OTHER);
if (null != othersP && 0 < othersP.length ) {
Set<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
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) ) {
boolean all = others.contains(FacetRangeOther.ALL);
final String startS = calc.formatValue(start);
final String endS = calc.formatValue(end);
if (all || others.contains(FacetRangeOther.BEFORE)) {
// include upper bound if "outer" or if first gap doesn't already include it
res.add(FacetRangeOther.BEFORE.toString(),
rangeCount(sf,null,startS,
false,
(include.contains(FacetRangeInclude.OUTER) ||
(! (include.contains(FacetRangeInclude.LOWER) ||
include.contains(FacetRangeInclude.EDGE))))));
}
if (all || others.contains(FacetRangeOther.AFTER)) {
// include lower bound if "outer" or if last gap doesn't already include it
res.add(FacetRangeOther.AFTER.toString(),
rangeCount(sf,endS,null,
(include.contains(FacetRangeInclude.OUTER) ||
(! (include.contains(FacetRangeInclude.UPPER) ||
include.contains(FacetRangeInclude.EDGE)))),
false));
}
if (all || others.contains(FacetRangeOther.BETWEEN)) {
res.add(FacetRangeOther.BETWEEN.toString(),
rangeCount(sf,startS,endS,
(include.contains(FacetRangeInclude.LOWER) ||
include.contains(FacetRangeInclude.EDGE)),
(include.contains(FacetRangeInclude.UPPER) ||
include.contains(FacetRangeInclude.EDGE))));
}
}
}
return res;
}
/** /**
* Macro for getting the numDocs of range over docs * Macro for getting the numDocs of range over docs
* @see SolrIndexSearcher#numDocs * @see SolrIndexSearcher#numDocs
@ -914,5 +1113,173 @@ public class SimpleFacets {
return (0 != vc ? vc : key.compareTo(o.key)); return (0 != vc ? vc : key.compareTo(o.key));
} }
} }
/**
* Perhaps someday instead of having a giant "instanceof" case
* statement to pick an impl, we can add a "RangeFacetable" marker
* interface to FieldTypes and they can return instances of these
* directly from some method -- but until then, keep this locked down
* and private.
*/
private static abstract class RangeEndpointCalculator<T extends Comparable<T>> {
protected final SchemaField field;
public RangeEndpointCalculator(final SchemaField field) {
this.field = field;
}
/**
* Formats a Range endpoint for use as a range label name in the response.
* Default Impl just uses toString()
*/
public String formatValue(final T val) {
return val.toString();
}
/**
* Parses a String param into an Range endpoint value throwing
* a useful exception if not possible
*/
public final T getValue(final String rawval) {
try {
return parseVal(rawval);
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Can't parse value "+rawval+" for field: " +
field.getName(), e);
}
}
/**
* Parses a String param into an Range endpoint.
* Can throw a low level format exception as needed.
*/
protected abstract T parseVal(final String rawval)
throws java.text.ParseException;
/**
* Parses a String param into a value that represents the gap and
* can be included in the response, throwing
* a useful exception if not possible.
*
* Note: uses Object as the return type instead of T for things like
* Date where gap is just a DateMathParser string
*/
public final Object getGap(final String gap) {
try {
return parseGap(gap);
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Can't parse gap "+gap+" for field: " +
field.getName(), e);
}
}
/**
* Parses a String param into a value that represents the gap and
* can be included in the response.
* Can throw a low level format exception as needed.
*
* Default Impl calls parseVal
*/
protected Object parseGap(final String rawval)
throws java.text.ParseException {
return parseVal(rawval);
}
/**
* Adds the String gap param to a low Range endpoint value to determine
* the corrisponding high Range endpoint value, throwing
* a useful exception if not possible.
*/
public final T addGap(T value, String gap) {
try {
return parseAndAddGap(value, gap);
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Can't add gap "+gap+" to value " + value +
" for field: " + field.getName(), e);
}
}
/**
* Adds the String gap param to a low Range endpoint value to determine
* the corrisponding high Range endpoint value.
* Can throw a low level format exception as needed.
*/
protected abstract T parseAndAddGap(T value, String gap)
throws java.text.ParseException;
}
private static class FloatRangeEndpointCalculator
extends RangeEndpointCalculator<Float> {
public FloatRangeEndpointCalculator(final SchemaField f) { super(f); }
protected Float parseVal(String rawval) {
return Float.valueOf(rawval);
}
public Float parseAndAddGap(Float value, String gap) {
return new Float(value.floatValue() + Float.valueOf(gap).floatValue());
}
}
private static class DoubleRangeEndpointCalculator
extends RangeEndpointCalculator<Double> {
public DoubleRangeEndpointCalculator(final SchemaField f) { super(f); }
protected Double parseVal(String rawval) {
return Double.valueOf(rawval);
}
public Double parseAndAddGap(Double value, String gap) {
return new Double(value.floatValue() + Double.valueOf(gap).floatValue());
}
}
private static class IntegerRangeEndpointCalculator
extends RangeEndpointCalculator<Integer> {
public IntegerRangeEndpointCalculator(final SchemaField f) { super(f); }
protected Integer parseVal(String rawval) {
return Integer.valueOf(rawval);
}
public Integer parseAndAddGap(Integer value, String gap) {
return new Integer(value.intValue() + Integer.valueOf(gap).intValue());
}
}
private static class LongRangeEndpointCalculator
extends RangeEndpointCalculator<Long> {
public LongRangeEndpointCalculator(final SchemaField f) { super(f); }
protected Long parseVal(String rawval) {
return Long.valueOf(rawval);
}
public Long parseAndAddGap(Long value, String gap) {
return new Long(value.intValue() + Long.valueOf(gap).intValue());
}
}
private static class DateRangeEndpointCalculator
extends RangeEndpointCalculator<Date> {
private final Date now;
public DateRangeEndpointCalculator(final SchemaField f,
final Date now) {
super(f);
this.now = now;
if (! (field.getType() instanceof DateField) ) {
throw new IllegalArgumentException
("SchemaField must use filed type extending DateField");
}
}
public String formatValue(Date val) {
return ((DateField)field.getType()).toExternal(val);
}
protected Date parseVal(String rawval) {
return ((DateField)field.getType()).parseMath(now, rawval);
}
protected Object parseGap(final String rawval) {
return rawval;
}
public Date parseAndAddGap(Date value, String gap) throws java.text.ParseException {
final DateMathParser dmp = new DateMathParser(DateField.UTC, Locale.US);
dmp.setNow(value);
return dmp.parseMath(gap);
}
}
} }

View File

@ -1150,7 +1150,7 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
,"//str[.='Yonik'] " ,"//str[.='Yonik'] "
,"//float[.='1.4142135'] " ,"//float[.='1.4142135'] "
,"//float[@name='score'] " ,"//float[@name='score'] "
,"*[count(//doc/*)=13]" ,"*[count(//doc/*)>=13]"
); );
args = new HashMap<String,String>(); args = new HashMap<String,String>();
args.put("version","2.0"); args.put("version","2.0");
@ -1161,7 +1161,7 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
,"//str[.='Yonik'] " ,"//str[.='Yonik'] "
,"//float[.='1.4142135'] " ,"//float[.='1.4142135'] "
,"//float[@name='score'] " ,"//float[@name='score'] "
,"*[count(//doc/*)=13]" ,"*[count(//doc/*)>=13]"
); );
args = new HashMap<String,String>(); args = new HashMap<String,String>();
args.put("version","2.0"); args.put("version","2.0");

View File

@ -72,18 +72,29 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
} }
static void indexSimpleFacetCounts() { static void indexSimpleFacetCounts() {
add_doc("id", "42", "trait_s", "Tool", "trait_s", "Obnoxious", add_doc("id", "42",
"name", "Zapp Brannigan"); "range_facet_f", "35.3",
"trait_s", "Tool", "trait_s", "Obnoxious",
"name", "Zapp Brannigan");
add_doc("id", "43" , add_doc("id", "43" ,
"title", "Democratic Order of Planets"); "range_facet_f", "28.789",
add_doc("id", "44", "trait_s", "Tool", "title", "Democratic Order of Planets");
"name", "The Zapper"); add_doc("id", "44",
add_doc("id", "45", "trait_s", "Chauvinist", "range_facet_f", "15.97",
"title", "25 star General"); "trait_s", "Tool",
add_doc("id", "46", "trait_s", "Obnoxious", "name", "The Zapper");
"subject", "Defeated the pacifists of the Gandhi nebula"); add_doc("id", "45",
add_doc("id", "47", "trait_s", "Pig", "range_facet_f", "30.0",
"text", "line up and fly directly at the enemy death cannons, clogging them with wreckage!"); "trait_s", "Chauvinist",
"title", "25 star General");
add_doc("id", "46",
"range_facet_f", "20.0",
"trait_s", "Obnoxious",
"subject", "Defeated the pacifists of the Gandhi nebula");
add_doc("id", "47",
"range_facet_f", "28.62",
"trait_s", "Pig",
"text", "line up and fly directly at the enemy death cannons, clogging them with wreckage!");
} }
@Test @Test
@ -281,23 +292,44 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
add_doc(i, "2014", f, "1976-07-05T22:22:22.222Z"); add_doc(i, "2014", f, "1976-07-05T22:22:22.222Z");
} }
@Test
public void testTrieDateFacets() {
helpTestDateFacets("bday", false);
}
@Test @Test
public void testDateFacets() { public void testDateFacets() {
final String f = "bday"; helpTestDateFacets("bday_pdt", false);
final String pre = "//lst[@name='facet_dates']/lst[@name='"+f+"']"; }
@Test
public void testTrieDateRangeFacets() {
helpTestDateFacets("bday", true);
}
@Test
public void testDateRangeFacets() {
helpTestDateFacets("bday_pdt", true);
}
private void helpTestDateFacets(final String fieldName,
final boolean rangeMode) {
final String p = rangeMode ? "facet.range" : "facet.date";
final String b = rangeMode ? "facet_ranges" : "facet_dates";
final String f = fieldName;
final String c = (rangeMode ? "/lst[@name='counts']" : "");
final String pre = "//lst[@name='"+b+"']/lst[@name='"+f+"']" + c;
final String meta = pre + (rangeMode ? "/../" : "");
assertQ("check counts for month of facet by day", assertQ("check counts for month of facet by day",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-01T00:00:00.000Z+1MONTH" ,p+".end", "1976-07-01T00:00:00.000Z+1MONTH"
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
) )
// 31 days + pre+post+inner = 34 ,"*[count("+pre+"/int)="+(rangeMode ? 31 : 34)+"]"
,"*[count("+pre+"/int)=34]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0' ]"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0' ]" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0' ]"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]"
@ -331,9 +363,9 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-30T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-30T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-31T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-31T00:00:00Z'][.='0']"
,pre+"/int[@name='before' ][.='2']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='after' ][.='1']" ,meta+"/int[@name='after' ][.='1']"
,pre+"/int[@name='between'][.='11']" ,meta+"/int[@name='between'][.='11']"
); );
@ -341,15 +373,14 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-01T00:00:00.000Z+1MONTH" ,p+".end", "1976-07-01T00:00:00.000Z+1MONTH"
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.mincount", "1" ,"facet.mincount", "1"
) )
// 31 days + pre+post+inner = 34 ,"*[count("+pre+"/int)="+(rangeMode ? 8 : 11)+"]"
,"*[count("+pre+"/int)=11]"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]"
// july4th = 2 because exists doc @ 00:00:00.000 on July5 // july4th = 2 because exists doc @ 00:00:00.000 on July5
// (date faceting is inclusive) // (date faceting is inclusive)
@ -360,68 +391,65 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2' ]"
,pre+"/int[@name='1976-07-21T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-21T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-30T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-30T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='2']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='after' ][.='1']" ,meta+"/int[@name='after' ][.='1']"
,pre+"/int[@name='between'][.='11']" ,meta+"/int[@name='between'][.='11']"
); );
assertQ("check counts for month of facet by day with field mincount = 1", assertQ("check counts for month of facet by day with field mincount = 1",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-01T00:00:00.000Z+1MONTH" ,p+".end", "1976-07-01T00:00:00.000Z+1MONTH"
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"f." + f + ".facet.mincount", "2" ,"f." + f + ".facet.mincount", "2"
) )
// 31 days + pre+post+inner = 34 ,"*[count("+pre+"/int)="+(rangeMode ? 4 : 7)+"]"
,"*[count("+pre+"/int)=7]"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]"
// july4th = 2 because exists doc @ 00:00:00.000 on July5 // july4th = 2 because exists doc @ 00:00:00.000 on July5
// (date faceting is inclusive) // (date faceting is inclusive)
,pre+"/int[@name='1976-07-04T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='2' ]"
,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]"
,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2' ]"
,pre+"/int[@name='before' ][.='2']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='after' ][.='1']" ,meta+"/int[@name='after' ][.='1']"
,pre+"/int[@name='between'][.='11']" ,meta+"/int[@name='between'][.='11']"
); );
assertQ("check before is not inclusive of upper bound by default", assertQ("check before is not inclusive of upper bound by default",
req("q", "*:*" req("q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-05T00:00:00.000Z" ,p+".start", "1976-07-05T00:00:00.000Z"
,"facet.date.end", "1976-07-07T00:00:00.000Z" ,p+".end", "1976-07-07T00:00:00.000Z"
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
) )
// 2 gaps + pre+post+inner = 5 ,"*[count("+pre+"/int)="+(rangeMode ? 2 : 5)+"]"
,"*[count("+pre+"/int)=5]"
,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]"
,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]"
,pre+"/int[@name='before' ][.='5']" ,meta+"/int[@name='before' ][.='5']"
); );
assertQ("check after is not inclusive of lower bound by default", assertQ("check after is not inclusive of lower bound by default",
req("q", "*:*" req("q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-03T00:00:00.000Z" ,p+".start", "1976-07-03T00:00:00.000Z"
,"facet.date.end", "1976-07-05T00:00:00.000Z" ,p+".end", "1976-07-05T00:00:00.000Z"
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
) )
// 2 gaps + pre+post+inner = 5 ,"*[count("+pre+"/int)="+(rangeMode ? 2 : 5)+"]"
,"*[count("+pre+"/int)=5]"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]"
,pre+"/int[@name='1976-07-04T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='2' ]"
,pre+"/int[@name='after' ][.='8']" ,meta+"/int[@name='after' ][.='8']"
); );
@ -429,68 +457,88 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-13T00:00:00.000Z" ,p+".end", "1976-07-13T00:00:00.000Z"
,"facet.date.gap", "+5DAYS" ,p+".gap", "+5DAYS"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.hardend","false" ,p+".hardend","false"
) )
// 3 gaps + pre+post+inner = 6 ,"*[count("+pre+"/int)="+(rangeMode ? 3 : 6)+"]"
,"*[count("+pre+"/int)=6]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5' ]"
,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]"
,pre+"/int[@name='1976-07-11T00:00:00Z'][.='4' ]" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='4' ]"
,pre+"/int[@name='before' ][.='2']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='after' ][.='3']" ,meta+"/int[@name='after' ][.='3']"
,pre+"/int[@name='between'][.='9']" ,meta+"/int[@name='between'][.='9']"
); );
assertQ("check hardend=true", assertQ("check hardend=true",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-13T00:00:00.000Z" ,p+".end", "1976-07-13T00:00:00.000Z"
,"facet.date.gap", "+5DAYS" ,p+".gap", "+5DAYS"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.hardend","true" ,p+".hardend","true"
) )
// 3 gaps + pre+post+inner = 6 ,"*[count("+pre+"/int)="+(rangeMode ? 3 : 6)+"]"
,"*[count("+pre+"/int)=6]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5' ]"
,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]" ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]"
,pre+"/int[@name='1976-07-11T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='2']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='after' ][.='6']" ,meta+"/int[@name='after' ][.='6']"
,pre+"/int[@name='between'][.='6']" ,meta+"/int[@name='between'][.='6']"
); );
} }
/** similar to testDateFacets, but a differnet field with test data @Test
exactly on on boundary marks */ public void testTrieDateFacetsWithIncludeOption() {
helpTestDateFacetsWithIncludeOption("a_tdt", false);
}
@Test @Test
public void testDateFacetsWithIncludeOption() { public void testDateFacetsWithIncludeOption() {
final String f = "a_tdt"; helpTestDateFacetsWithIncludeOption("a_pdt", false);
final String pre = "//lst[@name='facet_dates']/lst[@name='"+f+"']"; }
@Test
public void testTrieDateRangeFacetsWithIncludeOption() {
helpTestDateFacetsWithIncludeOption("a_tdt", true);
}
@Test
public void testDateRangeFacetsWithIncludeOption() {
helpTestDateFacetsWithIncludeOption("a_pdt", true);
}
/** similar to helpTestDateFacets, but for differnet fields with test data
exactly on on boundary marks */
private void helpTestDateFacetsWithIncludeOption(final String fieldName,
final boolean rangeMode) {
final String p = rangeMode ? "facet.range" : "facet.date";
final String b = rangeMode ? "facet_ranges" : "facet_dates";
final String f = fieldName;
final String c = (rangeMode ? "/lst[@name='counts']" : "");
final String pre = "//lst[@name='"+b+"']/lst[@name='"+f+"']" + c;
final String meta = pre + (rangeMode ? "/../" : "");
assertQ("checking counts for lower", assertQ("checking counts for lower",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-16T00:00:00.000Z" ,p+".end", "1976-07-16T00:00:00.000Z"
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.include", "lower" ,p+".include", "lower"
) )
// 15 days + pre+post+inner = 18 // 15 days + pre+post+inner = 18
,"*[count("+pre+"/int)=18]" ,"*[count("+pre+"/int)="+(rangeMode ? 15 : 18)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']"
@ -506,24 +554,25 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-13T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='2' ]"
,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='1']" //
,pre+"/int[@name='after' ][.='1']" ,meta+"/int[@name='before' ][.='1']"
,pre+"/int[@name='between'][.='8']" ,meta+"/int[@name='after' ][.='1']"
,meta+"/int[@name='between'][.='8']"
); );
assertQ("checking counts for upper", assertQ("checking counts for upper",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-16T00:00:00.000Z" ,p+".end", "1976-07-16T00:00:00.000Z"
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.include", "upper" ,p+".include", "upper"
) )
// 15 days + pre+post+inner = 18 // 15 days + pre+post+inner = 18
,"*[count("+pre+"/int)=18]" ,"*[count("+pre+"/int)="+(rangeMode ? 15 : 18)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]"
@ -539,25 +588,26 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='2']" //
,pre+"/int[@name='after' ][.='1']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='between'][.='7']" ,meta+"/int[@name='after' ][.='1']"
,meta+"/int[@name='between'][.='7']"
); );
assertQ("checking counts for lower & upper", assertQ("checking counts for lower & upper",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-16T00:00:00.000Z" ,p+".end", "1976-07-16T00:00:00.000Z"
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.include", "lower" ,p+".include", "lower"
,"facet.date.include", "upper" ,p+".include", "upper"
) )
// 15 days + pre+post+inner = 18 // 15 days + pre+post+inner = 18
,"*[count("+pre+"/int)=18]" ,"*[count("+pre+"/int)="+(rangeMode ? 15 : 18)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]"
@ -573,25 +623,26 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-13T00:00:00Z'][.='2' ]" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='2' ]"
,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='1']" //
,pre+"/int[@name='after' ][.='1']" ,meta+"/int[@name='before' ][.='1']"
,pre+"/int[@name='between'][.='8']" ,meta+"/int[@name='after' ][.='1']"
,meta+"/int[@name='between'][.='8']"
); );
assertQ("checking counts for upper & edge", assertQ("checking counts for upper & edge",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-16T00:00:00.000Z" ,p+".end", "1976-07-16T00:00:00.000Z"
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.include", "upper" ,p+".include", "upper"
,"facet.date.include", "edge" ,p+".include", "edge"
) )
// 15 days + pre+post+inner = 18 // 15 days + pre+post+inner = 18
,"*[count("+pre+"/int)=18]" ,"*[count("+pre+"/int)="+(rangeMode ? 15 : 18)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]"
@ -607,25 +658,26 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='1']" //
,pre+"/int[@name='after' ][.='1']" ,meta+"/int[@name='before' ][.='1']"
,pre+"/int[@name='between'][.='8']" ,meta+"/int[@name='after' ][.='1']"
,meta+"/int[@name='between'][.='8']"
); );
assertQ("checking counts for upper & outer", assertQ("checking counts for upper & outer",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.include", "upper" ,p+".include", "upper"
,"facet.date.include", "outer" ,p+".include", "outer"
) )
// 12 days + pre+post+inner = 15 // 12 days + pre+post+inner = 15
,"*[count("+pre+"/int)=15]" ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]"
@ -638,25 +690,26 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='2']" //
,pre+"/int[@name='after' ][.='4']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='between'][.='5']" ,meta+"/int[@name='after' ][.='4']"
,meta+"/int[@name='between'][.='5']"
); );
assertQ("checking counts for lower & edge", assertQ("checking counts for lower & edge",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.include", "lower" ,p+".include", "lower"
,"facet.date.include", "edge" ,p+".include", "edge"
) )
// 12 days + pre+post+inner = 15 // 12 days + pre+post+inner = 15
,"*[count("+pre+"/int)=15]" ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']"
@ -669,25 +722,26 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='1']" //
,pre+"/int[@name='after' ][.='3']" ,meta+"/int[@name='before' ][.='1']"
,pre+"/int[@name='between'][.='6']" ,meta+"/int[@name='after' ][.='3']"
,meta+"/int[@name='between'][.='6']"
); );
assertQ("checking counts for lower & outer", assertQ("checking counts for lower & outer",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.include", "lower" ,p+".include", "lower"
,"facet.date.include", "outer" ,p+".include", "outer"
) )
// 12 days + pre+post+inner = 15 // 12 days + pre+post+inner = 15
,"*[count("+pre+"/int)=15]" ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']"
@ -700,26 +754,27 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-12T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='0']"
,pre+"/int[@name='before' ][.='2']" //
,pre+"/int[@name='after' ][.='4']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='between'][.='5']" ,meta+"/int[@name='after' ][.='4']"
,meta+"/int[@name='between'][.='5']"
); );
assertQ("checking counts for lower & edge & outer", assertQ("checking counts for lower & edge & outer",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.include", "lower" ,p+".include", "lower"
,"facet.date.include", "edge" ,p+".include", "edge"
,"facet.date.include", "outer" ,p+".include", "outer"
) )
// 12 days + pre+post+inner = 15 // 12 days + pre+post+inner = 15
,"*[count("+pre+"/int)=15]" ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']"
@ -732,24 +787,25 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='2']" //
,pre+"/int[@name='after' ][.='4']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='between'][.='6']" ,meta+"/int[@name='after' ][.='4']"
,meta+"/int[@name='between'][.='6']"
); );
assertQ("checking counts for all", assertQ("checking counts for all",
req( "q", "*:*" req( "q", "*:*"
,"rows", "0" ,"rows", "0"
,"facet", "true" ,"facet", "true"
,"facet.date", f ,p, f
,"facet.date.start", "1976-07-01T00:00:00.000Z" ,p+".start", "1976-07-01T00:00:00.000Z"
,"facet.date.end", "1976-07-13T00:00:00.000Z" // smaller now ,p+".end", "1976-07-13T00:00:00.000Z" // smaller now
,"facet.date.gap", "+1DAY" ,p+".gap", "+1DAY"
,"facet.date.other", "all" ,p+".other", "all"
,"facet.date.include", "all" ,p+".include", "all"
) )
// 12 days + pre+post+inner = 15 // 12 days + pre+post+inner = 15
,"*[count("+pre+"/int)=15]" ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1' ]"
,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1' ]"
@ -762,12 +818,302 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]"
,pre+"/int[@name='before' ][.='2']" //
,pre+"/int[@name='after' ][.='4']" ,meta+"/int[@name='before' ][.='2']"
,pre+"/int[@name='between'][.='6']" ,meta+"/int[@name='after' ][.='4']"
,meta+"/int[@name='between'][.='6']"
); );
} }
@Test
public void testNumericRangeFacetsTrieFloat() {
helpTestFractionalNumberRangeFacets("range_facet_f");
}
@Test
public void testNumericRangeFacetsTrieDouble() {
helpTestFractionalNumberRangeFacets("range_facet_d");
}
@Test
public void testNumericRangeFacetsSortableFloat() {
helpTestFractionalNumberRangeFacets("range_facet_sf");
}
@Test
public void testNumericRangeFacetsSortableDouble() {
helpTestFractionalNumberRangeFacets("range_facet_sd");
}
private void helpTestFractionalNumberRangeFacets(final String fieldName) {
final String f = fieldName;
final String pre = "//lst[@name='facet_ranges']/lst[@name='"+f+"']/lst[@name='counts']";
final String meta = pre + "/../";
assertQ(f+": checking counts for lower",
req( "q", "*:*"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "10"
,"facet.range.end", "50"
,"facet.range.gap", "10"
,"facet.range.other", "all"
,"facet.range.include", "lower"
)
,"*[count("+pre+"/int)=4]"
,pre+"/int[@name='10.0'][.='1' ]"
,pre+"/int[@name='20.0'][.='3' ]"
,pre+"/int[@name='30.0'][.='2' ]"
,pre+"/int[@name='40.0'][.='0' ]"
//
,meta+"/int[@name='before' ][.='0']"
,meta+"/int[@name='after' ][.='0']"
,meta+"/int[@name='between'][.='6']"
);
assertQ(f + ":checking counts for upper",
req( "q", "*:*"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "10"
,"facet.range.end", "50"
,"facet.range.gap", "10"
,"facet.range.other", "all"
,"facet.range.include", "upper"
)
,"*[count("+pre+"/int)=4]"
,pre+"/int[@name='10.0'][.='2' ]"
,pre+"/int[@name='20.0'][.='3' ]"
,pre+"/int[@name='30.0'][.='1' ]"
,pre+"/int[@name='40.0'][.='0' ]"
//
,meta+"/int[@name='before' ][.='0']"
,meta+"/int[@name='after' ][.='0']"
,meta+"/int[@name='between'][.='6']"
);
assertQ(f + ":checking counts for lower & upper",
req( "q", "*:*"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "10"
,"facet.range.end", "50"
,"facet.range.gap", "10"
,"facet.range.other", "all"
,"facet.range.include", "upper"
,"facet.range.include", "lower"
)
,"*[count("+pre+"/int)=4]"
,pre+"/int[@name='10.0'][.='2' ]"
,pre+"/int[@name='20.0'][.='4' ]"
,pre+"/int[@name='30.0'][.='2' ]"
,pre+"/int[@name='40.0'][.='0' ]"
//
,meta+"/int[@name='before' ][.='0']"
,meta+"/int[@name='after' ][.='0']"
,meta+"/int[@name='between'][.='6']"
);
assertQ(f + ": checking counts for upper & edge",
req( "q", "*:*"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "20"
,"facet.range.end", "50"
,"facet.range.gap", "10"
,"facet.range.other", "all"
,"facet.range.include", "upper"
,"facet.range.include", "edge"
)
,"*[count("+pre+"/int)=3]"
,pre+"/int[@name='20.0'][.='4' ]"
,pre+"/int[@name='30.0'][.='1' ]"
,pre+"/int[@name='40.0'][.='0' ]"
//
,meta+"/int[@name='before' ][.='1']"
,meta+"/int[@name='after' ][.='0']"
,meta+"/int[@name='between'][.='5']"
);
assertQ(f + ": checking counts for upper & outer",
req( "q", "*:*"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "10"
,"facet.range.end", "30"
,"facet.range.gap", "10"
,"facet.range.other", "all"
,"facet.range.include", "upper"
,"facet.range.include", "outer"
)
,"*[count("+pre+"/int)=2]"
,pre+"/int[@name='10.0'][.='2' ]"
,pre+"/int[@name='20.0'][.='3' ]"
//
,meta+"/int[@name='before' ][.='0']"
,meta+"/int[@name='after' ][.='2']"
,meta+"/int[@name='between'][.='5']"
);
assertQ(f + ": checking counts for lower & edge",
req( "q", "*:*"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "10"
,"facet.range.end", "30"
,"facet.range.gap", "10"
,"facet.range.other", "all"
,"facet.range.include", "lower"
,"facet.range.include", "edge"
)
,"*[count("+pre+"/int)=2]"
,pre+"/int[@name='10.0'][.='1' ]"
,pre+"/int[@name='20.0'][.='4' ]"
//
,meta+"/int[@name='before' ][.='0']"
,meta+"/int[@name='after' ][.='1']"
,meta+"/int[@name='between'][.='5']"
);
assertQ(f + ": checking counts for lower & outer",
req( "q", "*:*"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "20"
,"facet.range.end", "40"
,"facet.range.gap", "10"
,"facet.range.other", "all"
,"facet.range.include", "lower"
,"facet.range.include", "outer"
)
,"*[count("+pre+"/int)=2]"
,pre+"/int[@name='20.0'][.='3' ]"
,pre+"/int[@name='30.0'][.='2' ]"
//
,meta+"/int[@name='before' ][.='2']"
,meta+"/int[@name='after' ][.='0']"
,meta+"/int[@name='between'][.='5']"
);
assertQ(f + ": checking counts for lower & edge & outer",
req( "q", "*:*"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "20"
,"facet.range.end", "35.3"
,"facet.range.gap", "10"
,"facet.range.other", "all"
,"facet.range.hardend", "true"
,"facet.range.include", "lower"
,"facet.range.include", "edge"
,"facet.range.include", "outer"
)
,"*[count("+pre+"/int)=2]"
,pre+"/int[@name='20.0'][.='3' ]"
,pre+"/int[@name='30.0'][.='2' ]"
//
,meta+"/int[@name='before' ][.='2']"
,meta+"/int[@name='after' ][.='1']"
,meta+"/int[@name='between'][.='5']"
);
assertQ(f + ": checking counts for include all",
req( "q", "*:*"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "20"
,"facet.range.end", "35.3"
,"facet.range.gap", "10"
,"facet.range.other", "all"
,"facet.range.hardend", "true"
,"facet.range.include", "all"
)
,"*[count("+pre+"/int)=2]"
,pre+"/int[@name='20.0'][.='4' ]"
,pre+"/int[@name='30.0'][.='2' ]"
//
,meta+"/int[@name='before' ][.='2']"
,meta+"/int[@name='after' ][.='1']"
,meta+"/int[@name='between'][.='5']"
);
}
@Test
public void testNumericRangeFacetsTrieInt() {
helpTestWholeNumberRangeFacets("id");
}
@Test
public void testNumericRangeFacetsTrieLong() {
helpTestWholeNumberRangeFacets("range_facet_l");
}
@Test
public void testNumericRangeFacetsSortableInt() {
helpTestWholeNumberRangeFacets("range_facet_si");
}
@Test
public void testNumericRangeFacetsSortableLong() {
helpTestWholeNumberRangeFacets("range_facet_sl");
}
private void helpTestWholeNumberRangeFacets(final String fieldName) {
// the float test covers a lot of the weird edge cases
// here we just need some basic sanity checking of the parsing
final String f = fieldName;
final String pre = "//lst[@name='facet_ranges']/lst[@name='"+f+"']/lst[@name='counts']";
final String meta = pre + "/../";
assertQ(f+": checking counts for lower",
req( "q", "id:[30 TO 60]"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "35"
,"facet.range.end", "50"
,"facet.range.gap", "5"
,"facet.range.other", "all"
,"facet.range.include", "lower"
)
,"*[count("+pre+"/int)=3]"
,pre+"/int[@name='35'][.='0' ]"
,pre+"/int[@name='40'][.='3' ]"
,pre+"/int[@name='45'][.='3' ]"
//
,meta+"/int[@name='before' ][.='0']"
,meta+"/int[@name='after' ][.='0']"
,meta+"/int[@name='between'][.='6']"
);
assertQ(f + ":checking counts for upper",
req( "q", "id:[30 TO 60]"
,"rows", "0"
,"facet", "true"
,"facet.range", f
,"facet.range.start", "35"
,"facet.range.end", "50"
,"facet.range.gap", "5"
,"facet.range.other", "all"
,"facet.range.include", "upper"
)
,"*[count("+pre+"/int)=3]"
,pre+"/int[@name='35'][.='0' ]"
,pre+"/int[@name='40'][.='4' ]"
,pre+"/int[@name='45'][.='2' ]"
//
,meta+"/int[@name='before' ][.='0']"
,meta+"/int[@name='after' ][.='0']"
,meta+"/int[@name='between'][.='6']"
);
}
static void indexFacetSingleValued() { static void indexFacetSingleValued() {
indexFacets("40","t_s1"); indexFacets("40","t_s1");
} }

View File

@ -564,7 +564,18 @@
<copyField source="subject" dest="text"/> <copyField source="subject" dest="text"/>
<copyField source="*_t" dest="text"/> <copyField source="*_t" dest="text"/>
<copyField source="id" dest="range_facet_si"/>
<copyField source="id" dest="range_facet_l"/>
<copyField source="id" dest="range_facet_sl"/>
<copyField source="range_facet_f" dest="range_facet_sf"/>
<copyField source="range_facet_f" dest="range_facet_d"/>
<copyField source="range_facet_f" dest="range_facet_sd"/>
<copyField source="bday" dest="bday_pdt"/>
<copyField source="a_tdt" dest="a_pdt"/>
<!-- dynamic destination --> <!-- dynamic destination -->
<copyField source="*_dynamic" dest="dynamic_*"/> <copyField source="*_dynamic" dest="dynamic_*"/>