Adds the ability to specify a format on composite date_histogram source (#28310)

This commit adds the ability to specify a date format on the `date_histogram` composite source.
If the format is defined, the key for the source is returned as a formatted date.

Closes #27923
This commit is contained in:
Jim Ferenczi 2018-01-23 15:14:49 +01:00 committed by GitHub
parent d31e964a86
commit 19cfc25873
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 401 additions and 76 deletions

View File

@ -225,7 +225,41 @@ Note that fractional time values are not supported, but you can address this by
time unit (e.g., `1.5h` could instead be specified as `90m`). time unit (e.g., `1.5h` could instead be specified as `90m`).
[float] [float]
===== Time Zone ====== Format
Internally, a date is represented as a 64 bit number representing a timestamp in milliseconds-since-the-epoch.
These timestamps are returned as the bucket keys. It is possible to return a formatted date string instead using
the format specified with the format parameter:
[source,js]
--------------------------------------------------
GET /_search
{
"aggs" : {
"my_buckets": {
"composite" : {
"sources" : [
{
"date": {
"date_histogram" : {
"field": "timestamp",
"interval": "1d",
"format": "yyyy-MM-dd" <1>
}
}
}
]
}
}
}
}
--------------------------------------------------
// CONSOLE
<1> Supports expressive date <<date-format-pattern,format pattern>>
[float]
====== Time Zone
Date-times are stored in Elasticsearch in UTC. By default, all bucketing and Date-times are stored in Elasticsearch in UTC. By default, all bucketing and
rounding is also done in UTC. The `time_zone` parameter can be used to indicate rounding is also done in UTC. The `time_zone` parameter can be used to indicate

View File

@ -7,6 +7,8 @@ setup:
mappings: mappings:
doc: doc:
properties: properties:
date:
type: date
keyword: keyword:
type: keyword type: keyword
long: long:
@ -40,6 +42,20 @@ setup:
id: 4 id: 4
body: { "keyword": "bar", "long": [1000, 0] } body: { "keyword": "bar", "long": [1000, 0] }
- do:
index:
index: test
type: doc
id: 5
body: { "date": "2017-10-20T03:08:45" }
- do:
index:
index: test
type: doc
id: 6
body: { "date": "2017-10-21T07:00:00" }
- do: - do:
indices.refresh: indices.refresh:
index: [test] index: [test]
@ -66,7 +82,7 @@ setup:
} }
] ]
- match: {hits.total: 4} - match: {hits.total: 6}
- length: { aggregations.test.buckets: 2 } - length: { aggregations.test.buckets: 2 }
- match: { aggregations.test.buckets.0.key.kw: "bar" } - match: { aggregations.test.buckets.0.key.kw: "bar" }
- match: { aggregations.test.buckets.0.doc_count: 3 } - match: { aggregations.test.buckets.0.doc_count: 3 }
@ -104,7 +120,7 @@ setup:
} }
] ]
- match: {hits.total: 4} - match: {hits.total: 6}
- length: { aggregations.test.buckets: 5 } - length: { aggregations.test.buckets: 5 }
- match: { aggregations.test.buckets.0.key.long: 0} - match: { aggregations.test.buckets.0.key.long: 0}
- match: { aggregations.test.buckets.0.key.kw: "bar" } - match: { aggregations.test.buckets.0.key.kw: "bar" }
@ -154,7 +170,7 @@ setup:
] ]
after: { "long": 20, "kw": "foo" } after: { "long": 20, "kw": "foo" }
- match: {hits.total: 4} - match: {hits.total: 6}
- length: { aggregations.test.buckets: 2 } - length: { aggregations.test.buckets: 2 }
- match: { aggregations.test.buckets.0.key.long: 100 } - match: { aggregations.test.buckets.0.key.long: 100 }
- match: { aggregations.test.buckets.0.key.kw: "bar" } - match: { aggregations.test.buckets.0.key.kw: "bar" }
@ -188,7 +204,7 @@ setup:
] ]
after: { "kw": "delta" } after: { "kw": "delta" }
- match: {hits.total: 4} - match: {hits.total: 6}
- length: { aggregations.test.buckets: 1 } - length: { aggregations.test.buckets: 1 }
- match: { aggregations.test.buckets.0.key.kw: "foo" } - match: { aggregations.test.buckets.0.key.kw: "foo" }
- match: { aggregations.test.buckets.0.doc_count: 2 } - match: { aggregations.test.buckets.0.doc_count: 2 }
@ -220,3 +236,62 @@ setup:
} }
} }
] ]
---
"Composite aggregation with format":
- skip:
version: " - 6.99.99"
reason: this uses a new option (format) added in 7.0.0
- do:
search:
index: test
body:
aggregations:
test:
composite:
sources: [
{
"date": {
"date_histogram": {
"field": "date",
"interval": "1d",
"format": "yyyy-MM-dd"
}
}
}
]
- match: {hits.total: 6}
- length: { aggregations.test.buckets: 2 }
- match: { aggregations.test.buckets.0.key.date: "2017-10-20" }
- match: { aggregations.test.buckets.0.doc_count: 1 }
- match: { aggregations.test.buckets.1.key.date: "2017-10-21" }
- match: { aggregations.test.buckets.1.doc_count: 1 }
- do:
search:
index: test
body:
aggregations:
test:
composite:
after: {
date: "2017-10-20"
}
sources: [
{
"date": {
"date_histogram": {
"field": "date",
"interval": "1d",
"format": "yyyy-MM-dd"
}
}
}
]
- match: {hits.total: 6}
- length: { aggregations.test.buckets: 1 }
- match: { aggregations.test.buckets.0.key.date: "2017-10-21" }
- match: { aggregations.test.buckets.0.doc_count: 1 }

View File

