SOLR-6349 + SOLR-6682: Added support for stats.field localparams to enable/disable individual stats; Fix response when using EnumField with StatsComponent

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1665579 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Chris M. Hostetter 2015-03-10 15:34:06 +00:00
parent ce47eee2f4
commit 10dd59701c
23 changed files with 1665 additions and 640 deletions

View File

@ -87,6 +87,7 @@ com.sun.jersey.version = 1.9
/org.apache.ant/ant = 1.8.2
/org.apache.avro/avro = 1.7.5
/org.apache.commons/commons-compress = 1.8.1
/org.apache.commons/commons-math3 = 3.4.1
/org.apache.derby/derby = 10.9.1.0
/org.apache.directory.server/apacheds-all = 2.0.0-M15

View File

@ -158,6 +158,12 @@ New Features
* SOLR-7126: Secure loading of runtime external jars (Noble Paul)
* SOLR-6349: Added support for stats.field localparams to enable/disable individual stats to
limit the amount of computation done and the amount of data returned.
eg: stats.field={!min=true max=true}field_name
(Tomas Fernandez-Lobbe, Xu Zhang, hossman)
Bug Fixes
----------------------
@ -221,6 +227,9 @@ Bug Fixes
looking for start.jar.
(Xu Zhang via Shawn Heisey)
* SOLR-6682: Fix response when using EnumField with StatsComponent
(Xu Zhang via hossman)
Optimizations
----------------------

View File

@ -37,6 +37,7 @@
<dependency org="com.google.guava" name="guava" rev="${/com.google.guava/guava}" conf="compile"/>
<dependency org="com.spatial4j" name="spatial4j" rev="${/com.spatial4j/spatial4j}" conf="compile"/>
<dependency org="org.antlr" name="antlr-runtime" rev="${/org.antlr/antlr-runtime}" conf="compile"/>
<dependency org="org.apache.commons" name="commons-math3" rev="${/org.apache.commons/commons-math3}" conf="test"/>
<dependency org="org.ow2.asm" name="asm" rev="${/org.ow2.asm/asm}" conf="compile"/>
<dependency org="org.ow2.asm" name="asm-commons" rev="${/org.ow2.asm/asm-commons}" conf="compile"/>
<dependency org="org.restlet.jee" name="org.restlet" rev="${/org.restlet.jee/org.restlet}" conf="compile"/>

View File

@ -278,8 +278,7 @@ public class PivotFacetProcessor extends SimpleFacets
for (StatsField statsField : statsFields) {
stv.put(statsField.getOutputKey(), statsField.computeLocalStatsValues(subset));
}
// for pivots, we *always* include requested stats - even if 'empty'
pivot.add("stats", StatsComponent.convertToResponse(true, stv));
pivot.add("stats", StatsComponent.convertToResponse(stv));
}
values.add( pivot );
}

View File

@ -183,8 +183,7 @@ public class PivotFacetValue {
}
if (null != statsValues) {
newList.add(PivotListEntry.STATS.getName(),
// for pivots, we *always* include requested stats - even if 'empty'
StatsComponent.convertToResponse(true, statsValues));
StatsComponent.convertToResponse(statsValues));
}
return newList;
}

View File

