diff --git a/extensions-core/datasketches/pom.xml b/extensions-core/datasketches/pom.xml
index c037f8317456..f16fa37ec795 100644
--- a/extensions-core/datasketches/pom.xml
+++ b/extensions-core/datasketches/pom.xml
@@ -38,7 +38,7 @@
com.yahoo.datasketchessketches-core
- 0.10.1
+ 0.10.3io.druid
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchAggregatorFactory.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchAggregatorFactory.java
new file mode 100644
index 000000000000..7ebb40b5d7a5
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchAggregatorFactory.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.metamx.common.IAE;
+import com.yahoo.sketches.Util;
+import com.yahoo.sketches.quantiles.DoublesSketch;
+import com.yahoo.sketches.quantiles.DoublesUnion;
+
+import io.druid.query.aggregation.Aggregator;
+import io.druid.query.aggregation.AggregatorFactory;
+import io.druid.query.aggregation.AggregatorFactoryNotMergeableException;
+import io.druid.query.aggregation.AggregatorUtil;
+import io.druid.query.aggregation.BufferAggregator;
+import io.druid.query.cache.CacheKeyBuilder;
+import io.druid.segment.ColumnSelectorFactory;
+import io.druid.segment.ColumnValueSelector;
+import io.druid.segment.NilColumnValueSelector;
+import io.druid.segment.column.ValueType;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+public class DoublesSketchAggregatorFactory extends AggregatorFactory
+{
+
+ private static final int DEFAULT_K = 128;
+
+ // Used for sketch size estimation.
+ private static final long MAX_STREAM_LENGTH = 1_000_000_000;
+
+ private final String name;
+ private final String fieldName;
+ private final int k;
+ private final byte cacheTypeId;
+
+ @JsonCreator
+ public DoublesSketchAggregatorFactory(
+ @JsonProperty("name") final String name,
+ @JsonProperty("fieldName") final String fieldName,
+ @JsonProperty("k") final Integer k)
+ {
+ this(name, fieldName, k, AggregatorUtil.QUANTILES_DOUBLES_SKETCH_BUILD_CACHE_TYPE_ID);
+ }
+
+ DoublesSketchAggregatorFactory(final String name, final String fieldName, final Integer k, final byte cacheTypeId)
+ {
+ if (name == null) {
+ throw new IAE("Must have a valid, non-null aggregator name");
+ }
+ this.name = name;
+ if (fieldName == null) {
+ throw new IAE("Parameter fieldName must be specified");
+ }
+ this.fieldName = fieldName;
+ this.k = k == null ? DEFAULT_K : k;
+ Util.checkIfPowerOf2(this.k, "k");
+ this.cacheTypeId = cacheTypeId;
+ }
+
+ @Override
+ public Aggregator factorize(final ColumnSelectorFactory metricFactory)
+ {
+ if (metricFactory.getColumnCapabilities(fieldName) != null
+ && ValueType.isNumeric(metricFactory.getColumnCapabilities(fieldName).getType())) {
+ final ColumnValueSelector selector = metricFactory.makeColumnValueSelector(fieldName);
+ if (selector instanceof NilColumnValueSelector) {
+ return new DoublesSketchNoOpAggregator();
+ }
+ return new DoublesSketchBuildAggregator(selector, k);
+ }
+ final ColumnValueSelector selector = metricFactory.makeColumnValueSelector(fieldName);
+ if (selector instanceof NilColumnValueSelector) {
+ return new DoublesSketchNoOpAggregator();
+ }
+ return new DoublesSketchMergeAggregator(selector, k);
+ }
+
+ @Override
+ public BufferAggregator factorizeBuffered(final ColumnSelectorFactory metricFactory)
+ {
+ if (metricFactory.getColumnCapabilities(fieldName) != null
+ && ValueType.isNumeric(metricFactory.getColumnCapabilities(fieldName).getType())) {
+ final ColumnValueSelector selector = metricFactory.makeColumnValueSelector(fieldName);
+ if (selector instanceof NilColumnValueSelector) {
+ return new DoublesSketchNoOpBufferAggregator();
+ }
+ return new DoublesSketchBuildBufferAggregator(selector, k, getMaxIntermediateSize());
+ }
+ final ColumnValueSelector selector = metricFactory.makeColumnValueSelector(fieldName);
+ if (selector instanceof NilColumnValueSelector) {
+ return new DoublesSketchNoOpBufferAggregator();
+ }
+ return new DoublesSketchMergeBufferAggregator(selector, k, getMaxIntermediateSize());
+ }
+
+ @Override
+ public Object deserialize(final Object object)
+ {
+ return DoublesSketchOperations.deserialize(object);
+ }
+
+ public static final Comparator COMPARATOR = new Comparator()
+ {
+ @Override
+ public int compare(DoublesSketch a, DoublesSketch b)
+ {
+ return Long.compare(a.getN(), b.getN());
+ }
+ };
+
+ @Override
+ public Comparator getComparator()
+ {
+ return COMPARATOR;
+ }
+
+ @Override
+ public Object combine(final Object lhs, final Object rhs)
+ {
+ final DoublesUnion union = DoublesUnion.builder().setMaxK(k).build();
+ union.update((DoublesSketch) lhs);
+ union.update((DoublesSketch) rhs);
+ return union.getResultAndReset();
+ }
+
+ @Override
+ @JsonProperty
+ public String getName()
+ {
+ return name;
+ }
+
+ @JsonProperty
+ public String getFieldName()
+ {
+ return fieldName;
+ }
+
+ @JsonProperty
+ public int getK()
+ {
+ return k;
+ }
+
+ @Override
+ public List requiredFields()
+ {
+ return Collections.singletonList(fieldName);
+ }
+
+ // Quantiles sketches never stop growing, but they do so very slowly.
+ // This size must suffice for overwhelming majority of sketches,
+ // but some sketches may request more memory on heap and move there
+ @Override
+ public int getMaxIntermediateSize()
+ {
+ return DoublesSketch.getUpdatableStorageBytes(k, MAX_STREAM_LENGTH);
+ }
+
+ @Override
+ public List getRequiredColumns()
+ {
+ return Collections. singletonList(
+ new DoublesSketchAggregatorFactory(
+ fieldName,
+ fieldName,
+ k)
+ );
+ }
+
+ @Override
+ public AggregatorFactory getCombiningFactory()
+ {
+ return new DoublesSketchMergeAggregatorFactory(name, k);
+ }
+
+ @Override
+ public AggregatorFactory getMergingFactory(AggregatorFactory other) throws AggregatorFactoryNotMergeableException
+ {
+ if (other.getName().equals(this.getName()) && other instanceof DoublesSketchAggregatorFactory) {
+ // DoublesUnion supports inputs with different k.
+ // The result will have effective k between the specified k and the minimum k from all input sketches
+ // to achieve higher accuracy as much as possible.
+ return new DoublesSketchMergeAggregatorFactory(name, Math.max(k, ((DoublesSketchAggregatorFactory) other).k));
+ } else {
+ throw new AggregatorFactoryNotMergeableException(this, other);
+ }
+ }
+
+ @Override
+ public Object finalizeComputation(final Object object)
+ {
+ return ((DoublesSketch) object).getN();
+ }
+
+ @Override
+ public String getTypeName()
+ {
+ return DoublesSketchModule.DOUBLES_SKETCH;
+ }
+
+ @Override
+ public byte[] getCacheKey()
+ {
+ return new CacheKeyBuilder(cacheTypeId).appendString(name).appendString(fieldName).appendInt(k).build();
+ }
+
+ @Override
+ public boolean equals(final Object o)
+ {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || !getClass().equals(o.getClass())) {
+ return false;
+ }
+ final DoublesSketchAggregatorFactory that = (DoublesSketchAggregatorFactory) o;
+ if (!name.equals(that.name)) {
+ return false;
+ }
+ if (!fieldName.equals(that.fieldName)) {
+ return false;
+ }
+ if (k != that.k) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(name, fieldName, k); // no need to use cacheTypeId here
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getSimpleName() + "{"
+ + "name=" + name
+ + ", fieldName=" + fieldName
+ + ", k=" + k
+ + "}";
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchBuildAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchBuildAggregator.java
new file mode 100644
index 000000000000..f957f5bb0ce1
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchBuildAggregator.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import com.yahoo.sketches.quantiles.UpdateDoublesSketch;
+
+import io.druid.query.aggregation.Aggregator;
+import io.druid.segment.ColumnValueSelector;
+
+public class DoublesSketchBuildAggregator implements Aggregator
+{
+
+ private final ColumnValueSelector valueSelector;
+ private final int size;
+
+ private UpdateDoublesSketch sketch;
+
+ public DoublesSketchBuildAggregator(final ColumnValueSelector valueSelector, final int size)
+ {
+ this.valueSelector = valueSelector;
+ this.size = size;
+ sketch = UpdateDoublesSketch.builder().setK(size).build();
+ }
+
+ @Override
+ public synchronized void aggregate()
+ {
+ sketch.update(valueSelector.getDouble());
+ }
+
+ @Override
+ public synchronized Object get()
+ {
+ return sketch;
+ }
+
+ @Override
+ public float getFloat()
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public long getLong()
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public synchronized void reset()
+ {
+ sketch = UpdateDoublesSketch.builder().setK(size).build();
+ }
+
+ @Override
+ public synchronized void close()
+ {
+ sketch = null;
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchBuildBufferAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchBuildBufferAggregator.java
new file mode 100644
index 000000000000..1318c11a725d
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchBuildBufferAggregator.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import java.nio.ByteBuffer;
+import java.util.IdentityHashMap;
+
+import com.yahoo.memory.WritableMemory;
+import com.yahoo.sketches.quantiles.UpdateDoublesSketch;
+
+import io.druid.query.aggregation.BufferAggregator;
+import io.druid.query.monomorphicprocessing.RuntimeShapeInspector;
+import io.druid.segment.ColumnValueSelector;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+public class DoublesSketchBuildBufferAggregator implements BufferAggregator
+{
+
+ private final ColumnValueSelector selector;
+ private final int size;
+ private final int maxIntermediateSize;
+
+ private final IdentityHashMap memCache = new IdentityHashMap<>();
+ private final IdentityHashMap> sketches = new IdentityHashMap<>();
+
+ public DoublesSketchBuildBufferAggregator(final ColumnValueSelector valueSelector, final int size,
+ final int maxIntermediateSize)
+ {
+ this.selector = valueSelector;
+ this.size = size;
+ this.maxIntermediateSize = maxIntermediateSize;
+ }
+
+ @Override
+ public synchronized void init(final ByteBuffer buffer, final int position)
+ {
+ final WritableMemory mem = getMemory(buffer);
+ final WritableMemory region = mem.writableRegion(position, maxIntermediateSize);
+ final UpdateDoublesSketch sketch = UpdateDoublesSketch.builder().setK(size).build(region);
+ putSketch(buffer, position, sketch);
+ }
+
+ @Override
+ public synchronized void aggregate(final ByteBuffer buffer, final int position)
+ {
+ final UpdateDoublesSketch sketch = sketches.get(buffer).get(position);
+ sketch.update(selector.getDouble());
+ }
+
+ @Override
+ public synchronized Object get(final ByteBuffer buffer, final int position)
+ {
+ return sketches.get(buffer).get(position);
+ }
+
+ @Override
+ public float getFloat(final ByteBuffer buffer, final int position)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public long getLong(final ByteBuffer buffer, final int position)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public synchronized void close()
+ {
+ sketches.clear();
+ memCache.clear();
+ }
+
+ // A small number of sketches may run out of the given memory, request more memory on heap and move there.
+ // In that case we need to reuse the object from the cache as opposed to wrapping the new buffer.
+ @Override
+ public synchronized void relocate(int oldPosition, int newPosition, ByteBuffer oldBuffer, ByteBuffer newBuffer)
+ {
+ UpdateDoublesSketch sketch = sketches.get(oldBuffer).get(oldPosition);
+ final WritableMemory oldRegion = getMemory(oldBuffer).writableRegion(oldPosition, maxIntermediateSize);
+ if (sketch.isSameResource(oldRegion)) { // sketch was not relocated on heap
+ final WritableMemory newRegion = getMemory(newBuffer).writableRegion(newPosition, maxIntermediateSize);
+ sketch = UpdateDoublesSketch.wrap(newRegion);
+ }
+ putSketch(newBuffer, newPosition, sketch);
+
+ final Int2ObjectMap map = sketches.get(oldBuffer);
+ map.remove(oldPosition);
+ if (map.isEmpty()) {
+ sketches.remove(oldBuffer);
+ memCache.remove(oldBuffer);
+ }
+ }
+
+ private WritableMemory getMemory(final ByteBuffer buffer)
+ {
+ return memCache.computeIfAbsent(buffer, buf -> WritableMemory.wrap(buf));
+ }
+
+ private void putSketch(final ByteBuffer buffer, final int position, final UpdateDoublesSketch sketch)
+ {
+ Int2ObjectMap map = sketches.computeIfAbsent(buffer, buf -> new Int2ObjectOpenHashMap<>());
+ map.put(position, sketch);
+ }
+
+ @Override
+ public void inspectRuntimeShape(final RuntimeShapeInspector inspector)
+ {
+ inspector.visit("selector", selector);
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchComplexMetricSerde.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchComplexMetricSerde.java
new file mode 100644
index 000000000000..fa35700c6030
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchComplexMetricSerde.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.quantiles.DoublesSketch;
+import com.yahoo.sketches.quantiles.UpdateDoublesSketch;
+import io.druid.data.input.InputRow;
+import io.druid.java.util.common.IAE;
+import io.druid.segment.GenericColumnSerializer;
+import io.druid.segment.column.ColumnBuilder;
+import io.druid.segment.data.GenericIndexed;
+import io.druid.segment.data.IOPeon;
+import io.druid.segment.data.ObjectStrategy;
+import io.druid.segment.serde.ComplexColumnPartSupplier;
+import io.druid.segment.serde.ComplexMetricExtractor;
+import io.druid.segment.serde.ComplexMetricSerde;
+import io.druid.segment.serde.LargeColumnSupportedComplexColumnSerializer;
+
+import java.nio.ByteBuffer;
+
+public class DoublesSketchComplexMetricSerde extends ComplexMetricSerde
+{
+
+ private static final DoublesSketchObjectStrategy strategy = new DoublesSketchObjectStrategy();
+
+ @Override
+ public String getTypeName()
+ {
+ return DoublesSketchModule.DOUBLES_SKETCH;
+ }
+
+ @Override
+ public ObjectStrategy getObjectStrategy()
+ {
+ return strategy;
+ }
+
+ @Override
+ public ComplexMetricExtractor getExtractor()
+ {
+ return new ComplexMetricExtractor()
+ {
+ private static final int MIN_K = 2; // package one input value into the smallest sketch
+
+ @Override
+ public Class> extractedClass()
+ {
+ return DoublesSketch.class;
+ }
+
+ @Override
+ public Object extractValue(final InputRow inputRow, final String metricName)
+ {
+ final Object object = inputRow.getRaw(metricName);
+ if (object instanceof String) { // everything is a string during ingestion
+ String objectString = (String) object;
+ // Autodetection of the input format: a number or base64 encoded sketch
+ // A serialized DoublesSketch, as currently implemented, always has 0 in the first 6 bits.
+ // This corresponds to "A" in base64, so it is not a digit
+ if (Character.isDigit((objectString).charAt(0))) {
+ try {
+ Double doubleValue = Double.parseDouble(objectString);
+ UpdateDoublesSketch sketch = DoublesSketch.builder().setK(MIN_K).build();
+ sketch.update(doubleValue);
+ return sketch;
+ }
+ catch (NumberFormatException e) {
+ throw new IAE("Expected a string with a number, received value " + objectString);
+ }
+ }
+ } else if (object instanceof Number) { // this is for reindexing
+ UpdateDoublesSketch sketch = DoublesSketch.builder().setK(MIN_K).build();
+ sketch.update(((Number) object).doubleValue());
+ return sketch;
+ }
+
+ if (object == null || object instanceof DoublesSketch || object instanceof Memory) {
+ return object;
+ }
+ return DoublesSketchOperations.deserialize(object);
+ }
+ };
+ }
+
+ @Override
+ public void deserializeColumn(final ByteBuffer buffer, final ColumnBuilder builder)
+ {
+ final GenericIndexed column = GenericIndexed.read(buffer, strategy, builder.getFileMapper());
+ builder.setComplexColumn(new ComplexColumnPartSupplier(getTypeName(), column));
+ }
+
+ // support large columns
+ @Override
+ public GenericColumnSerializer getSerializer(IOPeon peon, String column)
+ {
+ return LargeColumnSupportedComplexColumnSerializer.create(peon, column, this.getObjectStrategy());
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchJsonSerializer.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchJsonSerializer.java
new file mode 100644
index 000000000000..b2b51640a833
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchJsonSerializer.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.yahoo.sketches.quantiles.DoublesSketch;
+
+public class DoublesSketchJsonSerializer extends JsonSerializer
+{
+
+ @Override
+ public void serialize(final DoublesSketch sketch, final JsonGenerator generator, final SerializerProvider provider)
+ throws IOException
+ {
+ generator.writeBinary(sketch.toByteArray(true));
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchMergeAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchMergeAggregator.java
new file mode 100644
index 000000000000..6152ff417a34
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchMergeAggregator.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import com.yahoo.sketches.quantiles.DoublesSketch;
+import com.yahoo.sketches.quantiles.DoublesUnion;
+
+import io.druid.query.aggregation.Aggregator;
+import io.druid.segment.ColumnValueSelector;
+
+public class DoublesSketchMergeAggregator implements Aggregator
+{
+
+ private final ColumnValueSelector selector;
+ private DoublesUnion union;
+
+ public DoublesSketchMergeAggregator(final ColumnValueSelector selector, final int k)
+ {
+ this.selector = selector;
+ union = DoublesUnion.builder().setMaxK(k).build();
+ }
+
+ @Override
+ public synchronized void aggregate()
+ {
+ final DoublesSketch sketch = selector.getObject();
+ if (sketch == null) {
+ return;
+ }
+ union.update(sketch);
+ }
+
+ @Override
+ public synchronized Object get()
+ {
+ return union.getResult();
+ }
+
+ @Override
+ public float getFloat()
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public long getLong()
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public synchronized void reset()
+ {
+ union.reset();
+ }
+
+ @Override
+ public synchronized void close()
+ {
+ union = null;
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchMergeAggregatorFactory.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchMergeAggregatorFactory.java
new file mode 100644
index 000000000000..5c3d7b61dcbb
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchMergeAggregatorFactory.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.sketches.quantiles.DoublesSketch;
+import io.druid.query.aggregation.Aggregator;
+import io.druid.query.aggregation.AggregatorUtil;
+import io.druid.query.aggregation.BufferAggregator;
+import io.druid.segment.ColumnSelectorFactory;
+import io.druid.segment.ColumnValueSelector;
+import io.druid.segment.NilColumnValueSelector;
+
+public class DoublesSketchMergeAggregatorFactory extends DoublesSketchAggregatorFactory
+{
+
+ @JsonCreator
+ public DoublesSketchMergeAggregatorFactory(
+ @JsonProperty("name") final String name,
+ @JsonProperty("k") final Integer k)
+ {
+ super(name, name, k, AggregatorUtil.QUANTILES_DOUBLES_SKETCH_MERGE_CACHE_TYPE_ID);
+ }
+
+ @Override
+ public Aggregator factorize(final ColumnSelectorFactory metricFactory)
+ {
+ final ColumnValueSelector selector = metricFactory.makeColumnValueSelector(getFieldName());
+ if (selector instanceof NilColumnValueSelector) {
+ return new DoublesSketchNoOpAggregator();
+ }
+ return new DoublesSketchMergeAggregator(selector, getK());
+ }
+
+ @Override
+ public BufferAggregator factorizeBuffered(final ColumnSelectorFactory metricFactory)
+ {
+ final ColumnValueSelector selector = metricFactory.makeColumnValueSelector(getFieldName());
+ if (selector instanceof NilColumnValueSelector) {
+ return new DoublesSketchNoOpBufferAggregator();
+ }
+ return new DoublesSketchMergeBufferAggregator(selector, getK(), getMaxIntermediateSize());
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchMergeBufferAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchMergeBufferAggregator.java
new file mode 100644
index 000000000000..8578e7f125cb
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchMergeBufferAggregator.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import com.yahoo.memory.WritableMemory;
+import com.yahoo.sketches.quantiles.DoublesSketch;
+import com.yahoo.sketches.quantiles.DoublesUnion;
+import com.yahoo.sketches.quantiles.DoublesUnionBuilder;
+import io.druid.query.aggregation.BufferAggregator;
+import io.druid.query.monomorphicprocessing.RuntimeShapeInspector;
+import io.druid.segment.ColumnValueSelector;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+import java.nio.ByteBuffer;
+import java.util.IdentityHashMap;
+
+public class DoublesSketchMergeBufferAggregator implements BufferAggregator
+{
+
+ private final ColumnValueSelector selector;
+ private final int k;
+ private final int maxIntermediateSize;
+ private final IdentityHashMap memCache = new IdentityHashMap<>();
+ private final IdentityHashMap> unions = new IdentityHashMap<>();
+
+ public DoublesSketchMergeBufferAggregator(
+ final ColumnValueSelector selector,
+ final int k,
+ final int maxIntermediateSize)
+ {
+ this.selector = selector;
+ this.k = k;
+ this.maxIntermediateSize = maxIntermediateSize;
+ }
+
+ @Override
+ public synchronized void init(final ByteBuffer buffer, final int position)
+ {
+ final WritableMemory mem = getMemory(buffer);
+ final WritableMemory region = mem.writableRegion(position, maxIntermediateSize);
+ final DoublesUnion union = DoublesUnion.builder().setMaxK(k).build(region);
+ putUnion(buffer, position, union);
+ }
+
+ @Override
+ public synchronized void aggregate(final ByteBuffer buffer, final int position)
+ {
+ final DoublesSketch sketch = selector.getObject();
+ if (sketch == null) {
+ return;
+ }
+ final DoublesUnion union = unions.get(buffer).get(position);
+ union.update(sketch);
+ }
+
+ @Override
+ public synchronized Object get(final ByteBuffer buffer, final int position)
+ {
+ return unions.get(buffer).get(position).getResult();
+ }
+
+ @Override
+ public float getFloat(final ByteBuffer buffer, final int position)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public long getLong(final ByteBuffer buffer, final int position)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public synchronized void close()
+ {
+ unions.clear();
+ memCache.clear();
+ }
+
+ // A small number of sketches may run out of the given memory, request more memory on heap and move there.
+ // In that case we need to reuse the object from the cache as opposed to wrapping the new buffer.
+ @Override
+ public synchronized void relocate(int oldPosition, int newPosition, ByteBuffer oldBuffer, ByteBuffer newBuffer)
+ {
+ DoublesUnion union = unions.get(oldBuffer).get(oldPosition);
+ final WritableMemory oldMem = getMemory(oldBuffer).writableRegion(oldPosition, maxIntermediateSize);
+ if (union.isSameResource(oldMem)) { // union was not relocated on heap
+ final WritableMemory newMem = getMemory(newBuffer).writableRegion(newPosition, maxIntermediateSize);
+ union = DoublesUnionBuilder.wrap(newMem);
+ }
+ putUnion(newBuffer, newPosition, union);
+
+ Int2ObjectMap map = unions.get(oldBuffer);
+ map.remove(oldPosition);
+ if (map.isEmpty()) {
+ unions.remove(oldBuffer);
+ memCache.remove(oldBuffer);
+ }
+ }
+
+ private WritableMemory getMemory(final ByteBuffer buffer)
+ {
+ return memCache.computeIfAbsent(buffer, buf -> WritableMemory.wrap(buf));
+ }
+
+ private void putUnion(final ByteBuffer buffer, final int position, final DoublesUnion union)
+ {
+ Int2ObjectMap map = unions.computeIfAbsent(buffer, buf -> new Int2ObjectOpenHashMap<>());
+ map.put(position, union);
+ }
+
+ @Override
+ public void inspectRuntimeShape(final RuntimeShapeInspector inspector)
+ {
+ inspector.visit("selector", selector);
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchModule.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchModule.java
new file mode 100644
index 000000000000..0e3081965b03
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchModule.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.jsontype.NamedType;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.google.inject.Binder;
+import com.yahoo.sketches.quantiles.DoublesSketch;
+
+import io.druid.initialization.DruidModule;
+import io.druid.segment.serde.ComplexMetrics;
+
+public class DoublesSketchModule implements DruidModule
+{
+
+ public static final String DOUBLES_SKETCH = "quantilesDoublesSketch";
+
+ public static final String DOUBLES_SKETCH_HISTOGRAM_POST_AGG = "quantilesDoublesSketchToHistogram";
+ public static final String DOUBLES_SKETCH_QUANTILE_POST_AGG = "quantilesDoublesSketchToQuantile";
+ public static final String DOUBLES_SKETCH_QUANTILES_POST_AGG = "quantilesDoublesSketchToQuantiles";
+ public static final String DOUBLES_SKETCH_TO_STRING_POST_AGG = "quantilesDoublesSketchToString";
+
+ @Override
+ public void configure(final Binder binder)
+ {
+ if (ComplexMetrics.getSerdeForType(DOUBLES_SKETCH) == null) {
+ ComplexMetrics.registerSerde(DOUBLES_SKETCH, new DoublesSketchComplexMetricSerde());
+ }
+ }
+
+ @Override
+ public List extends Module> getJacksonModules()
+ {
+ return Arrays. asList(
+ new SimpleModule("DoublesQuantilesSketchModule").registerSubtypes(
+ new NamedType(DoublesSketchAggregatorFactory.class, DOUBLES_SKETCH),
+ new NamedType(DoublesSketchToHistogramPostAggregator.class, DOUBLES_SKETCH_HISTOGRAM_POST_AGG),
+ new NamedType(DoublesSketchToQuantilePostAggregator.class, DOUBLES_SKETCH_QUANTILE_POST_AGG),
+ new NamedType(DoublesSketchToQuantilesPostAggregator.class, DOUBLES_SKETCH_QUANTILES_POST_AGG),
+ new NamedType(DoublesSketchToStringPostAggregator.class, DOUBLES_SKETCH_TO_STRING_POST_AGG))
+ .addSerializer(DoublesSketch.class, new DoublesSketchJsonSerializer()));
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchNoOpAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchNoOpAggregator.java
new file mode 100644
index 000000000000..2cfeb03a534b
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchNoOpAggregator.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import io.druid.query.aggregation.Aggregator;
+
+public class DoublesSketchNoOpAggregator implements Aggregator
+{
+
+ @Override
+ public Object get()
+ {
+ return DoublesSketchOperations.EMPTY_SKETCH;
+ }
+
+ @Override
+ public void aggregate()
+ {
+ }
+
+ @Override
+ public void reset()
+ {
+ }
+
+ @Override
+ public void close()
+ {
+ }
+
+ @Override
+ public float getFloat()
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public long getLong()
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchNoOpBufferAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchNoOpBufferAggregator.java
new file mode 100644
index 000000000000..ad61ff5372f8
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchNoOpBufferAggregator.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import java.nio.ByteBuffer;
+
+import io.druid.query.aggregation.BufferAggregator;
+import io.druid.query.monomorphicprocessing.RuntimeShapeInspector;
+
+public class DoublesSketchNoOpBufferAggregator implements BufferAggregator
+{
+
+ @Override
+ public void init(final ByteBuffer buf, final int position)
+ {
+ }
+
+ @Override
+ public void aggregate(final ByteBuffer buf, final int position)
+ {
+ }
+
+ @Override
+ public Object get(final ByteBuffer buf, final int position)
+ {
+ return DoublesSketchOperations.EMPTY_SKETCH;
+ }
+
+ @Override
+ public float getFloat(final ByteBuffer buf, final int position)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public long getLong(final ByteBuffer buf, final int position)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public void close()
+ {
+ }
+
+ @Override
+ public void inspectRuntimeShape(final RuntimeShapeInspector inspector)
+ {
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchObjectStrategy.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchObjectStrategy.java
new file mode 100644
index 000000000000..3f89c905803f
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchObjectStrategy.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import java.nio.ByteBuffer;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.quantiles.DoublesSketch;
+
+import io.druid.segment.data.ObjectStrategy;
+
+public class DoublesSketchObjectStrategy implements ObjectStrategy
+{
+
+ private static final byte[] EMPTY_BYTES = new byte[] {};
+
+ @Override
+ public int compare(final DoublesSketch s1, final DoublesSketch s2)
+ {
+ return DoublesSketchAggregatorFactory.COMPARATOR.compare(s1, s2);
+ }
+
+ @Override
+ public DoublesSketch fromByteBuffer(final ByteBuffer buffer, final int numBytes)
+ {
+ if (numBytes == 0) {
+ return DoublesSketchOperations.EMPTY_SKETCH;
+ }
+ return DoublesSketch.wrap(Memory.wrap(buffer).region(buffer.position(), numBytes));
+ }
+
+ @Override
+ public Class extends DoublesSketch> getClazz()
+ {
+ return DoublesSketch.class;
+ }
+
+ @Override
+ public byte[] toBytes(final DoublesSketch sketch)
+ {
+ if (sketch == null || sketch.isEmpty()) {
+ return EMPTY_BYTES;
+ }
+ return sketch.toByteArray(true);
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchOperations.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchOperations.java
new file mode 100644
index 000000000000..5a0f70e6b76a
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchOperations.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import org.apache.commons.codec.binary.Base64;
+
+import com.google.common.base.Charsets;
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.quantiles.DoublesSketch;
+
+import io.druid.java.util.common.ISE;
+
+public class DoublesSketchOperations
+{
+
+ public static final DoublesSketch EMPTY_SKETCH = DoublesSketch.builder().build();
+
+ public static DoublesSketch deserialize(final Object serializedSketch)
+ {
+ if (serializedSketch instanceof String) {
+ return deserializeFromBase64EncodedString((String) serializedSketch);
+ } else if (serializedSketch instanceof byte[]) {
+ return deserializeFromByteArray((byte[]) serializedSketch);
+ } else if (serializedSketch instanceof DoublesSketch) {
+ return (DoublesSketch) serializedSketch;
+ }
+ throw new ISE(
+ "Object is not of a type that can be deserialized to a quantiles DoublsSketch: "
+ + serializedSketch.getClass());
+ }
+
+ public static DoublesSketch deserializeFromBase64EncodedString(final String str)
+ {
+ return deserializeFromByteArray(Base64.decodeBase64(str.getBytes(Charsets.UTF_8)));
+ }
+
+ public static DoublesSketch deserializeFromByteArray(final byte[] data)
+ {
+ return DoublesSketch.wrap(Memory.wrap(data));
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregator.java
new file mode 100644
index 000000000000..015d7787d43c
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregator.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+
+import io.druid.java.util.common.IAE;
+import io.druid.query.aggregation.AggregatorFactory;
+import io.druid.query.aggregation.AggregatorUtil;
+import io.druid.query.aggregation.PostAggregator;
+import io.druid.query.cache.CacheKeyBuilder;
+
+import com.yahoo.sketches.quantiles.DoublesSketch;
+
+public class DoublesSketchToHistogramPostAggregator implements PostAggregator
+{
+
+ private final String name;
+ private final PostAggregator field;
+ private final double[] splitPoints;
+
+ @JsonCreator
+ public DoublesSketchToHistogramPostAggregator(
+ @JsonProperty("name") final String name,
+ @JsonProperty("field") final PostAggregator field,
+ @JsonProperty("splitPoints") final double[] splitPoints)
+ {
+ this.name = Preconditions.checkNotNull(name, "name is null");
+ this.field = Preconditions.checkNotNull(field, "field is null");
+ this.splitPoints = Preconditions.checkNotNull(splitPoints, "array of split points is null");
+ }
+
+ @Override
+ public Object compute(final Map combinedAggregators)
+ {
+ final DoublesSketch sketch = (DoublesSketch) field.compute(combinedAggregators);
+ final double[] histogram = sketch.getPMF(splitPoints);
+ for (int i = 0; i < histogram.length; i++) {
+ histogram[i] *= sketch.getN();
+ }
+ return histogram;
+ }
+
+ @Override
+ @JsonProperty
+ public String getName()
+ {
+ return name;
+ }
+
+ @JsonProperty
+ public PostAggregator getField()
+ {
+ return field;
+ }
+
+ @JsonProperty
+ public double[] getSplitPoints()
+ {
+ return splitPoints;
+ }
+
+ @Override
+ public Comparator getComparator()
+ {
+ throw new IAE("Comparing histograms is not supported");
+ }
+
+ @Override
+ public Set getDependentFields()
+ {
+ return field.getDependentFields();
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getSimpleName() + "{" +
+ "name='" + name + '\'' +
+ ", field=" + field +
+ ", splitPoints=" + Arrays.toString(splitPoints) +
+ "}";
+ }
+
+ @Override
+ public boolean equals(final Object o)
+ {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final DoublesSketchToHistogramPostAggregator that = (DoublesSketchToHistogramPostAggregator) o;
+ if (!name.equals(that.name)) {
+ return false;
+ }
+ if (!Arrays.equals(splitPoints, that.splitPoints)) {
+ return false;
+ }
+ return field.equals(that.field);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hashCode = name.hashCode() * 31 + field.hashCode();
+ hashCode = hashCode * 31 + Arrays.hashCode(splitPoints);
+ return hashCode;
+ }
+
+ @Override
+ public byte[] getCacheKey()
+ {
+ final CacheKeyBuilder builder = new CacheKeyBuilder(
+ AggregatorUtil.QUANTILES_DOUBLES_SKETCH_TO_HISTOGRAM_CACHE_TYPE_ID).appendCacheable(field);
+ for (final double value: splitPoints) {
+ builder.appendDouble(value);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public PostAggregator decorate(final Map map)
+ {
+ return this;
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToQuantilePostAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToQuantilePostAggregator.java
new file mode 100644
index 000000000000..a9df26b57743
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToQuantilePostAggregator.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import com.google.common.primitives.Doubles;
+
+import io.druid.query.aggregation.AggregatorFactory;
+import io.druid.query.aggregation.AggregatorUtil;
+import io.druid.query.aggregation.PostAggregator;
+import io.druid.query.cache.CacheKeyBuilder;
+
+import com.yahoo.sketches.quantiles.DoublesSketch;
+
+public class DoublesSketchToQuantilePostAggregator implements PostAggregator
+{
+
+ private final String name;
+ private final PostAggregator field;
+ private final double fraction;
+
+ @JsonCreator
+ public DoublesSketchToQuantilePostAggregator(
+ @JsonProperty("name") final String name,
+ @JsonProperty("field") final PostAggregator field,
+ @JsonProperty("fraction") final double fraction)
+ {
+ this.name = Preconditions.checkNotNull(name, "name is null");
+ this.field = Preconditions.checkNotNull(field, "field is null");
+ this.fraction = fraction;
+ }
+
+ @Override
+ @JsonProperty
+ public String getName()
+ {
+ return name;
+ }
+
+ @JsonProperty
+ public PostAggregator getField()
+ {
+ return field;
+ }
+
+ @JsonProperty
+ public double getFraction()
+ {
+ return fraction;
+ }
+
+ @Override
+ public Object compute(final Map combinedAggregators)
+ {
+ final DoublesSketch sketch = (DoublesSketch) field.compute(combinedAggregators);
+ return sketch.getQuantile(fraction);
+ }
+
+ @Override
+ public Comparator getComparator()
+ {
+ return new Comparator()
+ {
+ @Override
+ public int compare(final Double a, final Double b)
+ {
+ return Doubles.compare(a, b);
+ }
+ };
+ }
+
+ @Override
+ public Set getDependentFields()
+ {
+ return field.getDependentFields();
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getSimpleName() + "{" +
+ "name='" + name + '\'' +
+ ", field=" + field +
+ ", fraction=" + fraction +
+ "}";
+ }
+
+ @Override
+ public boolean equals(final Object o)
+ {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final DoublesSketchToQuantilePostAggregator that = (DoublesSketchToQuantilePostAggregator) o;
+ if (!name.equals(that.name)) {
+ return false;
+ }
+ if (fraction != that.fraction) {
+ return false;
+ }
+ return field.equals(that.field);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (name.hashCode() * 31 + field.hashCode()) * 31 + Double.hashCode(fraction);
+ }
+
+ @Override
+ public byte[] getCacheKey()
+ {
+ return new CacheKeyBuilder(AggregatorUtil.QUANTILES_DOUBLES_SKETCH_TO_QUANTILE_CACHE_TYPE_ID)
+ .appendCacheable(field).appendDouble(fraction).build();
+ }
+
+ @Override
+ public PostAggregator decorate(final Map map)
+ {
+ return this;
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToQuantilesPostAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToQuantilesPostAggregator.java
new file mode 100644
index 000000000000..45674dcf7f71
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToQuantilesPostAggregator.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+
+import io.druid.java.util.common.IAE;
+import io.druid.query.aggregation.AggregatorFactory;
+import io.druid.query.aggregation.AggregatorUtil;
+import io.druid.query.aggregation.PostAggregator;
+import io.druid.query.cache.CacheKeyBuilder;
+
+import com.yahoo.sketches.quantiles.DoublesSketch;
+
+public class DoublesSketchToQuantilesPostAggregator implements PostAggregator
+{
+
+ private final String name;
+ private final PostAggregator field;
+ private final double[] fractions;
+
+ @JsonCreator
+ public DoublesSketchToQuantilesPostAggregator(
+ @JsonProperty("name") final String name,
+ @JsonProperty("field") final PostAggregator field,
+ @JsonProperty("fractions") final double[] fractions)
+ {
+ this.name = Preconditions.checkNotNull(name, "name is null");
+ this.field = Preconditions.checkNotNull(field, "field is null");
+ this.fractions = Preconditions.checkNotNull(fractions, "array of fractions is null");
+ }
+
+ @Override
+ @JsonProperty
+ public String getName()
+ {
+ return name;
+ }
+
+ @JsonProperty
+ public PostAggregator getField()
+ {
+ return field;
+ }
+
+ @JsonProperty
+ public double[] getFractions()
+ {
+ return fractions;
+ }
+
+ @Override
+ public Object compute(final Map combinedAggregators)
+ {
+ final DoublesSketch sketch = (DoublesSketch) field.compute(combinedAggregators);
+ return sketch.getQuantiles(fractions);
+ }
+
+ @Override
+ public Comparator getComparator()
+ {
+ throw new IAE("Comparing arrays of quantiles is not supported");
+ }
+
+ @Override
+ public Set getDependentFields()
+ {
+ return field.getDependentFields();
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getSimpleName() + "{" +
+ "name='" + name + '\'' +
+ ", field=" + field +
+ ", fractions=" + Arrays.toString(fractions) +
+ "}";
+ }
+
+ @Override
+ public boolean equals(final Object o)
+ {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final DoublesSketchToQuantilesPostAggregator that = (DoublesSketchToQuantilesPostAggregator) o;
+ if (!name.equals(that.name)) {
+ return false;
+ }
+ if (!Arrays.equals(fractions, that.fractions)) {
+ return false;
+ }
+ return field.equals(that.field);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (name.hashCode() * 31 + field.hashCode()) * 31 + Arrays.hashCode(fractions);
+ }
+
+ @Override
+ public byte[] getCacheKey()
+ {
+ final CacheKeyBuilder builder = new CacheKeyBuilder(
+ AggregatorUtil.QUANTILES_DOUBLES_SKETCH_TO_QUANTILES_CACHE_TYPE_ID).appendCacheable(field);
+ for (final double value: fractions) {
+ builder.appendDouble(value);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public PostAggregator decorate(final Map map)
+ {
+ return this;
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToStringPostAggregator.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToStringPostAggregator.java
new file mode 100644
index 000000000000..204b80cef50c
--- /dev/null
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchToStringPostAggregator.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+
+import io.druid.java.util.common.IAE;
+import io.druid.query.aggregation.AggregatorFactory;
+import io.druid.query.aggregation.AggregatorUtil;
+import io.druid.query.aggregation.PostAggregator;
+import io.druid.query.cache.CacheKeyBuilder;
+
+import com.yahoo.sketches.quantiles.DoublesSketch;
+
+public class DoublesSketchToStringPostAggregator implements PostAggregator
+{
+
+ private final String name;
+ private final PostAggregator field;
+
+ @JsonCreator
+ public DoublesSketchToStringPostAggregator(
+ @JsonProperty("name") final String name,
+ @JsonProperty("field") final PostAggregator field)
+ {
+ this.name = Preconditions.checkNotNull(name, "name is null");
+ this.field = Preconditions.checkNotNull(field, "field is null");
+ }
+
+ @Override
+ @JsonProperty
+ public String getName()
+ {
+ return name;
+ }
+
+ @JsonProperty
+ public PostAggregator getField()
+ {
+ return field;
+ }
+
+ @Override
+ public Object compute(final Map combinedAggregators)
+ {
+ final DoublesSketch sketch = (DoublesSketch) field.compute(combinedAggregators);
+ return sketch.toString();
+ }
+
+ @Override
+ public Comparator getComparator()
+ {
+ throw new IAE("Comparing sketch summaries is not supported");
+ }
+
+ @Override
+ public Set getDependentFields()
+ {
+ return field.getDependentFields();
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getClass().getSimpleName() + "{" +
+ "name='" + name + '\'' +
+ ", field=" + field +
+ "}";
+ }
+
+ @Override
+ public boolean equals(final Object o)
+ {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final DoublesSketchToStringPostAggregator that = (DoublesSketchToStringPostAggregator) o;
+ if (!name.equals(that.name)) {
+ return false;
+ }
+ return field.equals(that.field);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (name.hashCode() * 31 + field.hashCode()) * 31;
+ }
+
+ @Override
+ public byte[] getCacheKey()
+ {
+ final CacheKeyBuilder builder = new CacheKeyBuilder(
+ AggregatorUtil.QUANTILES_DOUBLES_SKETCH_TO_STRING_CACHE_TYPE_ID).appendCacheable(field);
+ return builder.build();
+ }
+
+ @Override
+ public PostAggregator decorate(final Map map)
+ {
+ return this;
+ }
+
+}
diff --git a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/theta/SynchronizedUnion.java b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/theta/SynchronizedUnion.java
index 4b613d848a6c..41e3d8ee410a 100644
--- a/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/theta/SynchronizedUnion.java
+++ b/extensions-core/datasketches/src/main/java/io/druid/query/aggregation/datasketches/theta/SynchronizedUnion.java
@@ -21,13 +21,14 @@
import com.yahoo.memory.Memory;
import com.yahoo.memory.WritableMemory;
+import com.yahoo.sketches.Family;
import com.yahoo.sketches.theta.CompactSketch;
import com.yahoo.sketches.theta.Sketch;
import com.yahoo.sketches.theta.Union;
/**
*/
-public class SynchronizedUnion implements Union
+public class SynchronizedUnion extends Union
{
private final Union delegate;
@@ -67,18 +68,21 @@ public synchronized void update(String datum)
}
@Override
+ @SuppressWarnings("ParameterPackage")
public synchronized void update(byte[] data)
{
delegate.update(data);
}
@Override
+ @SuppressWarnings("ParameterPackage")
public synchronized void update(int[] data)
{
delegate.update(data);
}
@Override
+ @SuppressWarnings("ParameterPackage")
public synchronized void update(char[] chars)
{
delegate.update(chars);
@@ -119,4 +123,11 @@ public synchronized boolean isSameResource(Memory mem)
{
return delegate.isSameResource(mem);
}
+
+ @Override
+ public synchronized Family getFamily()
+ {
+ return delegate.getFamily();
+ }
+
}
diff --git a/extensions-core/datasketches/src/main/resources/META-INF/services/io.druid.initialization.DruidModule b/extensions-core/datasketches/src/main/resources/META-INF/services/io.druid.initialization.DruidModule
index efd10f3a7e78..dfc5b69e00fb 100644
--- a/extensions-core/datasketches/src/main/resources/META-INF/services/io.druid.initialization.DruidModule
+++ b/extensions-core/datasketches/src/main/resources/META-INF/services/io.druid.initialization.DruidModule
@@ -1,2 +1,3 @@
io.druid.query.aggregation.datasketches.theta.SketchModule
io.druid.query.aggregation.datasketches.theta.oldapi.OldApiSketchModule
+io.druid.query.aggregation.datasketches.quantiles.DoublesSketchModule
diff --git a/extensions-core/datasketches/src/test/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchAggregatorTest.java b/extensions-core/datasketches/src/test/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchAggregatorTest.java
new file mode 100644
index 000000000000..899c8392e4fd
--- /dev/null
+++ b/extensions-core/datasketches/src/test/java/io/druid/query/aggregation/datasketches/quantiles/DoublesSketchAggregatorTest.java
@@ -0,0 +1,419 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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.
+ */
+
+package io.druid.query.aggregation.datasketches.quantiles;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.Lists;
+import io.druid.data.input.Row;
+import io.druid.initialization.DruidModule;
+import io.druid.jackson.DefaultObjectMapper;
+import io.druid.java.util.common.granularity.Granularities;
+import io.druid.java.util.common.guava.Sequence;
+import io.druid.java.util.common.guava.Sequences;
+import io.druid.query.aggregation.AggregationTestHelper;
+import io.druid.query.groupby.GroupByQueryConfig;
+import io.druid.query.groupby.GroupByQueryRunnerTest;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public class DoublesSketchAggregatorTest
+{
+
+ private final AggregationTestHelper helper;
+ private final AggregationTestHelper timeSeriesHelper;
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ public DoublesSketchAggregatorTest(final GroupByQueryConfig config)
+ {
+ DruidModule module = new DoublesSketchModule();
+ module.configure(null);
+ helper = AggregationTestHelper.createGroupByQueryAggregationTestHelper(
+ module.getJacksonModules(), config, tempFolder);
+ timeSeriesHelper = AggregationTestHelper.createTimeseriesQueryAggregationTestHelper(
+ module.getJacksonModules(),
+ tempFolder);
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection> constructorFeeder()
+ {
+ final List