@ -147,17 +147,15 @@ public class CompositeAggregationBuilder extends AbstractAggregationBuilder<Comp
Sort sort = indexSortConfig.buildIndexSort(shardContext::fieldMapper, shardContext::getForField); Sort sort = indexSortConfig.buildIndexSort(shardContext::fieldMapper, shardContext::getForField);
System.arraycopy(sort.getSort(), 0, sortFields, 0, sortFields.length); System.arraycopy(sort.getSort(), 0, sortFields, 0, sortFields.length);
} }
List<String> sourceNames = new ArrayList<>();
for (int i = 0; i < configs.length; i++) { for (int i = 0; i < configs.length; i++) {
configs[i] = sources.get(i).build(context, i, configs.length, sortFields[i]); configs[i] = sources.get(i).build(context, i, configs.length, sortFields[i]);
sourceNames.add(sources.get(i).name());
if (configs[i].valuesSource().needsScores()) { if (configs[i].valuesSource().needsScores()) {
throw new IllegalArgumentException("[sources] cannot access _score"); throw new IllegalArgumentException("[sources] cannot access _score");
} }
} }
final CompositeKey afterKey; final CompositeKey afterKey;
if (after != null) { if (after != null) {
if (after.size() != sources.size()) { if (after.size() != configs.length) {
throw new IllegalArgumentException("[after] has " + after.size() + throw new IllegalArgumentException("[after] has " + after.size() +
" value(s) but [sources] has " + sources.size()); " value(s) but [sources] has " + sources.size());
} }
@ -179,7 +177,7 @@ public class CompositeAggregationBuilder extends AbstractAggregationBuilder<Comp
} else { } else {
afterKey = null; afterKey = null;
} }
return new CompositeAggregationFactory(name, context, parent, subfactoriesBuilder, metaData, size, configs, sourceNames, afterKey); return new CompositeAggregationFactory(name, context, parent, subfactoriesBuilder, metaData, size, configs, afterKey);
} }

View File