@ -52,7 +52,7 @@ public class StatsComponent extends SearchComponent {
@Override
public void process(ResponseBuilder rb) throws IOException {
if (!rb.doStats) return;
boolean isShard = rb.req.getParams().getBool(ShardParams.IS_SHARD, false);
Map<String, StatsValues> statsValues = new LinkedHashMap<>();
@ -61,7 +61,7 @@ public class StatsComponent extends SearchComponent {
statsValues.put(statsField.getOutputKey(), statsField.computeLocalStatsValues(docs));
}
rb.rsp.add( "stats", convertToResponse(isShard, statsValues) );
rb.rsp.add( "stats", convertToResponse(statsValues) );
}
@Override
@ -122,7 +122,7 @@ public class StatsComponent extends SearchComponent {
// so that "result" is already stored in the response (for aesthetics)
Map<String, StatsValues> allStatsValues = rb._statsInfo.getAggregateStatsValues();
rb.rsp.add("stats", convertToResponse(false, allStatsValues));
rb.rsp.add("stats", convertToResponse(allStatsValues));
rb._statsInfo = null; // free some objects
}
@ -142,7 +142,7 @@ public class StatsComponent extends SearchComponent {
* including the esoteric "stats_fields" wrapper.
*/
public static NamedList<NamedList<NamedList<?>>> convertToResponse
(boolean force, Map<String,StatsValues> statsValues) {
(Map<String,StatsValues> statsValues) {
NamedList<NamedList<NamedList<?>>> stats = new SimpleOrderedMap<>();
NamedList<NamedList<?>> stats_fields = new SimpleOrderedMap<>();
@ -151,11 +151,7 @@ public class StatsComponent extends SearchComponent {
for (Map.Entry<String,StatsValues> entry : statsValues.entrySet()) {
String key = entry.getKey();
NamedList stv = entry.getValue().getStatsValues();
if (force || ((Long) stv.get("count") != 0)) {
stats_fields.add(key, stv);
} else {
stats_fields.add(key, null);
}
stats_fields.add(key, stv);
}
return stats;
}

View File

@ -19,30 +19,32 @@ package org.apache.solr.handler.component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.*;
import org.apache.lucene.queries.function.FunctionQuery;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.valuesource.QueryValueSource;
import org.apache.lucene.queries.function.valuesource.FieldCacheSource;
import org.apache.lucene.queries.function.valuesource.QueryValueSource;
import org.apache.lucene.search.Query;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.StatsParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.request.SolrQueryRequest; // jdocs
import org.apache.solr.request.DocValuesStats;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.DocIterator;
@ -60,6 +62,74 @@ import org.apache.solr.search.SyntaxError;
* @see StatsComponent
*/
public class StatsField {
/**
* An enumeration representing the sumer set of all possible stat values that can be computed.
* Each of these enum values can be specified as a local param in a <code>stats.field</code>
* (eg: <code>stats.field={!min=true mean=true}my_field_name</code>) but not all enum values
* are valid for all field types (eg: <code>mean</code> is meaningless for String fields)
*
* @lucene.internal
* @lucene.experimental
*/
public static enum Stat {
min(true),
max(true),
missing(true),
sum(true),
count(true),
mean(false, sum, count),
sumOfSquares(true),
stddev(false, sum, count, sumOfSquares),
calcdistinct(true);
private final List<Stat> distribDeps;
/**
* Sole constructor for Stat enum values
* @param deps the set of stat values, other then this one, which are a distributed
* dependency and must be computed and returned by each individual shards in
* order to compute <i>this</i> stat over the entire distributed result set.
* @param selfDep indicates that when computing this stat across a distributed result
* set, each shard must compute this stat <i>in addition to</i> any other
* distributed dependences.
* @see #getDistribDeps
*/
Stat(boolean selfDep, Stat... deps) {
distribDeps = new ArrayList<Stat>(deps.length+1);
distribDeps.addAll(Arrays.asList(deps));
if (selfDep) {
distribDeps.add(this);
}
}
/**
* Given a String, returns the corrisponding Stat enum value if any, otherwise returns null.
*/
public static Stat forName(String paramKey) {
try {
return Stat.valueOf(paramKey);
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* The stats that must be computed and returned by each shard involved in a distributed
* request in order to compute the overall value for this stat across the entire distributed
* result set. A Stat instance may include itself in the <code>getDistribDeps()</code> result,
* but that is not always the case.
*/
public EnumSet<Stat> getDistribDeps() {
return EnumSet.copyOf(this.distribDeps);
}
}
/**
* The set of stats computed by default when no localparams are used to specify explicit stats
*/
public final static Set<Stat> DEFAULT_STATS = Collections.<Stat>unmodifiableSet
(EnumSet.of(Stat.min, Stat.max, Stat.missing, Stat.sum, Stat.count, Stat.mean, Stat.sumOfSquares, Stat.stddev));
private final SolrIndexSearcher searcher;
private final ResponseBuilder rb;
@ -68,10 +138,13 @@ public class StatsField {
private final ValueSource valueSource; // may be null if simple field stats
private final SchemaField schemaField; // may be null if function/query stats
private final String key;
private final boolean calcDistinct; // TODO: put this inside localParams ? SOLR-6349 ?
private final boolean topLevelCalcDistinct;
private final String[] facets;
private final List<String> tagList;
private final List<String> excludeTagList;
private final EnumSet<Stat> statsToCalculate = EnumSet.noneOf(Stat.class);
private final EnumSet<Stat> statsInResponse = EnumSet.noneOf(Stat.class);
private final boolean isShard;
/**
* @param rb the current request/response
@ -84,6 +157,7 @@ public class StatsField {
SolrParams params = rb.req.getParams();
try {
isShard = params.getBool("isShard", false);
SolrParams localParams = QueryParsing.getLocalParams(originalParam, params);
if (null == localParams) {
// simplest possible input: bare string (field name)
@ -91,7 +165,9 @@ public class StatsField {
customParams.add(QueryParsing.V, originalParam);
localParams = customParams;
}
this.localParams = localParams;
String parserName = localParams.get(QueryParsing.TYPE);
SchemaField sf = null;
@ -141,11 +217,12 @@ public class StatsField {
// default to entire original param str.
originalParam));
this.calcDistinct = null == schemaField
? params.getBool(StatsParams.STATS_CALC_DISTINCT, false)
: params.getFieldBool(schemaField.getName(), StatsParams.STATS_CALC_DISTINCT, false);
this.topLevelCalcDistinct = null == schemaField
? params.getBool(StatsParams.STATS_CALC_DISTINCT, false)
: params.getFieldBool(schemaField.getName(), StatsParams.STATS_CALC_DISTINCT, false);
populateStatsSets();
String[] facets = params.getFieldParams(key, StatsParams.STATS_FACET);
this.facets = (null == facets) ? new String[0] : facets;
String tagStr = localParams.get(CommonParams.TAG);
@ -360,15 +437,6 @@ public class StatsField {
return valueSource;
}
/**
* Whether or not the effective value of the {@link StatsParams#STATS_CALC_DISTINCT} param
* is true or false for this StatsField
*/
public boolean getCalcDistinct() {
return calcDistinct;
}
public List<String> getTagList() {
return tagList;
}
@ -377,4 +445,59 @@ public class StatsField {
return "StatsField<" + originalParam + ">";
}
/**
* A helper method which inspects the {@link #localParams} associated with this StatsField,
* and uses them to populate the {@link #statsInResponse} and {@link #statsToCalculate} data
* structures
*/
private void populateStatsSets() {
boolean statSpecifiedByLocalParam = false;
// local individual stat
Iterator<String> itParams = localParams.getParameterNamesIterator();
while (itParams.hasNext()) {
String paramKey = itParams.next();
Stat stat = Stat.forName(paramKey);
if (stat != null) {
statSpecifiedByLocalParam = true;
// TODO: this isn't going to work for planned "non-boolean' stats - eg: SOLR-6350, SOLR-6968
if (localParams.getBool(paramKey, false)) {
statsInResponse.add(stat);
statsToCalculate.addAll(stat.getDistribDeps());
}
}
}
// if no individual stat setting.
if ( ! statSpecifiedByLocalParam ) {
statsInResponse.addAll(DEFAULT_STATS);
for (Stat stat : statsInResponse) {
statsToCalculate.addAll(stat.getDistribDeps());
}
}
// calcDistinct has special "default" behavior using top level CalcDistinct param
if (topLevelCalcDistinct && localParams.getBool(Stat.calcdistinct.toString(), true)) {
statsInResponse.add(Stat.calcdistinct);
statsToCalculate.addAll(Stat.calcdistinct.getDistribDeps());
}
}
public boolean calculateStats(Stat stat) {
return statsToCalculate.contains(stat);
}
public boolean includeInResponse(Stat stat) {
if (isShard) {
return statsToCalculate.contains(stat);
}
if (statsInResponse.contains(stat)) {
return true;
}
return false;
}
}

View File

@ -28,6 +28,7 @@ import org.apache.solr.common.EnumFieldValue;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.handler.component.StatsField.Stat;
import org.apache.solr.schema.*;
/**
@ -79,17 +80,19 @@ abstract class AbstractStatsValues<T> implements StatsValues {
/** Tracks all data about tthe stats we need to collect */
final protected StatsField statsField;
/**
* local copy to save method dispatch in tight loops
* @see StatsField#getCalcDistinct
*/
final protected boolean calcDistinct;
/** may be null if we are collecting stats directly from a function ValueSource */
final protected SchemaField sf;
/** may be null if we are collecting stats directly from a function ValueSource */
final protected FieldType ft;
// final booleans from StatsField to allow better inlining & JIT optimizing
final protected boolean computeCount;
final protected boolean computeMissing;
final protected boolean computeCalcDistinct;
final protected boolean computeMin;
final protected boolean computeMax;
final protected boolean computeMinOrMax;
/**
* Either a function value source to collect from, or the ValueSource associated
* with a single valued field we are collecting from. Will be null until/unless
@ -112,15 +115,21 @@ abstract class AbstractStatsValues<T> implements StatsValues {
protected long missing;
protected long count;
protected long countDistinct;
protected Set<T> distinctValues;
protected final Set<T> distinctValues;
// facetField facetValue
protected Map<String, Map<String, StatsValues>> facets = new HashMap<>();
protected AbstractStatsValues(StatsField statsField) {
this.statsField = statsField;
this.calcDistinct = statsField.getCalcDistinct();
this.distinctValues = new TreeSet<>();
this.computeCount = statsField.calculateStats(Stat.count);
this.computeMissing = statsField.calculateStats(Stat.missing);
this.computeCalcDistinct = statsField.calculateStats(Stat.calcdistinct);
this.computeMin = statsField.calculateStats(Stat.min);
this.computeMax = statsField.calculateStats(Stat.max);
this.computeMinOrMax = computeMin || computeMax;
this.distinctValues = computeCalcDistinct ? new TreeSet<>() : null;
// alternatively, we could refactor a common base class that doesn't know/care
// about either SchemaField or ValueSource - but then there would be a lot of
@ -149,14 +158,20 @@ abstract class AbstractStatsValues<T> implements StatsValues {
*/
@Override
public void accumulate(NamedList stv) {
count += (Long) stv.get("count");
missing += (Long) stv.get("missing");
if (calcDistinct) {
if (computeCount) {
count += (Long) stv.get("count");
}
if (computeMissing) {
missing += (Long) stv.get("missing");
}
if (computeCalcDistinct) {
distinctValues.addAll((Collection<T>) stv.get("distinctValues"));
countDistinct = distinctValues.size();
}
updateMinMax((T) stv.get("min"), (T) stv.get("max"));
if (computeMinOrMax) {
updateMinMax((T) stv.get("min"), (T) stv.get("max"));
}
updateTypeSpecificStats(stv);
NamedList f = (NamedList) stv.get(FACETS);
@ -197,12 +212,16 @@ abstract class AbstractStatsValues<T> implements StatsValues {
}
public void accumulate(T value, int count) {
this.count += count;
if (calcDistinct) {
if (computeCount) {
this.count += count;
}
if (computeCalcDistinct) {
distinctValues.add(value);
countDistinct = distinctValues.size();
}
updateMinMax(value, value);
if (computeMinOrMax) {
updateMinMax(value, value);
}
updateTypeSpecificStats(value, count);
}
@ -211,7 +230,9 @@ abstract class AbstractStatsValues<T> implements StatsValues {
*/
@Override
public void missing() {
missing++;
if (computeMissing) {
missing++;
}
}
/**
@ -237,27 +258,39 @@ abstract class AbstractStatsValues<T> implements StatsValues {
public NamedList<?> getStatsValues() {
NamedList<Object> res = new SimpleOrderedMap<>();
res.add("min", min);
res.add("max", max);
res.add("count", count);
res.add("missing", missing);
if (calcDistinct) {
if (statsField.includeInResponse(Stat.min)) {
res.add("min", min);
}
if (statsField.includeInResponse(Stat.max)) {
res.add("max", max);
}
if (statsField.includeInResponse(Stat.count)) {
res.add("count", count);
}
if (statsField.includeInResponse(Stat.missing)) {
res.add("missing", missing);
}
if (statsField.includeInResponse(Stat.calcdistinct)) {
res.add("distinctValues", distinctValues);
res.add("countDistinct", countDistinct);
}
addTypeSpecificStats(res);
if (!facets.isEmpty()) {
// add the facet stats
NamedList<NamedList<?>> nl = new SimpleOrderedMap<>();
for (Map.Entry<String, Map<String, StatsValues>> entry : facets.entrySet()) {
NamedList<NamedList<?>> nl2 = new SimpleOrderedMap<>();
nl.add(entry.getKey(), nl2);
for (Map.Entry<String, StatsValues> e2 : entry.getValue().entrySet()) {
nl2.add(e2.getKey(), e2.getValue().getStatsValues());
}
// add the facet stats
NamedList<NamedList<?>> nl = new SimpleOrderedMap<>();
for (Map.Entry<String, Map<String, StatsValues>> entry : facets.entrySet()) {
NamedList<NamedList<?>> nl2 = new SimpleOrderedMap<>();
nl.add(entry.getKey(), nl2);
for (Map.Entry<String, StatsValues> e2 : entry.getValue().entrySet()) {
nl2.add(e2.getKey(), e2.getValue().getStatsValues());
}
}
res.add(FACETS, nl);
}
res.add(FACETS, nl);
return res;
}
@ -314,8 +347,13 @@ class NumericStatsValues extends AbstractStatsValues<Number> {
double sum;
double sumOfSquares;
final protected boolean computeSum;
final protected boolean computeSumOfSquares;
public NumericStatsValues(StatsField statsField) {
super(statsField);
this.computeSum = statsField.calculateStats(Stat.sum);
this.computeSumOfSquares = statsField.calculateStats(Stat.sumOfSquares);
}
@Override
@ -332,8 +370,12 @@ class NumericStatsValues extends AbstractStatsValues<Number> {
*/
@Override
public void updateTypeSpecificStats(NamedList stv) {
sum += ((Number)stv.get("sum")).doubleValue();
sumOfSquares += ((Number)stv.get("sumOfSquares")).doubleValue();
if (computeSum) {
sum += ((Number)stv.get("sum")).doubleValue();
}
if (computeSumOfSquares) {
sumOfSquares += ((Number)stv.get("sumOfSquares")).doubleValue();
}
}
/**
@ -342,8 +384,12 @@ class NumericStatsValues extends AbstractStatsValues<Number> {
@Override
public void updateTypeSpecificStats(Number v, int count) {
double value = v.doubleValue();
sumOfSquares += (value * value * count); // for std deviation
sum += value * count;
if (computeSumOfSquares) {
sumOfSquares += (value * value * count); // for std deviation
}
if (computeSum) {
sum += value * count;
}
}
/**
@ -351,22 +397,23 @@ class NumericStatsValues extends AbstractStatsValues<Number> {
*/
@Override
protected void updateMinMax(Number min, Number max) {
if (null == min) {
assert null == max : "min is null but max isn't ? ==> " + max;
return; // No-Op
}
assert null != max : "max is null but min isn't ? ==> " + min;
// we always use the double value, because that way the response Object class is
// we always use the double values, because that way the response Object class is
// consistent regardless of whether we only have 1 value or many that we min/max
//
// TODO: would be nice to have subclasses for each type of Number ... breaks backcompat
double minD = min.doubleValue();
double maxD = max.doubleValue();
this.min = (null == this.min) ? minD : Math.min(this.min.doubleValue(), minD);
this.max = (null == this.max) ? maxD : Math.max(this.max.doubleValue(), maxD);
if (computeMin) { // nested if to encourage JIT to optimize aware final var?
if (null != min) {
double minD = min.doubleValue();
this.min = (null == this.min) ? minD : Math.min(this.min.doubleValue(), minD);
}
}
if (computeMax) { // nested if to encourage JIT to optimize aware final var?
if (null != max) {
double maxD = max.doubleValue();
this.max = (null == this.max) ? maxD : Math.max(this.max.doubleValue(), maxD);
}
}
}
/**
@ -376,10 +423,18 @@ class NumericStatsValues extends AbstractStatsValues<Number> {
*/
@Override
protected void addTypeSpecificStats(NamedList<Object> res) {
res.add("sum", sum);
res.add("sumOfSquares", sumOfSquares);
res.add("mean", sum / count);
res.add("stddev", getStandardDeviation());
if (statsField.includeInResponse(Stat.sum)) {
res.add("sum", sum);
}
if (statsField.includeInResponse(Stat.sumOfSquares)) {
res.add("sumOfSquares", sumOfSquares);
}
if (statsField.includeInResponse(Stat.mean)) {
res.add("mean", sum / count);
}
if (statsField.includeInResponse(Stat.stddev)) {
res.add("stddev", getStandardDeviation());
}
}
/**
@ -424,14 +479,20 @@ class EnumStatsValues extends AbstractStatsValues<EnumFieldValue> {
* {@inheritDoc}
*/
protected void updateMinMax(EnumFieldValue min, EnumFieldValue max) {
if (max != null) {
if (max.compareTo(this.max) > 0)
this.max = max;
if (computeMin) { // nested if to encourage JIT to optimize aware final var?
if (null != min) {
if (null == this.min || (min.compareTo(this.min) < 0)) {
this.min = min;
}
}
}
if (computeMax) { // nested if to encourage JIT to optimize aware final var?
if (null != max) {
if (null == this.max || (max.compareTo(this.max) > 0)) {
this.max = max;
}
}
}
if (this.min == null)
this.min = min;
else if (this.min.compareTo(min) > 0)
this.min = min;
}
/**
@ -470,8 +531,13 @@ class DateStatsValues extends AbstractStatsValues<Date> {
private long sum = 0;
double sumOfSquares = 0;
final protected boolean computeSum;
final protected boolean computeSumOfSquares;
public DateStatsValues(StatsField statsField) {
super(statsField);
this.computeSum = statsField.calculateStats(Stat.sum);
this.computeSumOfSquares = statsField.calculateStats(Stat.sumOfSquares);
}
@Override
@ -488,9 +554,10 @@ class DateStatsValues extends AbstractStatsValues<Date> {
*/
@Override
protected void updateTypeSpecificStats(NamedList stv) {
Date date = (Date) stv.get("sum");
if (date != null) {
sum += date.getTime();
if (computeSum) {
sum += ((Date) stv.get("sum")).getTime();
}
if (computeSumOfSquares) {
sumOfSquares += ((Number)stv.get("sumOfSquares")).doubleValue();
}
}
@ -501,8 +568,12 @@ class DateStatsValues extends AbstractStatsValues<Date> {
@Override
public void updateTypeSpecificStats(Date v, int count) {
long value = v.getTime();
sumOfSquares += (value * value * count); // for std deviation
sum += value * count;
if (computeSumOfSquares) {
sumOfSquares += (value * value * count); // for std deviation
}
if (computeSum) {
sum += value * count;
}
}
/**
@ -510,11 +581,15 @@ class DateStatsValues extends AbstractStatsValues<Date> {
*/
@Override
protected void updateMinMax(Date min, Date max) {
if(null != min && (this.min==null || this.min.after(min))) {
this.min = min;
if (computeMin) { // nested if to encourage JIT to optimize aware final var?
if (null != min && (this.min==null || this.min.after(min))) {
this.min = min;
}
}
if(null != max && (this.max==null || this.max.before(max))) {
this.max = max;
if (computeMax) { // nested if to encourage JIT to optimize aware final var?
if (null != max && (this.max==null || this.max.before(max))) {
this.max = max;
}
}
}
@ -525,15 +600,18 @@ class DateStatsValues extends AbstractStatsValues<Date> {
*/
@Override
protected void addTypeSpecificStats(NamedList<Object> res) {
if(sum<=0) {
return; // date==0 is meaningless
if (statsField.includeInResponse(Stat.sum)) {
res.add("sum", new Date(sum));
}
res.add("sum", new Date(sum));
if (count > 0) {
res.add("mean", new Date(sum / count));
if (statsField.includeInResponse(Stat.mean)) {
res.add("mean", (count > 0) ? new Date(sum / count) : null);
}
if (statsField.includeInResponse(Stat.sumOfSquares)) {
res.add("sumOfSquares", sumOfSquares);
}
if (statsField.includeInResponse(Stat.stddev)) {
res.add("stddev", getStandardDeviation());
}
res.add("sumOfSquares", sumOfSquares);
res.add("stddev", getStandardDeviation());
}
@ -594,8 +672,12 @@ class StringStatsValues extends AbstractStatsValues<String> {
*/
@Override
protected void updateMinMax(String min, String max) {
this.max = max(this.max, max);
this.min = min(this.min, min);
if (computeMin) { // nested if to encourage JIT to optimize aware final var?
this.min = min(this.min, min);
}
if (computeMax) { // nested if to encourage JIT to optimize aware final var?
this.max = max(this.max, max);
}
}
/**

View File

@ -56,7 +56,6 @@ public class DocValuesStats {
public static StatsValues getCounts(SolrIndexSearcher searcher, StatsField statsField, DocSet docs, String[] facet) throws IOException {
final SchemaField schemaField = statsField.getSchemaField();
final boolean calcDistinct = statsField.getCalcDistinct();
assert null != statsField.getSchemaField()
: "DocValuesStats requires a StatsField using a SchemaField";

View File

@ -23,35 +23,17 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.uninverting.DocTermOrds;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.CharsRefBuilder;
import org.apache.lucene.util.FixedBitSet;
import org.apache.lucene.util.UnicodeUtil;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.component.FieldFacetStats;
import org.apache.solr.handler.component.StatsField;
import org.apache.solr.handler.component.StatsValues;
import org.apache.solr.handler.component.StatsValuesFactory;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.TrieField;
import org.apache.solr.search.BitDocSet;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocSet;
import org.apache.solr.search.SolrCache;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.util.LongPriorityQueue;
import org.apache.solr.util.PrimUtils;
/**
*
@ -84,7 +66,6 @@ import org.apache.solr.util.PrimUtils;
*
*/
public class UnInvertedField extends DocTermOrds {
private static int TNUM_OFFSET=2;
static class TopTerm {
BytesRef term;
@ -214,420 +195,6 @@ public class UnInvertedField extends DocTermOrds {
return numTermsInField;
}
public NamedList<Integer> getCounts(SolrIndexSearcher searcher, DocSet baseDocs, int offset, int limit, Integer mincount, boolean missing, String sort, String prefix) throws IOException {
use.incrementAndGet();
FieldType ft = searcher.getSchema().getFieldType(field);
NamedList<Integer> res = new NamedList<>(); // order is important
DocSet docs = baseDocs;
int baseSize = docs.size();
int maxDoc = searcher.maxDoc();
//System.out.println("GET COUNTS field=" + field + " baseSize=" + baseSize + " minCount=" + mincount + " maxDoc=" + maxDoc + " numTermsInField=" + numTermsInField);
if (baseSize >= mincount) {
final int[] index = this.index;
// tricky: we add more more element than we need because we will reuse this array later
// for ordering term ords before converting to term labels.
final int[] counts = new int[numTermsInField + 1];
//
// If there is prefix, find its start and end term numbers
//
int startTerm = 0;
int endTerm = numTermsInField; // one past the end
TermsEnum te = getOrdTermsEnum(searcher.getLeafReader());
if (te != null && prefix != null && prefix.length() > 0) {
final BytesRefBuilder prefixBr = new BytesRefBuilder();
prefixBr.copyChars(prefix);
if (te.seekCeil(prefixBr.get()) == TermsEnum.SeekStatus.END) {
startTerm = numTermsInField;
} else {
startTerm = (int) te.ord();
}
prefixBr.append(UnicodeUtil.BIG_TERM);
if (te.seekCeil(prefixBr.get()) == TermsEnum.SeekStatus.END) {
endTerm = numTermsInField;
} else {
endTerm = (int) te.ord();
}
}
/***********
// Alternative 2: get the docSet of the prefix (could take a while) and
// then do the intersection with the baseDocSet first.
if (prefix != null && prefix.length() > 0) {
docs = searcher.getDocSet(new ConstantScorePrefixQuery(new Term(field, ft.toInternal(prefix))), docs);
// The issue with this method are problems of returning 0 counts for terms w/o
// the prefix. We can't just filter out those terms later because it may
// mean that we didn't collect enough terms in the queue (in the sorted case).
}
***********/
boolean doNegative = baseSize > maxDoc >> 1 && termInstances > 0
&& startTerm==0 && endTerm==numTermsInField
&& docs instanceof BitDocSet;
if (doNegative) {
FixedBitSet bs = ((BitDocSet)docs).getBits().clone();
bs.flip(0, maxDoc);
// TODO: when iterator across negative elements is available, use that
// instead of creating a new bitset and inverting.
docs = new BitDocSet(bs, maxDoc - baseSize);
// simply negating will mean that we have deleted docs in the set.
// that should be OK, as their entries in our table should be empty.
//System.out.println(" NEG");
}
// For the biggest terms, do straight set intersections
for (TopTerm tt : bigTerms.values()) {
//System.out.println(" do big termNum=" + tt.termNum + " term=" + tt.term.utf8ToString());
// TODO: counts could be deferred if sorted==false
if (tt.termNum >= startTerm && tt.termNum < endTerm) {
counts[tt.termNum] = searcher.numDocs(new TermQuery(new Term(field, tt.term)), docs);
//System.out.println(" count=" + counts[tt.termNum]);
} else {
//System.out.println("SKIP term=" + tt.termNum);
}
}
// TODO: we could short-circuit counting altogether for sorted faceting
// where we already have enough terms from the bigTerms
// TODO: we could shrink the size of the collection array, and
// additionally break when the termNumber got above endTerm, but
// it would require two extra conditionals in the inner loop (although
// they would be predictable for the non-prefix case).
// Perhaps a different copy of the code would be warranted.
if (termInstances > 0) {
DocIterator iter = docs.iterator();
while (iter.hasNext()) {
int doc = iter.nextDoc();
//System.out.println("iter doc=" + doc);
int code = index[doc];
if ((code & 0xff)==1) {
//System.out.println(" ptr");
int pos = code>>>8;
int whichArray = (doc >>> 16) & 0xff;
byte[] arr = tnums[whichArray];
int tnum = 0;
for(;;) {
int delta = 0;
for(;;) {
byte b = arr[pos++];
delta = (delta << 7) | (b & 0x7f);
if ((b & 0x80) == 0) break;
}
if (delta == 0) break;
tnum += delta - TNUM_OFFSET;
//System.out.println(" tnum=" + tnum);
counts[tnum]++;
}
} else {
//System.out.println(" inlined");
int tnum = 0;
int delta = 0;
for (;;) {
delta = (delta << 7) | (code & 0x7f);
if ((code & 0x80)==0) {
if (delta==0) break;
tnum += delta - TNUM_OFFSET;
//System.out.println(" tnum=" + tnum);
counts[tnum]++;
delta = 0;
}
code >>>= 8;
}
}
}
}
final CharsRefBuilder charsRef = new CharsRefBuilder();
int off=offset;
int lim=limit>=0 ? limit : Integer.MAX_VALUE;
if (sort.equals(FacetParams.FACET_SORT_COUNT) || sort.equals(FacetParams.FACET_SORT_COUNT_LEGACY)) {
int maxsize = limit>0 ? offset+limit : Integer.MAX_VALUE-1;
maxsize = Math.min(maxsize, numTermsInField);
LongPriorityQueue queue = new LongPriorityQueue(Math.min(maxsize,1000), maxsize, Long.MIN_VALUE);
int min=mincount-1; // the smallest value in the top 'N' values
//System.out.println("START=" + startTerm + " END=" + endTerm);
for (int i=startTerm; i<endTerm; i++) {
int c = doNegative ? maxTermCounts[i] - counts[i] : counts[i];
if (c>min) {
// NOTE: we use c>min rather than c>=min as an optimization because we are going in
// index order, so we already know that the keys are ordered. This can be very
// important if a lot of the counts are repeated (like zero counts would be).
// smaller term numbers sort higher, so subtract the term number instead
long pair = (((long)c)<<32) + (Integer.MAX_VALUE - i);
boolean displaced = queue.insert(pair);
if (displaced) min=(int)(queue.top() >>> 32);
}
}
// now select the right page from the results
// if we are deep paging, we don't have to order the highest "offset" counts.
int collectCount = Math.max(0, queue.size() - off);
assert collectCount <= lim;
// the start and end indexes of our list "sorted" (starting with the highest value)
int sortedIdxStart = queue.size() - (collectCount - 1);
int sortedIdxEnd = queue.size() + 1;
final long[] sorted = queue.sort(collectCount);
final int[] indirect = counts; // reuse the counts array for the index into the tnums array
assert indirect.length >= sortedIdxEnd;
for (int i=sortedIdxStart; i<sortedIdxEnd; i++) {
long pair = sorted[i];
int c = (int)(pair >>> 32);
int tnum = Integer.MAX_VALUE - (int)pair;
indirect[i] = i; // store the index for indirect sorting
sorted[i] = tnum; // reuse the "sorted" array to store the term numbers for indirect sorting
// add a null label for now... we'll fill it in later.
res.add(null, c);
}
// now sort the indexes by the term numbers
PrimUtils.sort(sortedIdxStart, sortedIdxEnd, indirect, new PrimUtils.IntComparator() {
@Override
public int compare(int a, int b) {
return (int)sorted[a] - (int)sorted[b];
}
@Override
public boolean lessThan(int a, int b) {
return sorted[a] < sorted[b];
}
@Override
public boolean equals(int a, int b) {
return sorted[a] == sorted[b];
}
});
// convert the term numbers to term values and set
// as the label
//System.out.println("sortStart=" + sortedIdxStart + " end=" + sortedIdxEnd);
for (int i=sortedIdxStart; i<sortedIdxEnd; i++) {
int idx = indirect[i];
int tnum = (int)sorted[idx];
final String label = getReadableValue(getTermValue(te, tnum), ft, charsRef);
//System.out.println(" label=" + label);
res.setName(idx - sortedIdxStart, label);
}
} else {
// add results in index order
int i=startTerm;
if (mincount<=0) {
// if mincount<=0, then we won't discard any terms and we know exactly
// where to start.
i=startTerm+off;
off=0;
}
for (; i<endTerm; i++) {
int c = doNegative ? maxTermCounts[i] - counts[i] : counts[i];
if (c<mincount || --off>=0) continue;
if (--lim<0) break;
final String label = getReadableValue(getTermValue(te, i), ft, charsRef);
res.add(label, c);
}
}
}
if (missing) {
// TODO: a faster solution for this?
res.add(null, SimpleFacets.getFieldMissingCount(searcher, baseDocs, field));
}
//System.out.println(" res=" + res);
return res;
}
/**
* Collect statistics about the UninvertedField. Code is very similar to {@link #getCounts(org.apache.solr.search.SolrIndexSearcher, org.apache.solr.search.DocSet, int, int, Integer, boolean, String, String)}
* It can be used to calculate stats on multivalued fields.
* <p>
* This method is mainly used by the {@link org.apache.solr.handler.component.StatsComponent}.
*
* @param searcher The Searcher to use to gather the statistics
* @param baseDocs The {@link org.apache.solr.search.DocSet} to gather the stats on
* @param statsField the {@link StatsField} param corrisponding to a real {@link SchemaField} to compute stats over
* @param facet One or more fields to facet on.
* @return The {@link org.apache.solr.handler.component.StatsValues} collected
* @throws IOException If there is a low-level I/O error.
*/
public StatsValues getStats(SolrIndexSearcher searcher, DocSet baseDocs, StatsField statsField, String[] facet) throws IOException {
//this function is ripped off nearly wholesale from the getCounts function to use
//for multiValued fields within the StatsComponent. may be useful to find common
//functionality between the two and refactor code somewhat
use.incrementAndGet();
assert null != statsField.getSchemaField()
: "DocValuesStats requires a StatsField using a SchemaField";
SchemaField sf = statsField.getSchemaField();
// FieldType ft = sf.getType();
StatsValues allstats = StatsValuesFactory.createStatsValues(statsField);
DocSet docs = baseDocs;
int baseSize = docs.size();
int maxDoc = searcher.maxDoc();
if (baseSize <= 0) return allstats;
DocSet missing = docs.andNot( searcher.getDocSet(new TermRangeQuery(field, null, null, false, false)) );
int i = 0;
final FieldFacetStats[] finfo = new FieldFacetStats[facet.length];
//Initialize facetstats, if facets have been passed in
SortedDocValues si;
for (String f : facet) {
SchemaField facet_sf = searcher.getSchema().getField(f);
finfo[i] = new FieldFacetStats(searcher, facet_sf, statsField);
i++;
}
final int[] index = this.index;
final int[] counts = new int[numTermsInField];//keep track of the number of times we see each word in the field for all the documents in the docset
TermsEnum te = getOrdTermsEnum(searcher.getLeafReader());
boolean doNegative = false;
if (finfo.length == 0) {
//if we're collecting statistics with a facet field, can't do inverted counting
doNegative = baseSize > maxDoc >> 1 && termInstances > 0
&& docs instanceof BitDocSet;
}
if (doNegative) {
FixedBitSet bs = ((BitDocSet) docs).getBits().clone();
bs.flip(0, maxDoc);
// TODO: when iterator across negative elements is available, use that
// instead of creating a new bitset and inverting.
docs = new BitDocSet(bs, maxDoc - baseSize);
// simply negating will mean that we have deleted docs in the set.
// that should be OK, as their entries in our table should be empty.
}
// For the biggest terms, do straight set intersections
for (TopTerm tt : bigTerms.values()) {
// TODO: counts could be deferred if sorted==false
if (tt.termNum >= 0 && tt.termNum < numTermsInField) {
final Term t = new Term(field, tt.term);
if (finfo.length == 0) {
counts[tt.termNum] = searcher.numDocs(new TermQuery(t), docs);
} else {
//COULD BE VERY SLOW
//if we're collecting stats for facet fields, we need to iterate on all matching documents
DocSet bigTermDocSet = searcher.getDocSet(new TermQuery(t)).intersection(docs);
DocIterator iter = bigTermDocSet.iterator();
while (iter.hasNext()) {
int doc = iter.nextDoc();
counts[tt.termNum]++;
for (FieldFacetStats f : finfo) {
f.facetTermNum(doc, tt.termNum);
}
}
}
}
}
if (termInstances > 0) {
DocIterator iter = docs.iterator();
while (iter.hasNext()) {
int doc = iter.nextDoc();
int code = index[doc];
if ((code & 0xff) == 1) {
int pos = code >>> 8;
int whichArray = (doc >>> 16) & 0xff;
byte[] arr = tnums[whichArray];
int tnum = 0;
for (; ;) {
int delta = 0;
for (; ;) {
byte b = arr[pos++];
delta = (delta << 7) | (b & 0x7f);
if ((b & 0x80) == 0) break;
}
if (delta == 0) break;
tnum += delta - TNUM_OFFSET;
counts[tnum]++;
for (FieldFacetStats f : finfo) {
f.facetTermNum(doc, tnum);
}
}
} else {
int tnum = 0;
int delta = 0;
for (; ;) {
delta = (delta << 7) | (code & 0x7f);
if ((code & 0x80) == 0) {
if (delta == 0) break;
tnum += delta - TNUM_OFFSET;
counts[tnum]++;
for (FieldFacetStats f : finfo) {
f.facetTermNum(doc, tnum);
}
delta = 0;
}
code >>>= 8;
}
}
}
}
// add results in index order
for (i = 0; i < numTermsInField; i++) {
int c = doNegative ? maxTermCounts[i] - counts[i] : counts[i];
if (c == 0) continue;
BytesRef value = getTermValue(te, i);
allstats.accumulate(value, c);
//as we've parsed the termnum into a value, lets also accumulate fieldfacet statistics
for (FieldFacetStats f : finfo) {
f.accumulateTermNum(i, value);
}
}
int c = missing.size();
allstats.addMissing(c);
if (finfo.length > 0) {
for (FieldFacetStats f : finfo) {
Map<String, StatsValues> facetStatsValues = f.facetStatsValues;
FieldType facetType = searcher.getSchema().getFieldType(f.name);
for (Map.Entry<String,StatsValues> entry : facetStatsValues.entrySet()) {
String termLabel = entry.getKey();
int missingCount = searcher.numDocs(new TermQuery(new Term(f.name, facetType.toInternal(termLabel))), missing);
entry.getValue().addMissing(missingCount);
}
allstats.addFacet(f.name, facetStatsValues);
}
}
return allstats;
}
String getReadableValue(BytesRef termval, FieldType ft, CharsRefBuilder charsRef) {
return ft.indexedToReadable(termval, charsRef).toString();
}

View File

@ -21,13 +21,12 @@ import java.io.IOException;
import java.io.Writer;
import java.util.*;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexableField;
import org.apache.solr.client.solrj.io.TupleStream;
import org.apache.solr.client.solrj.io.Tuple;
import org.apache.lucene.index.StorableField;
import org.apache.lucene.index.StoredDocument;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.common.EnumFieldValue;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.util.Base64;
@ -202,6 +201,8 @@ public abstract class TextResponseWriter {
} else if (val instanceof BytesRef) {
BytesRef arr = (BytesRef)val;
writeByteArr(name, arr.bytes, arr.offset, arr.length);
} else if (val instanceof EnumFieldValue) {
writeStr(name, val.toString(), true);
} else {
// default... for debugging only
writeStr(name, val.getClass().getName() + ':' + val.toString(), true);

View File

@ -410,6 +410,7 @@
<tokenizer class="solr.WhitespaceTokenizerFactory" />
</analyzer>
</fieldType>
<fieldType name="severityType" class="solr.EnumField" enumsConfig="enumsConfig.xml" enumName="severity"/>
</types>
@ -548,6 +549,10 @@
<field name="payloadDelimited" type="payloadDelimited" />
<!-- EnumType -->
<field name="severity" type="severityType" indexed="true" stored="true" multiValued="false"/>
<!-- Dynamic field definitions. If a field name is not found, dynamicFields
will be used if the name matches any of the patterns.
RESTRICTION: the glob-like pattern in the name attribute must have
@ -620,6 +625,7 @@
<dynamicField name="*_path" type="path" indexed="true" stored="true" omitNorms="true" multiValued="true" />
<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" />
</fields>
<defaultSearchField>text</defaultSearchField>

View File

@ -283,6 +283,9 @@ valued. -->
</fieldType>
<!-- EnumType -->
<fieldType name="severityType" class="solr.EnumField" enumsConfig="enumsConfig.xml" enumName="severity"/>
<!-- Valid attributes for fields:
name: mandatory - the name for the field
type: mandatory - the name of a previously defined type from the <types> section
@ -322,6 +325,10 @@ valued. -->
<field name="cat_floatDocValues" type="float" indexed="true" stored="true" docValues="true" multiValued="true" />
<field name="cat_length" type="text_length" indexed="true" stored="true" multiValued="true"/>
<!-- EnumType -->
<field name="severity" type="severityType" indexed="true" stored="true" multiValued="false"/>
<!-- Dynamic field definitions. If a field name is not found, dynamicFields
will be used if the name matches any of the patterns.
RESTRICTION: the glob-like pattern in the name attribute must have

View File

@ -19,26 +19,41 @@ package org.apache.solr;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.util.LuceneTestCase.Slow;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.RangeFacet;
import org.apache.solr.client.solrj.response.FieldStatsInfo;
import org.apache.solr.cloud.ChaosMonkey;
import org.apache.solr.common.EnumFieldValue;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
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.util.NamedList;
import org.apache.solr.handler.component.ShardResponse;
import org.apache.solr.handler.component.ShardRequest;
import org.apache.solr.handler.component.StatsField.Stat;
import org.apache.solr.handler.component.TrackingShardHandlerFactory;
import org.apache.solr.handler.component.TrackingShardHandlerFactory.ShardRequestAndParams;
import org.apache.solr.handler.component.TrackingShardHandlerFactory.RequestTrackingQueue;
import org.apache.solr.handler.component.StatsComponentTest.StatSetCombinations;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.EnumSet;
/**
* TODO? perhaps use:
@ -63,18 +78,25 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
String missingField="ignore_exception__missing_but_valid_field_t";
String invalidField="ignore_exception__invalid_field_not_in_schema";
@Override
protected String getSolrXml() {
return "solr-trackingshardhandler.xml";
}
@Test
public void test() throws Exception {
QueryResponse rsp = null;
int backupStress = stress; // make a copy so we can restore
del("*:*");
indexr(id,1, i1, 100, tlong, 100,t1,"now is the time for all good men",
"foo_sev_enum", "Medium",
tdate_a, "2010-04-20T11:00:00Z",
tdate_b, "2009-08-20T11:00:00Z",
"foo_f", 1.414f, "foo_b", "true", "foo_d", 1.414d);
indexr(id,2, i1, 50 , tlong, 50,t1,"to come to the aid of their country.",
"foo_sev_enum", "Medium",
"foo_sev_enum", "High",
tdate_a, "2010-05-02T11:00:00Z",
tdate_b, "2009-11-02T11:00:00Z");
indexr(id,3, i1, 2, tlong, 2,t1,"how now brown cow",
@ -90,6 +112,7 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
indexr(id,7, i1, 123, tlong, 123 ,t1,"humpty dumpy had a great fall");
indexr(id,8, i1, 876, tlong, 876,
tdate_b, "2010-01-05T11:00:00Z",
"foo_sev_enum", "High",
t1,"all the kings horses and all the kings men");
indexr(id,9, i1, 7, tlong, 7,t1,"couldn't put humpty together again");
@ -97,6 +120,7 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
indexr(id,10, i1, 4321, tlong, 4321,t1,"this too shall pass");
indexr(id,11, i1, -987, tlong, 987,
"foo_sev_enum", "Medium",
t1,"An eye for eye only ends up making the whole world blind.");
indexr(id,12, i1, 379, tlong, 379,
t1,"Great works are performed, not by strength, but by perseverance.");
@ -363,7 +387,7 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
query("q","*:*", "rows",100, "facet","true", "facet.field",missingField, "facet.mincount",2);
// test field that is valid in schema and missing in some shards
query("q","*:*", "rows",100, "facet","true", "facet.field",oddField, "facet.mincount",2);
query("q","*:*", "sort",i1+" desc", "stats", "true", "stats.field", "stats_dt");
query("q","*:*", "sort",i1+" desc", "stats", "true", "stats.field", i1);
query("q","*:*", "sort",i1+" desc", "stats", "true", "stats.field", tdate_a);
@ -392,6 +416,283 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
"stats.field", i1,
"stats.field", tdate_a,
"stats.field", tdate_b);
// only ask for "min" and "mean", explicitly exclude deps of mean, whitebox check shard responses
try {
RequestTrackingQueue trackingQueue = new RequestTrackingQueue();
TrackingShardHandlerFactory.setTrackingQueue(jettys, trackingQueue);
rsp = query("q","*:*", "sort",i1+" desc", "stats", "true",
"stats.field", "{!min=true sum=false mean=true count=false}" + i1);
FieldStatsInfo s = rsp.getFieldStatsInfo().get(i1);
assertNotNull("no stats for " + i1, s);
//
assertEquals("wrong min", -987.0D, (Double)s.getMin(), 0.0001D );
assertEquals("wrong mean", 377.153846D, (Double)s.getMean(), 0.0001D );
//
assertNull("expected null for count", s.getCount());
assertNull("expected null for calcDistinct", s.getCountDistinct());
assertNull("expected null for distinct vals", s.getDistinctValues());
assertNull("expected null for max", s.getMax());
assertNull("expected null for missing", s.getMissing());
assertNull("expected null for stddev", s.getStddev());
assertNull("expected null for sum", s.getSum());
// sanity check deps relationship
for (Stat dep : EnumSet.of(Stat.sum, Stat.count)) {
assertTrue("Purpose of this test is to ensure that asking for some stats works even when the deps " +
"of those stats are explicitly excluded -- but the expected dep relationshp is no longer valid. " +
"ie: who changed the code and didn't change this test?, expected: " + dep,
Stat.mean.getDistribDeps().contains(dep));
}
// check our shard requests & responses - ensure we didn't get unneccessary stats from every shard
int numStatsShardRequests = 0;
EnumSet<Stat> shardStatsExpected = EnumSet.of(Stat.min, Stat.sum, Stat.count);
for (List<ShardRequestAndParams> shard : trackingQueue.getAllRequests().values()) {
for (ShardRequestAndParams shardReq : shard) {
if (shardReq.params.getBool(StatsParams.STATS, false)) {
numStatsShardRequests++;
for (ShardResponse shardRsp : shardReq.sreq.responses) {
NamedList<Object> shardStats =
((NamedList<NamedList<NamedList<Object>>>)
shardRsp.getSolrResponse().getResponse().get("stats")).get("stats_fields").get(i1);
assertNotNull("no stard stats for " + i1, shardStats);
//
for (Map.Entry<String,Object> entry : shardStats) {
Stat found = Stat.forName(entry.getKey());
assertNotNull("found shardRsp stat key we were not expecting: " + entry, found);
assertTrue("found stat we were not expecting: " + entry, shardStatsExpected.contains(found));
}
}
}
}
}
assertTrue("did't see any stats=true shard requests", 0 < numStatsShardRequests);
} finally {
TrackingShardHandlerFactory.setTrackingQueue(jettys, null);
}
// only ask for "min", "mean" and "stddev",
rsp = query("q","*:*", "sort",i1+" desc", "stats", "true",
"stats.field", "{!min=true mean=true stddev=true}" + i1);
{ // don't leak variables
FieldStatsInfo s = rsp.getFieldStatsInfo().get(i1);
assertNotNull("no stats for " + i1, s);
//
assertEquals("wrong min", -987.0D, (Double)s.getMin(), 0.0001D );
assertEquals("wrong mean", 377.153846D, (Double)s.getMean(), 0.0001D );
assertEquals("wrong stddev", 1271.76215D, (Double)s.getStddev(), 0.0001D );
//
assertNull("expected null for count", s.getCount());
assertNull("expected null for calcDistinct", s.getCountDistinct());
assertNull("expected null for distinct vals", s.getDistinctValues());
assertNull("expected null for max", s.getMax());
assertNull("expected null for missing", s.getMissing());
assertNull("expected null for sum", s.getSum());
}
final String[] stats = new String[] {
"min", "max", "sum", "sumOfSquares", "stddev", "mean", "missing", "count"
};
// ask for arbitrary pairs of stats
for (String stat1 : stats) {
for (String stat2 : stats) {
// NOTE: stat1 might equal stat2 - good edge case to test for
rsp = query("q","*:*", "sort",i1+" desc", "stats", "true",
"stats.field", "{!" + stat1 + "=true " + stat2 + "=true}" + i1);
final List<String> statsExpected = new ArrayList<String>(2);
statsExpected.add(stat1);
if ( ! stat1.equals(stat2) ) {
statsExpected.add(stat2);
}
// ignore the FieldStatsInfo convinience class, and look directly at the NamedList
// so we don't need any sort of crazy reflection
NamedList<Object> svals =
((NamedList<NamedList<NamedList<Object>>>)
rsp.getResponse().get("stats")).get("stats_fields").get(i1);
assertNotNull("no stats for field " + i1, svals);
assertEquals("wrong quantity of stats", statsExpected.size(), svals.size());
for (String s : statsExpected) {
assertNotNull("stat shouldn't be null: " + s, svals.get(s));
assertTrue("stat should be a Number: " + s + " -> " + svals.get(s).getClass(),
svals.get(s) instanceof Number);
// some loose assertions since we're iterating over various stats
if (svals.get(s) instanceof Double) {
Double val = (Double) svals.get(s);
assertFalse("stat shouldn't be NaN: " + s, val.isNaN());
assertFalse("stat shouldn't be Inf: " + s, val.isInfinite());
assertFalse("stat shouldn't be 0: " + s, val.equals(0.0D));
} else {
// count or missing
assertTrue("stat should be count of missing: " + s,
("count".equals(s) || "missing".equals(s)));
assertTrue("stat should be a Long: " + s + " -> " + svals.get(s).getClass(),
svals.get(s) instanceof Long);
Long val = (Long) svals.get(s);
assertFalse("stat shouldn't be 0: " + s, val.equals(0L));
}
}
}
}
// all of these diff ways of asking for min & calcdistinct should have the same result
for (SolrParams p : new SolrParams[] {
params("stats.field", "{!min=true calcdistinct=true}" + i1),
params("stats.calcdistinct", "true",
"stats.field", "{!min=true}" + i1),
params("f."+i1+".stats.calcdistinct", "true",
"stats.field", "{!min=true}" + i1),
params("stats.calcdistinct", "false",
"f."+i1+".stats.calcdistinct", "true",
"stats.field", "{!min=true}" + i1),
params("stats.calcdistinct", "false",
"f."+i1+".stats.calcdistinct", "false",
"stats.field", "{!min=true calcdistinct=true}" + i1),
}) {
rsp = query(SolrParams.wrapDefaults
(p, params("q","*:*", "sort",i1+" desc", "stats", "true")));
FieldStatsInfo s = rsp.getFieldStatsInfo().get(i1);
assertNotNull(p+" no stats for " + i1, s);
//
assertEquals(p+" wrong min", -987.0D, (Double)s.getMin(), 0.0001D );
assertEquals(p+" wrong calcDistinct", new Long(13), s.getCountDistinct());
assertNotNull(p+" expected non-null list for distinct vals", s.getDistinctValues());
assertEquals(p+" expected list for distinct vals", 13, s.getDistinctValues().size());
//
assertNull(p+" expected null for mean", s.getMean() );
assertNull(p+" expected null for count", s.getCount());
assertNull(p+" expected null for max", s.getMax());
assertNull(p+" expected null for missing", s.getMissing());
assertNull(p+" expected null for stddev", s.getStddev());
assertNull(p+" expected null for sum", s.getSum());
}
// all of these diff ways of excluding calcdistinct should have the same result
for (SolrParams p : new SolrParams[] {
params("stats.field", "{!min=true calcdistinct=false}" + i1),
params("stats.calcdistinct", "false",
"stats.field", "{!min=true}" + i1),
params("f."+i1+".stats.calcdistinct", "false",
"stats.field", "{!min=true}" + i1),
params("stats.calcdistinct", "true",
"f."+i1+".stats.calcdistinct", "false",
"stats.field", "{!min=true}" + i1),
params("stats.calcdistinct", "true",
"f."+i1+".stats.calcdistinct", "true",
"stats.field", "{!min=true calcdistinct=false}" + i1),
}) {
rsp = query(SolrParams.wrapDefaults
(p, params("q","*:*", "sort",i1+" desc", "stats", "true")));
FieldStatsInfo s = rsp.getFieldStatsInfo().get(i1);
assertNotNull(p+" no stats for " + i1, s);
//
assertEquals(p+" wrong min", -987.0D, (Double)s.getMin(), 0.0001D );
//
assertNull(p+" expected null for calcDistinct", s.getCountDistinct());
assertNull(p+" expected null for distinct vals", s.getDistinctValues());
//
assertNull(p+" expected null for mean", s.getMean() );
assertNull(p+" expected null for count", s.getCount());
assertNull(p+" expected null for max", s.getMax());
assertNull(p+" expected null for missing", s.getMissing());
assertNull(p+" expected null for stddev", s.getStddev());
assertNull(p+" expected null for sum", s.getSum());
}
// this field doesn't exist in any doc in the result set.
// ensure we get expected values for the stats we ask for, but null for the stats
rsp = query("q","*:*", "sort",i1+" desc", "stats", "true",
"stats.field", "{!min=true mean=true stddev=true}does_not_exist_i");
{ // don't leak variables
FieldStatsInfo s = rsp.getFieldStatsInfo().get("does_not_exist_i");
assertNotNull("no stats for bogus field", s);
// things we explicit expect because we asked for them
// NOTE: min is expected to be null even though requested because of no values
assertEquals("wrong min", null, s.getMin());
assertTrue("mean should be NaN", ((Double)s.getMean()).isNaN());
assertEquals("wrong stddev", 0.0D, (Double)s.getStddev(), 0.0D );
// things that we didn't ask for, so they better be null
assertNull("expected null for count", s.getCount());
assertNull("expected null for calcDistinct", s.getCountDistinct());
assertNull("expected null for distinct vals", s.getDistinctValues());
assertNull("expected null for max", s.getMax());
assertNull("expected null for missing", s.getMissing());
assertNull("expected null for sum", s.getSum());
}
// look at stats on non numeric fields
//
// not all stats are supported on every field type, so some of these permutations will
// result in no stats being computed but this at least lets us sanity check that for each
// of these field+stats(s) combinations we get consistent results between the distribted
// request and the single node situation.
EnumSet<Stat> allStats = EnumSet.allOf(Stat.class);
int numTotalStatQueries = 0;
// don't go overboard, just do all permutations of 1 or 2 stat params, for each field & query
final int numStatParamsAtOnce = 2;
for (int numParams = 1; numParams <= numStatParamsAtOnce; numParams++) {
for (EnumSet<Stat> set : new StatSetCombinations(numParams, allStats)) {
for (String field : new String[] {
"foo_f", i1, tlong, tdate_a, oddField, "foo_sev_enum",
// fields that no doc has any value in
"bogus___s", "bogus___f", "bogus___i", "bogus___tdt", "bogus___sev_enum"
}) {
for ( String q : new String[] {
"*:*", // all docs
"bogus___s:bogus", // no docs
"id:" + random().nextInt(50 ), // 0 or 1 doc...
"id:" + random().nextInt(50 ),
"id:" + random().nextInt(100),
"id:" + random().nextInt(100),
"id:" + random().nextInt(200)
}) {
// EnumSets use natural ordering, we want to randomize the order of the params
List<Stat> combo = new ArrayList<Stat>(set);
Collections.shuffle(combo, random());
StringBuilder paras = new StringBuilder("{!key=k ");
for (Stat stat : combo) {
paras.append(stat + "=true ");
}
paras.append("}").append(field);
numTotalStatQueries++;
rsp = query("q","*:*", "rows", "0", "stats", "true",
"stats.field", paras.toString());
// simple assert, mostly relying on comparison with single shard
FieldStatsInfo s = rsp.getFieldStatsInfo().get("k");
assertNotNull(s);
// TODO: if we had a programatic way to determine what stats are supported
// by what field types, we could make more confident asserts here.
}
}
}
}
assertEquals("Sanity check failed: either test broke, or test changed, or you adjusted Stat enum" +
" (adjust constant accordingly if intentional)",
3465, numTotalStatQueries);
/*** TODO: the failure may come back in "exception"
try {
@ -567,6 +868,25 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
throw e;
}
}
String fieldName = "severity";
indexr("id", "1", fieldName, "Not Available");
indexr("id", "2", fieldName, "Low");
indexr("id", "3", fieldName, "Medium");
indexr("id", "4", fieldName, "High");
indexr("id", "5", fieldName, "Critical");
commit();
rsp = query("q", "*:*", "stats", "true", "stats.field", fieldName);
assertEquals(new EnumFieldValue(0, "Not Available"),
rsp.getFieldStatsInfo().get(fieldName).getMin());
query("q", "*:*", "stats", "true", "stats.field", fieldName,
StatsParams.STATS_CALC_DISTINCT, "true");
assertEquals(new EnumFieldValue(4, "Critical"),
rsp.getFieldStatsInfo().get(fieldName).getMax());
query("q", "*:*", "stats", "true", "stats.field", fieldName,
"stats.facet", fieldName);
}
protected void checkMinCountsField(List<FacetField.Count> counts, Object[] pairs) {

View File

@ -139,7 +139,8 @@ public class CollectionsAPIDistributedZkTest extends AbstractFullDistribZkTestBa
// for now, always upload the config and schema to the canonical names
AbstractZkTestCase.putConfig("conf2", zkClient, solrhome, getCloudSolrConfig(), "solrconfig.xml");
AbstractZkTestCase.putConfig("conf2", zkClient, solrhome, "schema.xml", "schema.xml");
AbstractZkTestCase.putConfig("conf2", zkClient, solrhome, "enumsConfig.xml", "enumsConfig.xml");
AbstractZkTestCase.putConfig("conf2", zkClient, solrhome, "solrconfig.snippet.randomindexconfig.xml");
AbstractZkTestCase.putConfig("conf2", zkClient, solrhome, "stopwords.txt");
AbstractZkTestCase.putConfig("conf2", zkClient, solrhome, "protwords.txt");

View File

@ -89,19 +89,23 @@ public class DistributedFacetPivotSmallAdvancedTest extends BaseDistributedSearc
handle.put("maxScore", SKIPVAL);
doTestDeepPivotStatsOnString();
doTestTopStatsWithRefinement();
doTestTopStatsWithRefinement(true);
doTestTopStatsWithRefinement(false);
}
/**
* we need to ensure that stats never "overcount" the values from a single shard
* even if we hit that shard with a refinement request
*/
private void doTestTopStatsWithRefinement() throws Exception {
private void doTestTopStatsWithRefinement(final boolean allStats) throws Exception {
String stat_param = allStats ?
"{!tag=s1}foo_i" : "{!tag=s1 min=true max=true count=true missing=true}foo_i";
ModifiableSolrParams coreParams = params("q", "*:*", "rows", "0",
"stats", "true",
"stats.field", "{!tag=s1}foo_i" );
"stats.field", stat_param );
ModifiableSolrParams facetParams = new ModifiableSolrParams(coreParams);
facetParams.add(params("facet", "true",
"facet.limit", "1",
@ -128,10 +132,18 @@ public class DistributedFacetPivotSmallAdvancedTest extends BaseDistributedSearc
assertEquals(msg, 91.0, fieldStatsInfo.getMax());
assertEquals(msg, 10, (long) fieldStatsInfo.getCount());
assertEquals(msg, 0, (long) fieldStatsInfo.getMissing());
assertEquals(msg, 248.0, fieldStatsInfo.getSum());
assertEquals(msg, 15294.0, fieldStatsInfo.getSumOfSquares(), 0.1E-7);
assertEquals(msg, 24.8, (double) fieldStatsInfo.getMean(), 0.1E-7);
assertEquals(msg, 31.87405772027709, fieldStatsInfo.getStddev(), 0.1E-7);
if (allStats) {
assertEquals(msg, 248.0, fieldStatsInfo.getSum());
assertEquals(msg, 15294.0, fieldStatsInfo.getSumOfSquares(), 0.1E-7);
assertEquals(msg, 24.8, (double) fieldStatsInfo.getMean(), 0.1E-7);
assertEquals(msg, 31.87405772027709, fieldStatsInfo.getStddev(), 0.1E-7);
} else {
assertNull(msg, fieldStatsInfo.getSum());
assertNull(msg, fieldStatsInfo.getSumOfSquares());
assertNull(msg, fieldStatsInfo.getMean());
assertNull(msg, fieldStatsInfo.getStddev());
}
if (params.getBool("facet", false)) {
// if this was a facet request, then the top pivot constraint and pivot
@ -156,6 +168,12 @@ public class DistributedFacetPivotSmallAdvancedTest extends BaseDistributedSearc
assertEquals(4, (long) dublinMicrosoftStatsInfo.getCount());
assertEquals(0, (long) dublinMicrosoftStatsInfo.getMissing());
if (! allStats) {
assertNull(msg, dublinMicrosoftStatsInfo.getSum());
assertNull(msg, dublinMicrosoftStatsInfo.getSumOfSquares());
assertNull(msg, dublinMicrosoftStatsInfo.getMean());
assertNull(msg, dublinMicrosoftStatsInfo.getStddev());
}
}
}

View File

@ -334,16 +334,21 @@ public class DistributedFacetPivotSmallTest extends BaseDistributedSearchTestCas
}
}
doTestDeepPivotStats();
doTestDeepPivotStats(false); // all price stats
doTestDeepPivotStats(true); // just the mean price stat
doTestPivotStatsFromOneShard();
}
private void doTestDeepPivotStats() throws Exception {
/**
* @param justMean - only the mean stat is requested/computed
*/
private void doTestDeepPivotStats(boolean justMean) throws Exception {
SolrParams params = params("q", "*:*", "rows", "0",
"facet", "true", "stats", "true",
"facet.pivot", "{!stats=s1}place_t,company_t",
"stats.field", "{!key=avg_price tag=s1}price_ti");
"stats.field", ("{!key=avg_price tag=s1 "+
(justMean ? "mean=true" : "") +"}price_ti"));
QueryResponse rsp = query(params);
List<PivotField> placePivots = rsp.getFacetPivot().get("place_t,company_t");
@ -357,15 +362,24 @@ public class DistributedFacetPivotSmallTest extends BaseDistributedSearchTestCas
assertEquals(4, microsoftPivotField.getCount());
FieldStatsInfo dublinMicrosoftStatsInfo = microsoftPivotField.getFieldStatsInfo().get("avg_price");
assertEquals(15.0, dublinMicrosoftStatsInfo.getMin());
assertEquals(29.0, dublinMicrosoftStatsInfo.getMax());
assertEquals(3, (long) dublinMicrosoftStatsInfo.getCount());
assertEquals(1, (long) dublinMicrosoftStatsInfo.getMissing());
assertEquals(63.0, dublinMicrosoftStatsInfo.getSum());
assertEquals(1427.0, dublinMicrosoftStatsInfo.getSumOfSquares(), 0.1E-7);
assertEquals(21.0, (double) dublinMicrosoftStatsInfo.getMean(), 0.1E-7);
assertEquals(7.211102550927978, dublinMicrosoftStatsInfo.getStddev(), 0.1E-7);
if (justMean) {
assertNull(dublinMicrosoftStatsInfo.getMin());
assertNull(dublinMicrosoftStatsInfo.getMax());
assertNull(dublinMicrosoftStatsInfo.getCount());
assertNull(dublinMicrosoftStatsInfo.getMissing());
assertNull(dublinMicrosoftStatsInfo.getSum());
assertNull(dublinMicrosoftStatsInfo.getSumOfSquares());
assertNull(dublinMicrosoftStatsInfo.getStddev());
} else {
assertEquals(15.0, dublinMicrosoftStatsInfo.getMin());
assertEquals(29.0, dublinMicrosoftStatsInfo.getMax());
assertEquals(3, (long) dublinMicrosoftStatsInfo.getCount());
assertEquals(1, (long) dublinMicrosoftStatsInfo.getMissing());
assertEquals(63.0, dublinMicrosoftStatsInfo.getSum());
assertEquals(1427.0, dublinMicrosoftStatsInfo.getSumOfSquares(), 0.1E-7);
assertEquals(7.211102550927978, dublinMicrosoftStatsInfo.getStddev(), 0.1E-7);
}
PivotField cardiffPivotField = placePivots.get(2);
assertEquals("cardiff", cardiffPivotField.getValue());
@ -376,15 +390,24 @@ public class DistributedFacetPivotSmallTest extends BaseDistributedSearchTestCas
assertEquals(3, polecatPivotField.getCount());
FieldStatsInfo cardiffPolecatStatsInfo = polecatPivotField.getFieldStatsInfo().get("avg_price");
assertEquals(15.0, cardiffPolecatStatsInfo.getMin());
assertEquals(39.0, cardiffPolecatStatsInfo.getMax());
assertEquals(2, (long) cardiffPolecatStatsInfo.getCount());
assertEquals(1, (long) cardiffPolecatStatsInfo.getMissing());
assertEquals(54.0, cardiffPolecatStatsInfo.getSum());
assertEquals(1746.0, cardiffPolecatStatsInfo.getSumOfSquares(), 0.1E-7);
assertEquals(27.0, (double) cardiffPolecatStatsInfo.getMean(), 0.1E-7);
assertEquals(16.97056274847714, cardiffPolecatStatsInfo.getStddev(), 0.1E-7);
if (justMean) {
assertNull(cardiffPolecatStatsInfo.getMin());
assertNull(cardiffPolecatStatsInfo.getMax());
assertNull(cardiffPolecatStatsInfo.getCount());
assertNull(cardiffPolecatStatsInfo.getMissing());
assertNull(cardiffPolecatStatsInfo.getSum());
assertNull(cardiffPolecatStatsInfo.getSumOfSquares());
assertNull(cardiffPolecatStatsInfo.getStddev());
} else {
assertEquals(15.0, cardiffPolecatStatsInfo.getMin());
assertEquals(39.0, cardiffPolecatStatsInfo.getMax());
assertEquals(2, (long) cardiffPolecatStatsInfo.getCount());
assertEquals(1, (long) cardiffPolecatStatsInfo.getMissing());
assertEquals(54.0, cardiffPolecatStatsInfo.getSum());
assertEquals(1746.0, cardiffPolecatStatsInfo.getSumOfSquares(), 0.1E-7);
assertEquals(16.97056274847714, cardiffPolecatStatsInfo.getStddev(), 0.1E-7);
}
PivotField krakowPivotField = placePivots.get(3);
assertEquals("krakow", krakowPivotField.getValue());
@ -395,14 +418,25 @@ public class DistributedFacetPivotSmallTest extends BaseDistributedSearchTestCas
assertEquals(1, fujitsuPivotField.getCount());
FieldStatsInfo krakowFujitsuStatsInfo = fujitsuPivotField.getFieldStatsInfo().get("avg_price");
assertEquals(null, krakowFujitsuStatsInfo.getMin());
assertEquals(null, krakowFujitsuStatsInfo.getMax());
assertEquals(0, (long) krakowFujitsuStatsInfo.getCount());
assertEquals(1, (long) krakowFujitsuStatsInfo.getMissing());
assertEquals(0.0, krakowFujitsuStatsInfo.getSum());
assertEquals(0.0, krakowFujitsuStatsInfo.getSumOfSquares(), 0.1E-7);
assertEquals(Double.NaN, (double) krakowFujitsuStatsInfo.getMean(), 0.1E-7);
assertEquals(0.0, krakowFujitsuStatsInfo.getStddev(), 0.1E-7);
if (justMean) {
assertNull(krakowFujitsuStatsInfo.getMin());
assertNull(krakowFujitsuStatsInfo.getMax());
assertNull(krakowFujitsuStatsInfo.getCount());
assertNull(krakowFujitsuStatsInfo.getMissing());
assertNull(krakowFujitsuStatsInfo.getSum());
assertNull(krakowFujitsuStatsInfo.getSumOfSquares());
assertNull(krakowFujitsuStatsInfo.getStddev());
} else {
assertEquals(null, krakowFujitsuStatsInfo.getMin());
assertEquals(null, krakowFujitsuStatsInfo.getMax());
assertEquals(0, (long) krakowFujitsuStatsInfo.getCount());
assertEquals(1, (long) krakowFujitsuStatsInfo.getMissing());
assertEquals(0.0, krakowFujitsuStatsInfo.getSum());
assertEquals(0.0, krakowFujitsuStatsInfo.getSumOfSquares(), 0.1E-7);
assertEquals(Double.NaN, (double) krakowFujitsuStatsInfo.getMean(), 0.1E-7);
assertEquals(0.0, krakowFujitsuStatsInfo.getStddev(), 0.1E-7);
}
}
// Useful to check for errors, orders lists and does toString() equality check

View File

@ -158,7 +158,7 @@ public class FacetPivotSmallTest extends SolrTestCaseJ4 {
params.add("facet", "true");
params.add("facet.pivot", "{!stats=s1}place_t,company_t");
params.add("stats", "true");
params.add("stats.field", "{!key=avg_price tag=s1 mean=true}price_ti");
params.add("stats.field", "{!key=avg_price tag=s1}price_ti");
SolrQueryRequest req = req(params);
final String statsPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='place_t,company_t']/lst";
@ -174,6 +174,8 @@ public class FacetPivotSmallTest extends SolrTestCaseJ4 {
dublinMicrosoftStats + "/double[@name='sumOfSquares'][.=1427.0]",
dublinMicrosoftStats + "/double[@name='mean'][.=21.0]",
dublinMicrosoftStats + "/double[@name='stddev'][.=7.211102550927978]",
// if new stats are supported, this will break - update test to assert values for each
"count(" + dublinMicrosoftStats + "/*)=8",
cardiffPolecatStats + "/double[@name='min'][.=15.0]",
cardiffPolecatStats + "/double[@name='max'][.=39.0]",
@ -183,6 +185,8 @@ public class FacetPivotSmallTest extends SolrTestCaseJ4 {
cardiffPolecatStats + "/double[@name='sumOfSquares'][.=1746.0]",
cardiffPolecatStats + "/double[@name='mean'][.=27.0]",
cardiffPolecatStats + "/double[@name='stddev'][.=16.97056274847714]",
// if new stats are supported, this will break - update test to assert values for each
"count(" + cardiffPolecatStats + "/*)=8",
krakowFujitsuStats + "/null[@name='min']",
krakowFujitsuStats + "/null[@name='max']",
@ -191,7 +195,10 @@ public class FacetPivotSmallTest extends SolrTestCaseJ4 {
krakowFujitsuStats + "/double[@name='sum'][.=0.0]",
krakowFujitsuStats + "/double[@name='sumOfSquares'][.=0.0]",
krakowFujitsuStats + "/double[@name='mean'][.='NaN']",
krakowFujitsuStats + "/double[@name='stddev'][.=0.0]"
krakowFujitsuStats + "/double[@name='stddev'][.=0.0]",
// if new stats are supported, this will break - update test to assert values for each
"count(" + krakowFujitsuStats + "/*)=8"
);
}

View File

@ -21,6 +21,8 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -29,19 +31,21 @@ import java.util.TimeZone;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.queries.function.valuesource.QueryValueSource;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.StatsParams;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.component.StatsField.Stat;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.util.AbstractSolrTestCase;
import org.apache.commons.math3.util.Combinations;
import org.junit.BeforeClass;
@ -647,8 +651,20 @@ public class StatsComponentTest extends AbstractSolrTestCase {
args.put("indent", "true");
SolrQueryRequest req = new LocalSolrQueryRequest(core, new MapSolrParams(args));
assertQ("test string statistics values", req,
"//null[@name='active_i'][.='']");
assertQ("test string statistics values", req
,"//lst[@name='active_i']/long[@name='count'][.='0']"
,"//lst[@name='active_i']/long[@name='missing'][.='4']"
,"//lst[@name='active_i']/null[@name='min']"
,"//lst[@name='active_i']/null[@name='max']"
,"//lst[@name='active_i']/double[@name='sum'][.='0.0']"
,"//lst[@name='active_i']/double[@name='sumOfSquares'][.='0.0']"
,"//lst[@name='active_i']/double[@name='stddev'][.='0.0']"
,"//lst[@name='active_i']/double[@name='mean'][.='NaN']"
// if new stats are supported, this will break - update test to assert values for each
,"count(//lst[@name='active_i']/*)=8"
);
}
public void testFieldStatisticsResultsStringFieldAlwaysMissing() throws Exception {
@ -667,8 +683,15 @@ public class StatsComponentTest extends AbstractSolrTestCase {
args.put("indent", "true");
SolrQueryRequest req = new LocalSolrQueryRequest(core, new MapSolrParams(args));
assertQ("test string statistics values", req,
"//null[@name='active_s'][.='']");
assertQ("test string statistics values", req
,"//lst[@name='active_s']/long[@name='count'][.='0']"
,"//lst[@name='active_s']/long[@name='missing'][.='4']"
,"//lst[@name='active_s']/null[@name='min']"
,"//lst[@name='active_s']/null[@name='max']"
// if new stats are supported, this will break - update test to assert values for each
,"count(//lst[@name='active_s']/*)=4"
);
}
//SOLR-3160
@ -688,8 +711,20 @@ public class StatsComponentTest extends AbstractSolrTestCase {
args.put("indent", "true");
SolrQueryRequest req = new LocalSolrQueryRequest(core, new MapSolrParams(args));
assertQ("test string statistics values", req,
"//null[@name='active_dt'][.='']");
assertQ("test string statistics values", req
,"//lst[@name='active_dt']/long[@name='count'][.='0']"
,"//lst[@name='active_dt']/long[@name='missing'][.='3']"
,"//lst[@name='active_dt']/null[@name='min']"
,"//lst[@name='active_dt']/null[@name='max']"
,"//lst[@name='active_dt']/null[@name='mean']"
,"//lst[@name='active_dt']/date[@name='sum'][.='1970-01-01T00:00:00Z']"
,"//lst[@name='active_dt']/double[@name='sumOfSquares'][.='0.0']"
,"//lst[@name='active_dt']/double[@name='stddev'][.='0.0']"
// if new stats are supported, this will break - update test to assert values for each
,"count(//lst[@name='active_dt']/*)=8"
);
}
public void testStatsFacetMultivaluedErrorHandling() throws Exception {
@ -813,8 +848,8 @@ public class StatsComponentTest extends AbstractSolrTestCase {
args.put(CommonParams.Q, "*:*");
args.put(StatsParams.STATS, "true");
args.put(StatsParams.STATS_FIELD, fieldName);
args.put("indent", "true");
args.put(StatsParams.STATS_CALC_DISTINCT, "true");
args.put("indent", "true");
SolrQueryRequest req = new LocalSolrQueryRequest(core, new MapSolrParams(args));
@ -860,8 +895,8 @@ public class StatsComponentTest extends AbstractSolrTestCase {
args.put(StatsParams.STATS, "true");
args.put(StatsParams.STATS_FIELD, fieldName);
args.put(StatsParams.STATS_FACET, fieldName);
args.put("indent", "true");
args.put(StatsParams.STATS_CALC_DISTINCT, "true");
args.put("indent", "true");
SolrQueryRequest req = new LocalSolrQueryRequest(core, new MapSolrParams(args));
@ -987,27 +1022,107 @@ public class StatsComponentTest extends AbstractSolrTestCase {
assertU(commit());
Map<String, String> args = new HashMap<>();
args.put(CommonParams.Q, "*:*");
args.put(StatsParams.STATS, "true");
args.put(StatsParams.STATS_FIELD, fieldName);
args.put(StatsParams.STATS_CALC_DISTINCT, "true");
args.put("indent", "true");
SolrQueryRequest req = new LocalSolrQueryRequest(core, new MapSolrParams(args));
final SolrParams baseParams = params(CommonParams.Q, "*:*",
"indent", "true",
StatsParams.STATS, "true");
assertQ("test min/max on docValues and multiValued", req
, "//lst[@name='" + fieldName + "']/double[@name='min'][.='-3.0']"
, "//lst[@name='" + fieldName + "']/double[@name='max'][.='16.0']"
, "//lst[@name='" + fieldName + "']/long[@name='count'][.='12']"
, "//lst[@name='" + fieldName + "']/double[@name='sum'][.='38.0']"
, "//lst[@name='" + fieldName + "']/long[@name='countDistinct'][.='9']"
, "//lst[@name='" + fieldName + "']/double[@name='mean'][.='3.1666666666666665']"
, "//lst[@name='" + fieldName + "']/double[@name='stddev'][.='5.638074031784151']"
, "//lst[@name='" + fieldName + "']/double[@name='sumOfSquares'][.='470.0']"
, "//lst[@name='" + fieldName + "']/long[@name='missing'][.='0']");
SolrQueryRequest req1 = req(baseParams,
StatsParams.STATS_CALC_DISTINCT, "true",
StatsParams.STATS_FIELD, fieldName);
SolrQueryRequest req2 = req(baseParams,
StatsParams.STATS_FIELD,
"{!min=true, max=true, count=true, sum=true, mean=true, stddev=true, sumOfSquares=true, missing=true, calcdistinct=true}" + fieldName);
for (SolrQueryRequest req : new SolrQueryRequest[] { req1, req2 }) {
assertQ("test status on docValues and multiValued: " + req.toString(), req
, "//lst[@name='" + fieldName + "']/double[@name='min'][.='-3.0']"
, "//lst[@name='" + fieldName + "']/double[@name='max'][.='16.0']"
, "//lst[@name='" + fieldName + "']/long[@name='count'][.='12']"
, "//lst[@name='" + fieldName + "']/double[@name='sum'][.='38.0']"
, "//lst[@name='" + fieldName + "']/double[@name='mean'][.='3.1666666666666665']"
, "//lst[@name='" + fieldName + "']/double[@name='stddev'][.='5.638074031784151']"
, "//lst[@name='" + fieldName + "']/double[@name='sumOfSquares'][.='470.0']"
, "//lst[@name='" + fieldName + "']/long[@name='missing'][.='0']"
, "//lst[@name='" + fieldName + "']/long[@name='countDistinct'][.='9']"
// always comes along with countDistinct
, "count(//lst[@name='" + fieldName + "']/arr[@name='distinctValues']/float)=9"
// if new default stats are added, this will break - update test to assert values for each
,"count(//lst[@name='" + fieldName + "']/*)=10"
);
}
}
public void testEnumFieldTypeStatus() throws Exception {
clearIndex();
String fieldName = "severity";
assertU(adoc("id", "0", fieldName, "Not Available"));
assertU(adoc("id", "1", fieldName, "Not Available"));
assertU(adoc("id", "2", fieldName, "Not Available"));
assertU(adoc("id", "3", fieldName, "Not Available"));
assertU(adoc("id", "4", fieldName, "Not Available"));
assertU(adoc("id", "5", fieldName, "Low"));
assertU(adoc("id", "6", fieldName, "Low"));
assertU(adoc("id", "7", fieldName, "Low"));
assertU(adoc("id", "8", fieldName, "Low"));
assertU(adoc("id", "9", fieldName, "Medium"));
assertU(adoc("id", "10", fieldName, "Medium"));
assertU(adoc("id", "11", fieldName, "Medium"));
assertU(adoc("id", "12", fieldName, "High"));
assertU(adoc("id", "13", fieldName, "High"));
assertU(adoc("id", "14", fieldName, "Critical"));
for (int i = 20; i <= 30; i++) {
assertU(adoc("id", "" + i));
}
assertU(commit());
assertQ("enum", req("q","*:*", "stats", "true", "stats.field", fieldName)
, "//lst[@name='" + fieldName + "']/str[@name='min'][.='Not Available']"
, "//lst[@name='" + fieldName + "']/str[@name='max'][.='Critical']"
, "//lst[@name='" + fieldName + "']/long[@name='count'][.='15']"
, "//lst[@name='" + fieldName + "']/long[@name='missing'][.='11']");
assertQ("enum calcdistinct", req("q","*:*", "stats", "true", "stats.field", fieldName,
StatsParams.STATS_CALC_DISTINCT, "true")
, "//lst[@name='" + fieldName + "']/str[@name='min'][.='Not Available']"
, "//lst[@name='" + fieldName + "']/str[@name='max'][.='Critical']"
, "//lst[@name='" + fieldName + "']/long[@name='count'][.='15']"
, "//lst[@name='" + fieldName + "']/long[@name='countDistinct'][.='5']"
, "count(//lst[@name='" + fieldName + "']/arr[@name='distinctValues']/*)=5"
, "//lst[@name='" + fieldName + "']/long[@name='missing'][.='11']");
final String pre = "//lst[@name='stats_fields']/lst[@name='"+fieldName+"']/lst[@name='facets']/lst[@name='severity']";
assertQ("enum + stats.facet", req("q","*:*", "stats", "true", "stats.field", fieldName,
"stats.facet", fieldName)
, pre + "/lst[@name='High']/str[@name='min'][.='High']"
, pre + "/lst[@name='High']/str[@name='max'][.='High']"
, pre + "/lst[@name='High']/long[@name='count'][.='2']"
, pre + "/lst[@name='High']/long[@name='missing'][.='0']"
, pre + "/lst[@name='Low']/str[@name='min'][.='Low']"
, pre + "/lst[@name='Low']/str[@name='max'][.='Low']"
, pre + "/lst[@name='Low']/long[@name='count'][.='4']"
, pre + "/lst[@name='Low']/long[@name='missing'][.='0']"
, pre + "/lst[@name='Medium']/str[@name='min'][.='Medium']"
, pre + "/lst[@name='Medium']/str[@name='max'][.='Medium']"
, pre + "/lst[@name='Medium']/long[@name='count'][.='3']"
, pre + "/lst[@name='Medium']/long[@name='missing'][.='0']"
, pre + "/lst[@name='Not Available']/str[@name='min'][.='Not Available']"
, pre + "/lst[@name='Not Available']/str[@name='max'][.='Not Available']"
, pre + "/lst[@name='Not Available']/long[@name='count'][.='5']"
, pre + "/lst[@name='Not Available']/long[@name='missing'][.='0']"
, pre + "/lst[@name='Critical']/str[@name='min'][.='Critical']"
, pre + "/lst[@name='Critical']/str[@name='max'][.='Critical']"
, pre + "/lst[@name='Critical']/long[@name='count'][.='1']"
, pre + "/lst[@name='Critical']/long[@name='missing'][.='0']"
);
}
private Doc createDocValuesDocument(List<FldType> types, String fieldName, String id, Comparable... values) throws Exception {
Doc doc = createDoc(types);
doc.getValues("id").set(0, id);
@ -1020,30 +1135,302 @@ public class StatsComponentTest extends AbstractSolrTestCase {
return cat_docValues;
}
public void testIndividualStatLocalParams() throws Exception {
final String kpre = XPRE + "lst[@name='stats_fields']/lst[@name='k']/";
assertU(adoc("id", "1", "a_f", "2.3", "b_f", "9.7", "a_i", "9", "foo_t", "how now brown cow"));
assertU(commit());
// some quick sanity check assertions...
// trivial check that we only get the exact 2 we ask for
assertQ("ask for and get only 2 stats",
req("q","*:*", "stats", "true",
"stats.field", "{!key=k mean=true min=true}a_i")
, kpre + "double[@name='mean'][.='9.0']"
, kpre + "double[@name='min'][.='9.0']"
, "count(" + kpre + "*)=2"
);
// for stats that are true/false, sanity check false does it's job
assertQ("min=true & max=false: only min should come back",
req("q","*:*", "stats", "true",
"stats.field", "{!key=k max=false min=true}a_i")
, kpre + "double[@name='min'][.='9.0']"
, "count(" + kpre + "*)=1"
);
assertQ("min=false: localparam stat means ignore default set, "+
"but since only local param is false no stats should be returned",
req("q","*:*", "stats", "true",
"stats.field", "{!key=k min=false}a_i")
, "count(" + kpre + "*)=0"
);
double sum = 0;
double sumOfSquares = 0;
final int count = 20;
for (int i = 0; i < count; i++) {
assertU(adoc("id", String.valueOf(i), "a_f", "2.3", "b_f", "9.7", "a_i", String.valueOf(i%10), "foo_t", "how now brown cow"));
sum+=i%10;
sumOfSquares+=(i%10)*(i%10);
}
assertU(commit());
EnumSet<Stat> allStats = EnumSet.allOf(Stat.class);
Map<Stat, String> expectedStats = new HashMap<>();
expectedStats.put(Stat.min, "0.0");
expectedStats.put(Stat.max, "9.0");
expectedStats.put(Stat.missing, "0");
expectedStats.put(Stat.sum, String.valueOf(sum));
expectedStats.put(Stat.count, String.valueOf(count));
expectedStats.put(Stat.mean, String.valueOf(sum/count));
expectedStats.put(Stat.sumOfSquares, String.valueOf(sumOfSquares));
expectedStats.put(Stat.stddev, String.valueOf(Math.sqrt(((count * sumOfSquares) - (sum * sum)) / (20 * (count - 1.0D)))));
expectedStats.put(Stat.calcdistinct, "10");
Map<Stat, String> expectedType = new HashMap<>();
expectedType.put(Stat.min, "double");
expectedType.put(Stat.max, "double");
expectedType.put(Stat.missing, "long");
expectedType.put(Stat.sum, "double");
expectedType.put(Stat.count, "long");
expectedType.put(Stat.mean, "double");
expectedType.put(Stat.sumOfSquares, "double");
expectedType.put(Stat.stddev, "double");
expectedType.put(Stat.calcdistinct, "long");
// canary in the coal mine
assertEquals("size of expectedStats doesn't match all known stats; " +
"enum was updated w/o updating test?",
expectedStats.size(), allStats.size());
assertEquals("size of expectedType doesn't match all known stats; " +
"enum was updated w/o updating test?",
expectedType.size(), allStats.size());
// whitebox test: explicitly ask for isShard=true with an individual stat
for (Stat stat : expectedStats.keySet()) {
EnumSet<Stat> distribDeps = stat.getDistribDeps();
StringBuilder exclude = new StringBuilder();
List<String> testParas = new ArrayList<String>(distribDeps.size() + 2);
int calcdistinctFudge = 0;
for (Stat perShardStat : distribDeps ){
String key = perShardStat.toString();
if (perShardStat.equals(Stat.calcdistinct)) {
// this abomination breaks all the rules - uses a diff response key and triggers
// the additional "distinctValues" stat
key = "countDistinct";
calcdistinctFudge++;
testParas.add("count(" + kpre + "arr[@name='distinctValues']/*)=10");
}
testParas.add(kpre + expectedType.get(perShardStat) +
"[@name='" + key + "'][.='" + expectedStats.get(perShardStat) + "']");
// even if we go out of our way to exclude the dependent stats,
// the shard should return them since they are a dependency for the requested stat
exclude.append(perShardStat + "=false ");
}
testParas.add("count(" + kpre + "*)=" + (distribDeps.size() + calcdistinctFudge));
assertQ("ask for only "+stat+", with isShard=true, and expect only deps: " + distribDeps,
req("q", "*:*", "isShard", "true", "stats", "true",
"stats.field", "{!key=k " + exclude + stat + "=true}a_i")
, testParas.toArray(new String[testParas.size()])
);
}
// test all the possible combinations (of all possible sizes) of stats params
for (int numParams = 1; numParams <= allStats.size(); numParams++) {
for (EnumSet<Stat> set : new StatSetCombinations(numParams, allStats)) {
// EnumSets use natural ordering, we want to randomize the order of the params
List<Stat> combo = new ArrayList<Stat>(set);
Collections.shuffle(combo, random());
StringBuilder paras = new StringBuilder("{!key=k ");
List<String> testParas = new ArrayList<String>(numParams + 2);
int calcdistinctFudge = 0;
for (Stat stat : combo) {
String key = stat.toString();
if (stat.equals(Stat.calcdistinct)) {
// this abomination breaks all the rules - uses a diff response key and triggers
// the additional "distinctValues" stat
key = "countDistinct";
calcdistinctFudge++;
testParas.add("count(" + kpre + "arr[@name='distinctValues']/*)=10");
}
paras.append(stat + "=true ");
testParas.add(kpre + expectedType.get(stat) + "[@name='" + key + "'][.='" + expectedStats.get(stat) + "']");
}
paras.append("}a_i");
testParas.add("count(" + kpre + "*)=" + (combo.size() + calcdistinctFudge));
assertQ("ask for an get only: "+ combo,
req("q","*:*", "stats", "true",
"stats.field", paras.toString())
, testParas.toArray(new String[testParas.size()])
);
}
}
}
// public void testOtherFacetStatsResult() throws Exception {
//
// assertU(adoc("id", "1", "stats_tls_dv", "10", "active_i", "1"));
// assertU(adoc("id", "2", "stats_tls_dv", "20", "active_i", "1"));
// assertU(commit());
// assertU(adoc("id", "3", "stats_tls_dv", "30", "active_i", "2"));
// assertU(adoc("id", "4", "stats_tls_dv", "40", "active_i", "2"));
// assertU(commit());
//
// final String pre = "//lst[@name='stats_fields']/lst[@name='stats_tls_dv']/lst[@name='facets']/lst[@name='active_i']";
//
// assertQ("test value for active_s=true", req("q", "*:*", "stats", "true", "stats.field", "stats_tls_dv", "stats.facet", "active_i","indent", "true")
// , "*[count("+pre+")=1]"
// , pre+"/lst[@name='1']/double[@name='min'][.='10.0']"
// , pre+"/lst[@name='1']/double[@name='max'][.='20.0']"
// , pre+"/lst[@name='1']/double[@name='sum'][.='30.0']"
// , pre+"/lst[@name='1']/long[@name='count'][.='2']"
// , pre+"/lst[@name='1']/long[@name='missing'][.='0']"
// , pre + "/lst[@name='true']/long[@name='countDistinct'][.='2']"
// , "count(" + pre + "/lst[@name='true']/arr[@name='distinctValues']/*)=2"
// , pre+"/lst[@name='1']/double[@name='sumOfSquares'][.='500.0']"
// , pre+"/lst[@name='1']/double[@name='mean'][.='15.0']"
// , pre+"/lst[@name='1']/double[@name='stddev'][.='7.0710678118654755']"
// );
// }
// Test for Solr-6349
public void testCalcDistinctStats() throws Exception {
final String kpre = XPRE + "lst[@name='stats_fields']/lst[@name='k']/";
final String min = "count(" + kpre +"/double[@name='min'])";
final String countDistinct = "count(" + kpre +"/long[@name='countDistinct'])";
final String distinctValues = "count(" + kpre +"/arr[@name='distinctValues'])";
final int count = 20;
for (int i = 0; i < count; i++) {
assertU(adoc("id", String.valueOf(i), "a_f", "2.3", "b_f", "9.7", "a_i",
String.valueOf(i % 10), "foo_t", "how now brown cow"));
}
assertU(commit());
String[] baseParams = new String[] { "q", "*:*", "stats", "true","indent", "true" };
for (SolrParams p : new SolrParams[] {
params("stats.field", "{!key=k}a_i"),
params(StatsParams.STATS_CALC_DISTINCT, "false",
"stats.field", "{!key=k}a_i"),
params("f.a_i." + StatsParams.STATS_CALC_DISTINCT, "false",
"stats.field", "{!key=k}a_i"),
params(StatsParams.STATS_CALC_DISTINCT, "true",
"f.a_i." + StatsParams.STATS_CALC_DISTINCT, "false",
"stats.field", "{!key=k}a_i"),
params("stats.field", "{!key=k min='true'}a_i"),
params(StatsParams.STATS_CALC_DISTINCT, "true",
"f.a_i." + StatsParams.STATS_CALC_DISTINCT, "true",
"stats.field", "{!key=k min='true' calcdistinct='false'}a_i"),
}) {
assertQ("min is either default or explicitly requested; "+
"countDistinct & distinctValues either default or explicitly prevented"
, req(p, baseParams)
, min + "=1"
, countDistinct + "=0"
, distinctValues + "=0");
}
for (SolrParams p : new SolrParams[] {
params("stats.calcdistinct", "true",
"stats.field", "{!key=k}a_i"),
params("f.a_i." + StatsParams.STATS_CALC_DISTINCT, "true",
"stats.field", "{!key=k}a_i"),
params("stats.calcdistinct", "false",
"f.a_i." + StatsParams.STATS_CALC_DISTINCT, "true",
"stats.field", "{!key=k}a_i"),
params("stats.calcdistinct", "false ",
"stats.field", "{!key=k min=true calcdistinct=true}a_i"),
params("f.a_i." + StatsParams.STATS_CALC_DISTINCT, "false",
"stats.field", "{!key=k min=true calcdistinct=true}a_i"),
params("stats.calcdistinct", "false ",
"f.a_i." + StatsParams.STATS_CALC_DISTINCT, "false",
"stats.field", "{!key=k min=true calcdistinct=true}a_i"),
}) {
assertQ("min is either default or explicitly requested; " +
"countDistinct & distinctValues explicitly requested"
, req(p, baseParams)
, min + "=1"
, countDistinct + "=1"
, distinctValues + "=1");
}
for (SolrParams p : new SolrParams[] {
params("stats.field", "{!key=k calcdistinct=true}a_i"),
params("stats.calcdistinct", "true",
"stats.field", "{!key=k min='false'}a_i"),
params("stats.calcdistinct", "true",
"stats.field", "{!key=k max='true' min='false'}a_i"),
params("stats.calcdistinct", "false",
"stats.field", "{!key=k calcdistinct=true}a_i"),
params("f.a_i." + StatsParams.STATS_CALC_DISTINCT, "false",
"stats.field", "{!key=k calcdistinct=true}a_i"),
params("stats.calcdistinct", "false",
"f.a_i." + StatsParams.STATS_CALC_DISTINCT, "false",
"stats.field", "{!key=k calcdistinct=true}a_i"),
params("stats.calcdistinct", "false",
"f.a_i." + StatsParams.STATS_CALC_DISTINCT, "false",
"stats.field", "{!key=k min='false' calcdistinct=true}a_i"),
}) {
assertQ("min is explicitly excluded; " +
"countDistinct & distinctValues explicitly requested"
, req(p, baseParams)
, min + "=0"
, countDistinct + "=1"
, distinctValues + "=1");
}
for (SolrParams p : new SolrParams[] {
params(StatsParams.STATS_CALC_DISTINCT, "true",
"stats.field", "{!key=k min=true}a_i"),
params("f.a_i.stats.calcdistinct", "true",
"stats.field", "{!key=k min=true}a_i"),
params(StatsParams.STATS_CALC_DISTINCT, "false",
"f.a_i.stats.calcdistinct", "true",
"stats.field", "{!key=k min=true}a_i"),
params("f.a_i.stats.calcdistinct", "false",
"stats.field", "{!key=k min=true calcdistinct=true}a_i"),
params(StatsParams.STATS_CALC_DISTINCT, "false",
"stats.field", "{!key=k min=true calcdistinct=true}a_i"),
params(StatsParams.STATS_CALC_DISTINCT, "false",
"f.a_i.stats.calcdistinct", "false",
"stats.field", "{!key=k min=true calcdistinct=true}a_i"),
}) {
assertQ("min is explicitly requested; " +
"countDistinct & distinctValues explicitly requested"
, req(p, baseParams)
, min + "=1"
, countDistinct + "=1"
, distinctValues + "=1");
}
}
/**
* given a comboSize and an EnumSet of Stats, generates iterators that produce every possible
* enum combination of that size
*/
public static final class StatSetCombinations implements Iterable<EnumSet<Stat>> {
// we need an array so we can do fixed index offset lookups
private final Stat[] all;
private final Combinations intCombos;
public StatSetCombinations(int comboSize, EnumSet<Stat> universe) {
// NOTE: should not need to sort, EnumSet uses natural ordering
all = universe.toArray(new Stat[universe.size()]);
intCombos = new Combinations(all.length, comboSize);
}
public Iterator<EnumSet<Stat>> iterator() {
return new Iterator<EnumSet<Stat>>() {
final Iterator<int[]> wrapped = intCombos.iterator();
public void remove() {
wrapped.remove();
}
public boolean hasNext() {
return wrapped.hasNext();
}
public EnumSet<Stat> next() {
EnumSet<Stat> result = EnumSet.noneOf(Stat.class);
int[] indexes = wrapped.next();
for (int i = 0; i < indexes.length; i++) {
result.add(all[indexes[i]]);
}
return result;
}
};
}
}
}

View File

@ -0,0 +1 @@
3ac44a8664228384bc68437264cf7c4cf112f579

View File

@ -0,0 +1,457 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache Commons Math includes the following code provided to the ASF under the
Apache License 2.0:
- The inverse error function implementation in the Erf class is based on CUDA
code developed by Mike Giles, Oxford-Man Institute of Quantitative Finance,
and published in GPU Computing Gems, volume 2, 2010 (grant received on
March 23th 2013)
- The LinearConstraint, LinearObjectiveFunction, LinearOptimizer,
RelationShip, SimplexSolver and SimplexTableau classes in package
org.apache.commons.math3.optimization.linear include software developed by
Benjamin McCann (http://www.benmccann.com) and distributed with
the following copyright: Copyright 2009 Google Inc. (grant received on
March 16th 2009)
- The class "org.apache.commons.math3.exception.util.LocalizedFormatsTest" which
is an adapted version of "OrekitMessagesTest" test class for the Orekit library
- The "org.apache.commons.math3.analysis.interpolation.HermiteInterpolator"
has been imported from the Orekit space flight dynamics library.
===============================================================================
APACHE COMMONS MATH DERIVATIVE WORKS:
The Apache commons-math library includes a number of subcomponents
whose implementation is derived from original sources written
in C or Fortran. License terms of the original sources
are reproduced below.
===============================================================================
For the lmder, lmpar and qrsolv Fortran routine from minpack and translated in
the LevenbergMarquardtOptimizer class in package
org.apache.commons.math3.optimization.general
Original source copyright and license statement:
Minpack Copyright Notice (1999) University of Chicago. All rights reserved
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
1. Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
3. The end-user documentation included with the
redistribution, if any, must include the following
acknowledgment:
"This product includes software developed by the
University of Chicago, as Operator of Argonne National
Laboratory.
Alternately, this acknowledgment may appear in the software
itself, if and wherever such third-party acknowledgments
normally appear.
4. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS"
WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE
UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND
THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE
OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY
OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR
USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF
THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4)
DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION
UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL
BE CORRECTED.
5. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT
HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF
ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT,
INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF
ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF
PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER
SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT
(INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE,
EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
POSSIBILITY OF SUCH LOSS OR DAMAGES.
===============================================================================
Copyright and license statement for the odex Fortran routine developed by
E. Hairer and G. Wanner and translated in GraggBulirschStoerIntegrator class
in package org.apache.commons.math3.ode.nonstiff:
Copyright (c) 2004, Ernst Hairer
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================
Copyright and license statement for the original Mersenne twister C
routines translated in MersenneTwister class in package
org.apache.commons.math3.random:
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================
The initial code for shuffling an array (originally in class
"org.apache.commons.math3.random.RandomDataGenerator", now replaced by
a method in class "org.apache.commons.math3.util.MathArrays") was
inspired from the algorithm description provided in
"Algorithms", by Ian Craw and John Pulham (University of Aberdeen 1999).
The textbook (containing a proof that the shuffle is uniformly random) is
available here:
http://citeseerx.ist.psu.edu/viewdoc/download;?doi=10.1.1.173.1898&rep=rep1&type=pdf
===============================================================================
License statement for the direction numbers in the resource files for Sobol sequences.
-----------------------------------------------------------------------------
Licence pertaining to sobol.cc and the accompanying sets of direction numbers
-----------------------------------------------------------------------------
Copyright (c) 2008, Frances Y. Kuo and Stephen Joe
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of the copyright holders nor the names of the
University of New South Wales and the University of Waikato
and its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================
The initial commit of package "org.apache.commons.math3.ml.neuralnet" is
an adapted version of code developed in the context of the Data Processing
and Analysis Consortium (DPAC) of the "Gaia" project of the European Space
Agency (ESA).
===============================================================================
The initial commit of the class "org.apache.commons.math3.special.BesselJ" is
an adapted version of code translated from the netlib Fortran program, rjbesl
http://www.netlib.org/specfun/rjbesl by R.J. Cody at Argonne National
Laboratory (USA). There is no license or copyright statement included with the
original Fortran sources.
===============================================================================
The BracketFinder (package org.apache.commons.math3.optimization.univariate)
and PowellOptimizer (package org.apache.commons.math3.optimization.general)
classes are based on the Python code in module "optimize.py" (version 0.5)
developed by Travis E. Oliphant for the SciPy library (http://www.scipy.org/)
Copyright © 2003-2009 SciPy Developers.
SciPy license
Copyright © 2001, 2002 Enthought, Inc.
All rights reserved.
Copyright © 2003-2013 SciPy Developers.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Enthought nor the names of the SciPy Developers may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================

View File

@ -0,0 +1,9 @@
Apache Commons Math
Copyright 2001-2015 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
This product includes software developed for Orekit by
CS Systèmes d'Information (http://www.c-s.fr/)
Copyright 2010-2012 CS Systèmes d'Information

View File

@ -750,7 +750,7 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
try {
String m = (null == message) ? "" : message + " ";
String response = h.query(req);
if (req.getParams().getBool("facet", false)) {
// add a test to ensure that faceting did not throw an exception
// internally, where it would be added to facet_counts/exception
@ -1888,6 +1888,7 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
FileUtils.copyFile(new File(top, "open-exchange-rates.json"), new File(subHome, "open-exchange-rates.json"));
FileUtils.copyFile(new File(top, "protwords.txt"), new File(subHome, "protwords.txt"));
FileUtils.copyFile(new File(top, "schema.xml"), new File(subHome, "schema.xml"));
FileUtils.copyFile(new File(top, "enumsConfig.xml"), new File(subHome, "enumsConfig.xml"));
FileUtils.copyFile(new File(top, "solrconfig.snippet.randomindexconfig.xml"), new File(subHome, "solrconfig.snippet.randomindexconfig.xml"));
FileUtils.copyFile(new File(top, "solrconfig.xml"), new File(subHome, "solrconfig.xml"));
FileUtils.copyFile(new File(top, "stopwords.txt"), new File(subHome, "stopwords.txt"));