@ -32,17 +32,14 @@ import java.util.Map;
class CompositeAggregationFactory extends AggregatorFactory<CompositeAggregationFactory> { class CompositeAggregationFactory extends AggregatorFactory<CompositeAggregationFactory> {
private final int size; private final int size;
private final CompositeValuesSourceConfig[] sources; private final CompositeValuesSourceConfig[] sources;
private final List<String> sourceNames;
private final CompositeKey afterKey; private final CompositeKey afterKey;
CompositeAggregationFactory(String name, SearchContext context, AggregatorFactory<?> parent, CompositeAggregationFactory(String name, SearchContext context, AggregatorFactory<?> parent,
AggregatorFactories.Builder subFactoriesBuilder, Map<String, Object> metaData, AggregatorFactories.Builder subFactoriesBuilder, Map<String, Object> metaData,
int size, CompositeValuesSourceConfig[] sources, int size, CompositeValuesSourceConfig[] sources, CompositeKey afterKey) throws IOException {
List<String> sourceNames, CompositeKey afterKey) throws IOException {
super(name, context, parent, subFactoriesBuilder, metaData); super(name, context, parent, subFactoriesBuilder, metaData);
this.size = size; this.size = size;
this.sources = sources; this.sources = sources;
this.sourceNames = sourceNames;
this.afterKey = afterKey; this.afterKey = afterKey;
} }
@ -50,6 +47,6 @@ class CompositeAggregationFactory extends AggregatorFactory<CompositeAggregation
protected Aggregator createInternal(Aggregator parent, boolean collectsFromSingleBucket, protected Aggregator createInternal(Aggregator parent, boolean collectsFromSingleBucket,
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException { List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException {
return new CompositeAggregator(name, factories, context, parent, pipelineAggregators, metaData, return new CompositeAggregator(name, factories, context, parent, pipelineAggregators, metaData,
size, sources, sourceNames, afterKey); size, sources, afterKey);
} }
} }

View File

@ -27,6 +27,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight; import org.apache.lucene.search.Weight;
import org.apache.lucene.util.RoaringDocIdSet; import org.apache.lucene.util.RoaringDocIdSet;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.Aggregator;
import org.elasticsearch.search.aggregations.AggregatorFactories; import org.elasticsearch.search.aggregations.AggregatorFactories;
import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.InternalAggregation;
@ -43,11 +44,13 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.stream.Collectors;
final class CompositeAggregator extends BucketsAggregator { final class CompositeAggregator extends BucketsAggregator {
private final int size; private final int size;
private final CompositeValuesSourceConfig[] sources; private final CompositeValuesSourceConfig[] sources;
private final List<String> sourceNames; private final List<String> sourceNames;
private final List<DocValueFormat> formats;
private final boolean canEarlyTerminate; private final boolean canEarlyTerminate;
private final TreeMap<Integer, Integer> keys; private final TreeMap<Integer, Integer> keys;
@ -59,12 +62,12 @@ final class CompositeAggregator extends BucketsAggregator {
CompositeAggregator(String name, AggregatorFactories factories, SearchContext context, Aggregator parent, CompositeAggregator(String name, AggregatorFactories factories, SearchContext context, Aggregator parent,
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData, List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData,
int size, CompositeValuesSourceConfig[] sources, List<String> sourceNames, int size, CompositeValuesSourceConfig[] sources, CompositeKey rawAfterKey) throws IOException {
CompositeKey rawAfterKey) throws IOException {
super(name, factories, context, parent, pipelineAggregators, metaData); super(name, factories, context, parent, pipelineAggregators, metaData);
this.size = size; this.size = size;
this.sources = sources; this.sources = sources;
this.sourceNames = sourceNames; this.sourceNames = Arrays.stream(sources).map(CompositeValuesSourceConfig::name).collect(Collectors.toList());
this.formats = Arrays.stream(sources).map(CompositeValuesSourceConfig::format).collect(Collectors.toList());
// we use slot 0 to fill the current document (size+1). // we use slot 0 to fill the current document (size+1).
this.array = new CompositeValuesComparator(context.searcher().getIndexReader(), sources, size+1); this.array = new CompositeValuesComparator(context.searcher().getIndexReader(), sources, size+1);
if (rawAfterKey != null) { if (rawAfterKey != null) {
@ -131,15 +134,17 @@ final class CompositeAggregator extends BucketsAggregator {
CompositeKey key = array.toCompositeKey(slot); CompositeKey key = array.toCompositeKey(slot);
InternalAggregations aggs = bucketAggregations(slot); InternalAggregations aggs = bucketAggregations(slot);
int docCount = bucketDocCount(slot); int docCount = bucketDocCount(slot);
buckets[pos++] = new InternalComposite.InternalBucket(sourceNames, key, reverseMuls, docCount, aggs); buckets[pos++] = new InternalComposite.InternalBucket(sourceNames, formats, key, reverseMuls, docCount, aggs);
} }
return new InternalComposite(name, size, sourceNames, Arrays.asList(buckets), reverseMuls, pipelineAggregators(), metaData()); return new InternalComposite(name, size, sourceNames, formats, Arrays.asList(buckets), reverseMuls,
pipelineAggregators(), metaData());
} }
@Override @Override
public InternalAggregation buildEmptyAggregation() { public InternalAggregation buildEmptyAggregation() {
final int[] reverseMuls = getReverseMuls(); final int[] reverseMuls = getReverseMuls();
return new InternalComposite(name, size, sourceNames, Collections.emptyList(), reverseMuls, pipelineAggregators(), metaData()); return new InternalComposite(name, size, sourceNames, formats, Collections.emptyList(), reverseMuls,
pipelineAggregators(), metaData());
} }
@Override @Override

View File

@ -56,7 +56,7 @@ final class CompositeValuesComparator {
if (vs.isFloatingPoint()) { if (vs.isFloatingPoint()) {
arrays[i] = CompositeValuesSource.wrapDouble(vs, size, reverseMul); arrays[i] = CompositeValuesSource.wrapDouble(vs, size, reverseMul);
} else { } else {
arrays[i] = CompositeValuesSource.wrapLong(vs, size, reverseMul); arrays[i] = CompositeValuesSource.wrapLong(vs, sources[i].format(), size, reverseMul);
} }
} }
} }

View File

@ -23,8 +23,10 @@ import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.search.LeafCollector; import org.apache.lucene.search.LeafCollector;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSource;
import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.sort.SortOrder;
@ -96,8 +98,9 @@ abstract class CompositeValuesSource<VS extends ValuesSource, T extends Comparab
/** /**
* Creates a {@link CompositeValuesSource} that generates long values. * Creates a {@link CompositeValuesSource} that generates long values.
*/ */
static CompositeValuesSource<ValuesSource.Numeric, Long> wrapLong(ValuesSource.Numeric vs, int size, int reverseMul) { static CompositeValuesSource<ValuesSource.Numeric, Long> wrapLong(ValuesSource.Numeric vs, DocValueFormat format,
return new LongValuesSource(vs, size, reverseMul); int size, int reverseMul) {
return new LongValuesSource(vs, format, size, reverseMul);
} }
/** /**
@ -273,9 +276,12 @@ abstract class CompositeValuesSource<VS extends ValuesSource, T extends Comparab
*/ */
private static class LongValuesSource extends CompositeValuesSource<ValuesSource.Numeric, Long> { private static class LongValuesSource extends CompositeValuesSource<ValuesSource.Numeric, Long> {
private final long[] values; private final long[] values;
// handles "format" for date histogram source
private final DocValueFormat format;
LongValuesSource(ValuesSource.Numeric vs, int size, int reverseMul) { LongValuesSource(ValuesSource.Numeric vs, DocValueFormat format, int size, int reverseMul) {
super(vs, size, reverseMul); super(vs, size, reverseMul);
this.format = format;
this.values = new long[size]; this.values = new long[size];
} }
@ -304,7 +310,11 @@ abstract class CompositeValuesSource<VS extends ValuesSource, T extends Comparab
if (value instanceof Number) { if (value instanceof Number) {
topValue = ((Number) value).longValue(); topValue = ((Number) value).longValue();
} else { } else {
topValue = Long.parseLong(value.toString()); // for date histogram source with "format", the after value is formatted
// as a string so we need to retrieve the original value in milliseconds.
topValue = format.parseLong(value.toString(), false, () -> {
throw new IllegalArgumentException("now() is not supported in [after] key");
});
} }
} }

View File

@ -25,6 +25,7 @@ import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortField;
import org.elasticsearch.Version;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.io.stream.Writeable;
@ -51,6 +52,7 @@ public abstract class CompositeValuesSourceBuilder<AB extends CompositeValuesSou
private ValueType valueType = null; private ValueType valueType = null;
private Object missing = null; private Object missing = null;
private SortOrder order = SortOrder.ASC; private SortOrder order = SortOrder.ASC;
private String format = null;
CompositeValuesSourceBuilder(String name) { CompositeValuesSourceBuilder(String name) {
this(name, null); this(name, null);
@ -72,6 +74,11 @@ public abstract class CompositeValuesSourceBuilder<AB extends CompositeValuesSou
} }
this.missing = in.readGenericValue(); this.missing = in.readGenericValue();
this.order = SortOrder.readFromStream(in); this.order = SortOrder.readFromStream(in);
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
this.format = in.readOptionalString();
} else {
this.format = null;
}
} }
@Override @Override
@ -90,6 +97,9 @@ public abstract class CompositeValuesSourceBuilder<AB extends CompositeValuesSou
} }
out.writeGenericValue(missing); out.writeGenericValue(missing);
order.writeTo(out); order.writeTo(out);
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
out.writeOptionalString(format);
}
innerWriteTo(out); innerWriteTo(out);
} }
@ -112,6 +122,9 @@ public abstract class CompositeValuesSourceBuilder<AB extends CompositeValuesSou
if (valueType != null) { if (valueType != null) {
builder.field("value_type", valueType.getPreferredName()); builder.field("value_type", valueType.getPreferredName());
} }
if (format != null) {
builder.field("format", format);
}
builder.field("order", order); builder.field("order", order);
doXContentBody(builder, params); doXContentBody(builder, params);
builder.endObject(); builder.endObject();
@ -120,7 +133,7 @@ public abstract class CompositeValuesSourceBuilder<AB extends CompositeValuesSou
@Override @Override
public final int hashCode() { public final int hashCode() {
return Objects.hash(field, missing, script, valueType, order, innerHashCode()); return Objects.hash(field, missing, script, valueType, order, format, innerHashCode());
} }
protected abstract int innerHashCode(); protected abstract int innerHashCode();
@ -137,6 +150,7 @@ public abstract class CompositeValuesSourceBuilder<AB extends CompositeValuesSou
Objects.equals(valueType, that.valueType()) && Objects.equals(valueType, that.valueType()) &&
Objects.equals(missing, that.missing()) && Objects.equals(missing, that.missing()) &&
Objects.equals(order, that.order()) && Objects.equals(order, that.order()) &&
Objects.equals(format, that.format()) &&
innerEquals(that); innerEquals(that);
} }
@ -254,6 +268,24 @@ public abstract class CompositeValuesSourceBuilder<AB extends CompositeValuesSou
return order; return order;
} }
/**
* Sets the format to use for the output of the aggregation.
*/
public AB format(String format) {
if (format == null) {
throw new IllegalArgumentException("[format] must not be null: [" + name + "]");
}
this.format = format;
return (AB) this;
}
/**
* Gets the format to use for the output of the aggregation.
*/
public String format() {
return format;
}
/** /**
* Creates a {@link CompositeValuesSourceConfig} for this source. * Creates a {@link CompositeValuesSourceConfig} for this source.
* *
@ -271,7 +303,7 @@ public abstract class CompositeValuesSourceBuilder<AB extends CompositeValuesSou
public final CompositeValuesSourceConfig build(SearchContext context, int pos, int numPos, SortField sortField) throws IOException { public final CompositeValuesSourceConfig build(SearchContext context, int pos, int numPos, SortField sortField) throws IOException {
ValuesSourceConfig<?> config = ValuesSourceConfig.resolve(context.getQueryShardContext(), ValuesSourceConfig<?> config = ValuesSourceConfig.resolve(context.getQueryShardContext(),
valueType, field, script, missing, null, null); valueType, field, script, missing, null, format);
return innerBuild(context, config, pos, numPos, sortField); return innerBuild(context, config, pos, numPos, sortField);
} }

View File

@ -19,30 +19,47 @@
package org.elasticsearch.search.aggregations.bucket.composite; package org.elasticsearch.search.aggregations.bucket.composite;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSource;
import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.sort.SortOrder;
class CompositeValuesSourceConfig { class CompositeValuesSourceConfig {
private final String name; private final String name;
private final ValuesSource vs; private final ValuesSource vs;
private final DocValueFormat format;
private final int reverseMul; private final int reverseMul;
private final boolean canEarlyTerminate; private final boolean canEarlyTerminate;
CompositeValuesSourceConfig(String name, ValuesSource vs, SortOrder order, boolean canEarlyTerminate) { CompositeValuesSourceConfig(String name, ValuesSource vs, DocValueFormat format, SortOrder order, boolean canEarlyTerminate) {
this.name = name; this.name = name;
this.vs = vs; this.vs = vs;
this.format = format;
this.canEarlyTerminate = canEarlyTerminate; this.canEarlyTerminate = canEarlyTerminate;
this.reverseMul = order == SortOrder.ASC ? 1 : -1; this.reverseMul = order == SortOrder.ASC ? 1 : -1;
} }
/**
* Returns the name associated with this configuration.
*/
String name() { String name() {
return name; return name;
} }
/**
* Returns the {@link ValuesSource} for this configuration.
*/
ValuesSource valuesSource() { ValuesSource valuesSource() {
return vs; return vs;
} }
/**
* The {@link DocValueFormat} to use for formatting the keys.
* {@link DocValueFormat#RAW} means no formatting.
*/
DocValueFormat format() {
return format;
}
/** /**
* The sort order for the values source (e.g. -1 for descending and 1 for ascending). * The sort order for the values source (e.g. -1 for descending and 1 for ascending).
*/ */
@ -51,6 +68,9 @@ class CompositeValuesSourceConfig {
return reverseMul; return reverseMul;
} }
/**
* Returns whether this {@link ValuesSource} is used to sort the index.
*/
boolean canEarlyTerminate() { boolean canEarlyTerminate() {
return canEarlyTerminate; return canEarlyTerminate;
} }

View File

@ -30,6 +30,8 @@ import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.Script; import org.elasticsearch.script.Script;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
import org.elasticsearch.search.aggregations.support.FieldContext; import org.elasticsearch.search.aggregations.support.FieldContext;
@ -46,8 +48,8 @@ import java.util.Objects;
import static org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder.DATE_FIELD_UNITS; import static org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder.DATE_FIELD_UNITS;
/** /**
* A {@link CompositeValuesSourceBuilder} that that builds a {@link RoundingValuesSource} from a {@link Script} or * A {@link CompositeValuesSourceBuilder} that builds a {@link RoundingValuesSource} from a {@link Script} or
* a field name. * a field name using the provided interval.
*/ */
public class DateHistogramValuesSourceBuilder extends CompositeValuesSourceBuilder<DateHistogramValuesSourceBuilder> { public class DateHistogramValuesSourceBuilder extends CompositeValuesSourceBuilder<DateHistogramValuesSourceBuilder> {
static final String TYPE = "date_histogram"; static final String TYPE = "date_histogram";
@ -55,6 +57,7 @@ public class DateHistogramValuesSourceBuilder extends CompositeValuesSourceBuild
private static final ObjectParser<DateHistogramValuesSourceBuilder, Void> PARSER; private static final ObjectParser<DateHistogramValuesSourceBuilder, Void> PARSER;
static { static {
PARSER = new ObjectParser<>(DateHistogramValuesSourceBuilder.TYPE); PARSER = new ObjectParser<>(DateHistogramValuesSourceBuilder.TYPE);
PARSER.declareString(DateHistogramValuesSourceBuilder::format, new ParseField("format"));
PARSER.declareField((histogram, interval) -> { PARSER.declareField((histogram, interval) -> {
if (interval instanceof Long) { if (interval instanceof Long) {
histogram.interval((long) interval); histogram.interval((long) interval);
@ -235,7 +238,11 @@ public class DateHistogramValuesSourceBuilder extends CompositeValuesSourceBuild
canEarlyTerminate = checkCanEarlyTerminate(context.searcher().getIndexReader(), canEarlyTerminate = checkCanEarlyTerminate(context.searcher().getIndexReader(),
fieldContext.field(), order() == SortOrder.ASC ? false : true, sortField); fieldContext.field(), order() == SortOrder.ASC ? false : true, sortField);
} }
return new CompositeValuesSourceConfig(name, vs, order(), canEarlyTerminate); // dates are returned as timestamp in milliseconds-since-the-epoch unless a specific date format
// is specified in the builder.
final DocValueFormat docValueFormat = format() == null ? DocValueFormat.RAW : config.format();
return new CompositeValuesSourceConfig(name, vs, docValueFormat,
order(), canEarlyTerminate);
} else { } else {
throw new IllegalArgumentException("invalid source, expected numeric, got " + orig.getClass().getSimpleName()); throw new IllegalArgumentException("invalid source, expected numeric, got " + orig.getClass().getSimpleName());
} }

View File

@ -37,7 +37,7 @@ import java.io.IOException;
import java.util.Objects; import java.util.Objects;
/** /**
* A {@link CompositeValuesSourceBuilder} that that builds a {@link HistogramValuesSource} from another numeric values source * A {@link CompositeValuesSourceBuilder} that builds a {@link HistogramValuesSource} from another numeric values source
* using the provided interval. * using the provided interval.
*/ */
public class HistogramValuesSourceBuilder extends CompositeValuesSourceBuilder<HistogramValuesSourceBuilder> { public class HistogramValuesSourceBuilder extends CompositeValuesSourceBuilder<HistogramValuesSourceBuilder> {
@ -128,7 +128,7 @@ public class HistogramValuesSourceBuilder extends CompositeValuesSourceBuilder<H
canEarlyTerminate = checkCanEarlyTerminate(context.searcher().getIndexReader(), canEarlyTerminate = checkCanEarlyTerminate(context.searcher().getIndexReader(),
fieldContext.field(), order() == SortOrder.ASC ? false : true, sortField); fieldContext.field(), order() == SortOrder.ASC ? false : true, sortField);
} }
return new CompositeValuesSourceConfig(name, vs, order(), canEarlyTerminate); return new CompositeValuesSourceConfig(name, vs, config.format(), order(), canEarlyTerminate);
} else { } else {
throw new IllegalArgumentException("invalid source, expected numeric, got " + orig.getClass().getSimpleName()); throw new IllegalArgumentException("invalid source, expected numeric, got " + orig.getClass().getSimpleName());
} }

View File

@ -20,9 +20,11 @@
package org.elasticsearch.search.aggregations.bucket.composite; package org.elasticsearch.search.aggregations.bucket.composite;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.Version;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.InternalAggregation;
import org.elasticsearch.search.aggregations.InternalAggregations; import org.elasticsearch.search.aggregations.InternalAggregations;
@ -35,6 +37,7 @@ import java.util.AbstractMap;
import java.util.AbstractSet; import java.util.AbstractSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -49,11 +52,14 @@ public class InternalComposite
private final List<InternalBucket> buckets; private final List<InternalBucket> buckets;
private final int[] reverseMuls; private final int[] reverseMuls;
private final List<String> sourceNames; private final List<String> sourceNames;
private final List<DocValueFormat> formats;
InternalComposite(String name, int size, List<String> sourceNames, List<InternalBucket> buckets, int[] reverseMuls, InternalComposite(String name, int size, List<String> sourceNames, List<DocValueFormat> formats,
List<InternalBucket> buckets, int[] reverseMuls,
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) { List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
super(name, pipelineAggregators, metaData); super(name, pipelineAggregators, metaData);
this.sourceNames = sourceNames; this.sourceNames = sourceNames;
this.formats = formats;
this.buckets = buckets; this.buckets = buckets;
this.size = size; this.size = size;
this.reverseMuls = reverseMuls; this.reverseMuls = reverseMuls;
@ -63,14 +69,27 @@ public class InternalComposite
super(in); super(in);
this.size = in.readVInt(); this.size = in.readVInt();
this.sourceNames = in.readList(StreamInput::readString); this.sourceNames = in.readList(StreamInput::readString);
this.formats = new ArrayList<>(sourceNames.size());
for (int i = 0; i < sourceNames.size(); i++) {
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
formats.add(in.readNamedWriteable(DocValueFormat.class));
} else {
formats.add(DocValueFormat.RAW);
}
}
this.reverseMuls = in.readIntArray(); this.reverseMuls = in.readIntArray();
this.buckets = in.readList((input) -> new InternalBucket(input, sourceNames, reverseMuls)); this.buckets = in.readList((input) -> new InternalBucket(input, sourceNames, formats, reverseMuls));
} }
@Override @Override
protected void doWriteTo(StreamOutput out) throws IOException { protected void doWriteTo(StreamOutput out) throws IOException {
out.writeVInt(size); out.writeVInt(size);
out.writeStringList(sourceNames); out.writeStringList(sourceNames);
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
for (DocValueFormat format : formats) {
out.writeNamedWriteable(format);
}
}
out.writeIntArray(reverseMuls); out.writeIntArray(reverseMuls);
out.writeList(buckets); out.writeList(buckets);
} }
@ -87,12 +106,13 @@ public class InternalComposite
@Override @Override
public InternalComposite create(List<InternalBucket> buckets) { public InternalComposite create(List<InternalBucket> buckets) {
return new InternalComposite(name, size, sourceNames, buckets, reverseMuls, pipelineAggregators(), getMetaData()); return new InternalComposite(name, size, sourceNames, formats, buckets, reverseMuls, pipelineAggregators(), getMetaData());
} }
@Override @Override
public InternalBucket createBucket(InternalAggregations aggregations, InternalBucket prototype) { public InternalBucket createBucket(InternalAggregations aggregations, InternalBucket prototype) {
return new InternalBucket(prototype.sourceNames, prototype.key, prototype.reverseMuls, prototype.docCount, aggregations); return new InternalBucket(prototype.sourceNames, prototype.formats, prototype.key, prototype.reverseMuls,
prototype.docCount, aggregations);
} }
public int getSize() { public int getSize() {
@ -149,7 +169,7 @@ public class InternalComposite
reduceContext.consumeBucketsAndMaybeBreak(1); reduceContext.consumeBucketsAndMaybeBreak(1);
result.add(reduceBucket); result.add(reduceBucket);
} }
return new InternalComposite(name, size, sourceNames, result, reverseMuls, pipelineAggregators(), metaData); return new InternalComposite(name, size, sourceNames, formats, result, reverseMuls, pipelineAggregators(), metaData);
} }
@Override @Override
@ -191,18 +211,21 @@ public class InternalComposite
private final InternalAggregations aggregations; private final InternalAggregations aggregations;
private final transient int[] reverseMuls; private final transient int[] reverseMuls;
private final transient List<String> sourceNames; private final transient List<String> sourceNames;
private final transient List<DocValueFormat> formats;
InternalBucket(List<String> sourceNames, CompositeKey key, int[] reverseMuls, long docCount, InternalAggregations aggregations) { InternalBucket(List<String> sourceNames, List<DocValueFormat> formats, CompositeKey key, int[] reverseMuls, long docCount,
InternalAggregations aggregations) {
this.key = key; this.key = key;
this.docCount = docCount; this.docCount = docCount;
this.aggregations = aggregations; this.aggregations = aggregations;
this.reverseMuls = reverseMuls; this.reverseMuls = reverseMuls;
this.sourceNames = sourceNames; this.sourceNames = sourceNames;
this.formats = formats;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
InternalBucket(StreamInput in, List<String> sourceNames, int[] reverseMuls) throws IOException { InternalBucket(StreamInput in, List<String> sourceNames, List<DocValueFormat> formats, int[] reverseMuls) throws IOException {
final Comparable<?>[] values = new Comparable<?>[in.readVInt()]; final Comparable<?>[] values = new Comparable<?>[in.readVInt()];
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
values[i] = (Comparable<?>) in.readGenericValue(); values[i] = (Comparable<?>) in.readGenericValue();
@ -212,6 +235,7 @@ public class InternalComposite
this.aggregations = InternalAggregations.readAggregations(in); this.aggregations = InternalAggregations.readAggregations(in);
this.reverseMuls = reverseMuls; this.reverseMuls = reverseMuls;
this.sourceNames = sourceNames; this.sourceNames = sourceNames;
this.formats = formats;
} }
@Override @Override
@ -242,9 +266,11 @@ public class InternalComposite
@Override @Override
public Map<String, Object> getKey() { public Map<String, Object> getKey() {
return new ArrayMap(sourceNames, key.values()); // returns the formatted key in a map
return new ArrayMap(sourceNames, formats, key.values());
} }
// get the raw key (without formatting to preserve the natural order).
// visible for testing // visible for testing
CompositeKey getRawKey() { CompositeKey getRawKey() {
return key; return key;
@ -260,7 +286,7 @@ public class InternalComposite
} }
builder.append(sourceNames.get(i)); builder.append(sourceNames.get(i));
builder.append('='); builder.append('=');
builder.append(formatObject(key.get(i))); builder.append(formatObject(key.get(i), formats.get(i)));
} }
builder.append('}'); builder.append('}');
return builder.toString(); return builder.toString();
@ -284,7 +310,7 @@ public class InternalComposite
aggregations.add(bucket.aggregations); aggregations.add(bucket.aggregations);
} }
InternalAggregations aggs = InternalAggregations.reduce(aggregations, reduceContext); InternalAggregations aggs = InternalAggregations.reduce(aggregations, reduceContext);
return new InternalBucket(sourceNames, key, reverseMuls, docCount, aggs); return new InternalBucket(sourceNames, formats, key, reverseMuls, docCount, aggs);
} }
@Override @Override
@ -303,26 +329,52 @@ public class InternalComposite
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
/** /**
* See {@link CompositeAggregation#bucketToXContentFragment} * See {@link CompositeAggregation#bucketToXContent}
*/ */
throw new UnsupportedOperationException("not implemented"); throw new UnsupportedOperationException("not implemented");
} }
} }
static Object formatObject(Object obj) { /**
if (obj instanceof BytesRef) { * Format <code>obj</code> using the provided {@link DocValueFormat}.
return ((BytesRef) obj).utf8ToString(); * If the format is equals to {@link DocValueFormat#RAW}, the object is returned as is
* for numbers and a string for {@link BytesRef}s.
*/
static Object formatObject(Object obj, DocValueFormat format) {
if (obj.getClass() == BytesRef.class) {
BytesRef value = (BytesRef) obj;
if (format == DocValueFormat.RAW) {
return value.utf8ToString();
} else {
return format.format((BytesRef) obj);
}
} else if (obj.getClass() == Long.class) {
Long value = (Long) obj;
if (format == DocValueFormat.RAW) {
return value;
} else {
return format.format(value);
}
} else if (obj.getClass() == Double.class) {
Double value = (Double) obj;
if (format == DocValueFormat.RAW) {
return value;
} else {
return format.format((Double) obj);
}
} }
return obj; return obj;
} }
private static class ArrayMap extends AbstractMap<String, Object> { private static class ArrayMap extends AbstractMap<String, Object> {
final List<String> keys; final List<String> keys;
final List<DocValueFormat> formats;
final Object[] values; final Object[] values;
ArrayMap(List<String> keys, Object[] values) { ArrayMap(List<String> keys, List<DocValueFormat> formats, Object[] values) {
assert keys.size() == values.length; assert keys.size() == values.length && keys.size() == formats.size();
this.keys = keys; this.keys = keys;
this.formats = formats;
this.values = values; this.values = values;
} }
@ -335,7 +387,7 @@ public class InternalComposite
public Object get(Object key) { public Object get(Object key) {
for (int i = 0; i < keys.size(); i++) { for (int i = 0; i < keys.size(); i++) {
if (key.equals(keys.get(i))) { if (key.equals(keys.get(i))) {
return formatObject(values[i]); return formatObject(values[i], formats.get(i));
} }
} }
return null; return null;
@ -356,7 +408,7 @@ public class InternalComposite
@Override @Override
public Entry<String, Object> next() { public Entry<String, Object> next() {
SimpleEntry<String, Object> entry = SimpleEntry<String, Object> entry =
new SimpleEntry<>(keys.get(pos), formatObject(values[pos])); new SimpleEntry<>(keys.get(pos), formatObject(values[pos], formats.get(pos)));
++ pos; ++ pos;
return entry; return entry;
} }

View File

@ -95,6 +95,6 @@ public class TermsValuesSourceBuilder extends CompositeValuesSourceBuilder<Terms
canEarlyTerminate = checkCanEarlyTerminate(context.searcher().getIndexReader(), canEarlyTerminate = checkCanEarlyTerminate(context.searcher().getIndexReader(),
fieldContext.field(), order() == SortOrder.ASC ? false : true, sortField); fieldContext.field(), order() == SortOrder.ASC ? false : true, sortField);
} }
return new CompositeValuesSourceConfig(name, vs, order(), canEarlyTerminate); return new CompositeValuesSourceConfig(name, vs, config.format(), order(), canEarlyTerminate);
} }
} }

View File

@ -39,6 +39,7 @@ import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.NumericUtils; import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.TestUtil; import org.apache.lucene.util.TestUtil;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexSettings;
@ -68,6 +69,9 @@ import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
public class CompositeAggregatorTests extends AggregatorTestCase { public class CompositeAggregatorTests extends AggregatorTestCase {
private static MappedFieldType[] FIELD_TYPES; private static MappedFieldType[] FIELD_TYPES;
@ -761,6 +765,89 @@ public class CompositeAggregatorTests extends AggregatorTestCase {
); );
} }
public void testWithDateHistogramAndFormat() throws IOException {
final List<Map<String, List<Object>>> dataset = new ArrayList<>();
dataset.addAll(
Arrays.asList(
createDocument("date", asLong("2017-10-20T03:08:45")),
createDocument("date", asLong("2016-09-20T09:00:34")),
createDocument("date", asLong("2016-09-20T11:34:00")),
createDocument("date", asLong("2017-10-20T06:09:24")),
createDocument("date", asLong("2017-10-19T06:09:24")),
createDocument("long", 4L)
)
);
final Sort sort = new Sort(new SortedNumericSortField("date", SortField.Type.LONG));
testSearchCase(new MatchAllDocsQuery(), sort, dataset,
() -> {
DateHistogramValuesSourceBuilder histo = new DateHistogramValuesSourceBuilder("date")
.field("date")
.dateHistogramInterval(DateHistogramInterval.days(1))
.format("yyyy-MM-dd");
return new CompositeAggregationBuilder("name", Collections.singletonList(histo));
},
(result) -> {
assertEquals(3, result.getBuckets().size());
assertEquals("{date=2016-09-20}", result.getBuckets().get(0).getKeyAsString());
assertEquals(2L, result.getBuckets().get(0).getDocCount());
assertEquals("{date=2017-10-19}", result.getBuckets().get(1).getKeyAsString());
assertEquals(1L, result.getBuckets().get(1).getDocCount());
assertEquals("{date=2017-10-20}", result.getBuckets().get(2).getKeyAsString());
assertEquals(2L, result.getBuckets().get(2).getDocCount());
}
);
testSearchCase(new MatchAllDocsQuery(), sort, dataset,
() -> {
DateHistogramValuesSourceBuilder histo = new DateHistogramValuesSourceBuilder("date")
.field("date")
.dateHistogramInterval(DateHistogramInterval.days(1))
.format("yyyy-MM-dd");
return new CompositeAggregationBuilder("name", Collections.singletonList(histo))
.aggregateAfter(createAfterKey("date", "2016-09-20"));
}, (result) -> {
assertEquals(2, result.getBuckets().size());
assertEquals("{date=2017-10-19}", result.getBuckets().get(0).getKeyAsString());
assertEquals(1L, result.getBuckets().get(0).getDocCount());
assertEquals("{date=2017-10-20}", result.getBuckets().get(1).getKeyAsString());
assertEquals(2L, result.getBuckets().get(1).getDocCount());
}
);
}
public void testThatDateHistogramFailsFormatAfter() throws IOException {
ElasticsearchParseException exc = expectThrows(ElasticsearchParseException.class,
() -> testSearchCase(new MatchAllDocsQuery(), null, Collections.emptyList(),
() -> {
DateHistogramValuesSourceBuilder histo = new DateHistogramValuesSourceBuilder("date")
.field("date")
.dateHistogramInterval(DateHistogramInterval.days(1))
.format("yyyy-MM-dd");
return new CompositeAggregationBuilder("name", Collections.singletonList(histo))
.aggregateAfter(createAfterKey("date", "now"));
},
(result) -> {}
));
assertThat(exc.getCause(), instanceOf(IllegalArgumentException.class));
assertThat(exc.getCause().getMessage(), containsString("now() is not supported in [after] key"));
exc = expectThrows(ElasticsearchParseException.class,
() -> testSearchCase(new MatchAllDocsQuery(), null, Collections.emptyList(),
() -> {
DateHistogramValuesSourceBuilder histo = new DateHistogramValuesSourceBuilder("date")
.field("date")
.dateHistogramInterval(DateHistogramInterval.days(1))
.format("yyyy-MM-dd");
return new CompositeAggregationBuilder("name", Collections.singletonList(histo))
.aggregateAfter(createAfterKey("date", "1474329600000"));
},
(result) -> {}
));
assertThat(exc.getCause(), instanceOf(IllegalArgumentException.class));
assertThat(exc.getCause().getMessage(), containsString("Parse failure"));
}
public void testWithDateHistogramAndTimeZone() throws IOException { public void testWithDateHistogramAndTimeZone() throws IOException {
final List<Map<String, List<Object>>> dataset = new ArrayList<>(); final List<Map<String, List<Object>>> dataset = new ArrayList<>();
dataset.addAll( dataset.addAll(

View File

@ -21,12 +21,15 @@ package org.elasticsearch.search.aggregations.bucket.composite;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.InternalAggregation;
import org.elasticsearch.search.aggregations.InternalAggregations; import org.elasticsearch.search.aggregations.InternalAggregations;
import org.elasticsearch.search.aggregations.ParsedAggregation; import org.elasticsearch.search.aggregations.ParsedAggregation;
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
import org.elasticsearch.test.InternalMultiBucketAggregationTestCase; import org.elasticsearch.test.InternalMultiBucketAggregationTestCase;
import org.joda.time.DateTimeZone;
import org.junit.After; import org.junit.After;
import java.io.IOException; import java.io.IOException;
@ -41,28 +44,45 @@ import java.util.TreeSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomAsciiLettersOfLengthBetween; import static com.carrotsearch.randomizedtesting.RandomizedTest.randomAsciiLettersOfLengthBetween;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomLongBetween;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.Matchers.lessThanOrEqualTo;
public class InternalCompositeTests extends InternalMultiBucketAggregationTestCase<InternalComposite> { public class InternalCompositeTests extends InternalMultiBucketAggregationTestCase<InternalComposite> {
private List<String> sourceNames; private List<String> sourceNames;
private List<DocValueFormat> formats;
private int[] reverseMuls; private int[] reverseMuls;
private int[] formats; private int[] types;
private int size; private int size;
private static DocValueFormat randomDocValueFormat(boolean isLong) {
if (isLong) {
// we use specific format only for date histogram on a long/date field
if (randomBoolean()) {
return new DocValueFormat.DateTime(Joda.forPattern("epoch_second"), DateTimeZone.forOffsetHours(1));
} else {
return DocValueFormat.RAW;
}
} else {
// and the raw format for the other types
return DocValueFormat.RAW;
}
}
@Override @Override
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
int numFields = randomIntBetween(1, 10); int numFields = randomIntBetween(1, 10);
size = randomNumberOfBuckets(); size = randomNumberOfBuckets();
sourceNames = new ArrayList<>(); sourceNames = new ArrayList<>();
formats = new ArrayList<>();
reverseMuls = new int[numFields]; reverseMuls = new int[numFields];
formats = new int[numFields]; types = new int[numFields];
for (int i = 0; i < numFields; i++) { for (int i = 0; i < numFields; i++) {
sourceNames.add("field_" + i); sourceNames.add("field_" + i);
reverseMuls[i] = randomBoolean() ? 1 : -1; reverseMuls[i] = randomBoolean() ? 1 : -1;
formats[i] = randomIntBetween(0, 2); int type = randomIntBetween(0, 2);
types[i] = type;
formats.add(randomDocValueFormat(type == 0));
} }
} }
@ -70,9 +90,10 @@ public class InternalCompositeTests extends InternalMultiBucketAggregationTestCa
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
super.tearDown(); super.tearDown();
sourceNames= null; sourceNames = null;
reverseMuls = null;
formats = null; formats = null;
reverseMuls = null;
types = null;
} }
@Override @Override
@ -93,7 +114,7 @@ public class InternalCompositeTests extends InternalMultiBucketAggregationTestCa
private CompositeKey createCompositeKey() { private CompositeKey createCompositeKey() {
Comparable<?>[] keys = new Comparable<?>[sourceNames.size()]; Comparable<?>[] keys = new Comparable<?>[sourceNames.size()];
for (int j = 0; j < keys.length; j++) { for (int j = 0; j < keys.length; j++) {
switch (formats[j]) { switch (types[j]) {
case 0: case 0:
keys[j] = randomLong(); keys[j] = randomLong();
break; break;
@ -123,19 +144,6 @@ public class InternalCompositeTests extends InternalMultiBucketAggregationTestCa
}; };
} }
@SuppressWarnings("unchecked")
private Comparator<InternalComposite.InternalBucket> getBucketComparator() {
return (o1, o2) -> {
for (int i = 0; i < o1.getRawKey().size(); i++) {
int cmp = ((Comparable) o1.getRawKey().get(i)).compareTo(o2.getRawKey().get(i)) * reverseMuls[i];
if (cmp != 0) {
return cmp;
}
}
return 0;
};
}
@Override @Override
protected InternalComposite createTestInstance(String name, List<PipelineAggregator> pipelineAggregators, protected InternalComposite createTestInstance(String name, List<PipelineAggregator> pipelineAggregators,
Map<String, Object> metaData, InternalAggregations aggregations) { Map<String, Object> metaData, InternalAggregations aggregations) {
@ -149,11 +157,11 @@ public class InternalCompositeTests extends InternalMultiBucketAggregationTestCa
} }
keys.add(key); keys.add(key);
InternalComposite.InternalBucket bucket = InternalComposite.InternalBucket bucket =
new InternalComposite.InternalBucket(sourceNames, key, reverseMuls, 1L, aggregations); new InternalComposite.InternalBucket(sourceNames, formats, key, reverseMuls, 1L, aggregations);
buckets.add(bucket); buckets.add(bucket);
} }
Collections.sort(buckets, (o1, o2) -> o1.compareKey(o2)); Collections.sort(buckets, (o1, o2) -> o1.compareKey(o2));
return new InternalComposite(name, size, sourceNames, buckets, reverseMuls, Collections.emptyList(), metaData); return new InternalComposite(name, size, sourceNames, formats, buckets, reverseMuls, Collections.emptyList(), metaData);
} }
@Override @Override
@ -172,7 +180,7 @@ public class InternalCompositeTests extends InternalMultiBucketAggregationTestCa
break; break;
case 1: case 1:
buckets = new ArrayList<>(buckets); buckets = new ArrayList<>(buckets);
buckets.add(new InternalComposite.InternalBucket(sourceNames, createCompositeKey(), reverseMuls, buckets.add(new InternalComposite.InternalBucket(sourceNames, formats, createCompositeKey(), reverseMuls,
randomLongBetween(1, 100), InternalAggregations.EMPTY) randomLongBetween(1, 100), InternalAggregations.EMPTY)
); );
break; break;
@ -187,7 +195,7 @@ public class InternalCompositeTests extends InternalMultiBucketAggregationTestCa
default: default:
throw new AssertionError("illegal branch"); throw new AssertionError("illegal branch");
} }
return new InternalComposite(instance.getName(), instance.getSize(), sourceNames, buckets, reverseMuls, return new InternalComposite(instance.getName(), instance.getSize(), sourceNames, formats, buckets, reverseMuls,
instance.pipelineAggregators(), metaData); instance.pipelineAggregators(), metaData);
} }