From 67f45fa7bfc5eeb17e9ff23b3519fb01520a8360 Mon Sep 17 00:00:00 2001 From: Magnus Henoch Date: Thu, 16 Nov 2023 07:01:22 +0000 Subject: [PATCH] Fix histograms for sketches where min and max are equal (#15381) There is a problem with Quantiles sketches and KLL Quantiles sketches. Queries using the histogram post-aggregator fail if: - the sketch contains at least one value, and - the values in the sketch are all equal, and - the splitPoints argument is not passed to the post-aggregator, and - the numBins argument is greater than 2 (or not specified, which leads to the default of 10 being used) In that case, the query fails and returns this error: { "error": "Unknown exception", "errorClass": "org.apache.datasketches.common.SketchesArgumentException", "host": null, "errorCode": "legacyQueryException", "persona": "OPERATOR", "category": "RUNTIME_FAILURE", "errorMessage": "Values must be unique, monotonically increasing and not NaN.", "context": { "host": null, "errorClass": "org.apache.datasketches.common.SketchesArgumentException", "legacyErrorCode": "Unknown exception" } } This behaviour is undesirable, since the caller doesn't necessarily know in advance whether the sketch has values that are diverse enough. With this change, the post-aggregators return [N, 0, 0...] instead of crashing, where N is the number of values in the sketch, and the length of the list is equal to numBins. That is what they already returned for numBins = 2. Here is an example of a query that would fail: {"queryType":"timeseries", "dataSource": { "type": "inline", "columnNames": ["foo", "bar"], "rows": [ ["abc", 42.0], ["def", 42.0] ] }, "intervals":["0000/3000"], "granularity":"all", "aggregations":[ {"name":"the_sketch", "fieldName":"bar", "type":"quantilesDoublesSketch"}], "postAggregations":[ {"name":"the_histogram", "type":"quantilesDoublesSketchToHistogram", "field":{"type":"fieldAccess","fieldName":"the_sketch"}, "numBins": 3}]} I believe this also fixes issue #10585. --- ...oublesSketchToHistogramPostAggregator.java | 21 ++- ...FloatsSketchToHistogramPostAggregator.java | 21 ++- ...oublesSketchToHistogramPostAggregator.java | 21 ++- ...esSketchToHistogramPostAggregatorTest.java | 152 ++++++++++++++++++ ...tsSketchToHistogramPostAggregatorTest.java | 152 ++++++++++++++++++ ...esSketchToHistogramPostAggregatorTest.java | 152 ++++++++++++++++++ 6 files changed, 510 insertions(+), 9 deletions(-) diff --git a/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/kll/KllDoublesSketchToHistogramPostAggregator.java b/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/kll/KllDoublesSketchToHistogramPostAggregator.java index 90a295e7e8a..54a0022cfac 100644 --- a/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/kll/KllDoublesSketchToHistogramPostAggregator.java +++ b/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/kll/KllDoublesSketchToHistogramPostAggregator.java @@ -67,7 +67,7 @@ public class KllDoublesSketchToHistogramPostAggregator implements PostAggregator public Object compute(final Map combinedAggregators) { final KllDoublesSketch sketch = (KllDoublesSketch) field.compute(combinedAggregators); - final int numBins = splitPoints != null ? splitPoints.length + 1 : + final int numBins = this.splitPoints != null ? this.splitPoints.length + 1 : (this.numBins != null ? this.numBins.intValue() : DEFAULT_NUM_BINS); if (numBins < 2) { throw new IAE("at least 2 bins expected"); @@ -77,8 +77,23 @@ public class KllDoublesSketchToHistogramPostAggregator implements PostAggregator Arrays.fill(histogram, Double.NaN); return histogram; } - final double[] histogram = sketch.getPMF(splitPoints != null ? splitPoints : - equallySpacedPoints(numBins, sketch.getMinItem(), sketch.getMaxItem())); + final double[] splitPoints; + if (this.splitPoints != null) { + splitPoints = this.splitPoints; + } else { + final double min = sketch.getMinItem(); + final double max = sketch.getMaxItem(); + if (min == max) { + // if min is equal to max, we can't create an array of equally spaced points. + // all values would go into the first bucket anyway, and the remaining + // buckets are left as zero. + final double[] histogram = new double[numBins]; + histogram[0] = sketch.getN(); + return histogram; + } + splitPoints = equallySpacedPoints(numBins, min, max); + } + final double[] histogram = sketch.getPMF(splitPoints); for (int i = 0; i < histogram.length; i++) { histogram[i] *= sketch.getN(); // scale fractions to counts } diff --git a/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/kll/KllFloatsSketchToHistogramPostAggregator.java b/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/kll/KllFloatsSketchToHistogramPostAggregator.java index 90e0942f80a..a5bfc68f326 100644 --- a/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/kll/KllFloatsSketchToHistogramPostAggregator.java +++ b/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/kll/KllFloatsSketchToHistogramPostAggregator.java @@ -67,7 +67,7 @@ public class KllFloatsSketchToHistogramPostAggregator implements PostAggregator public Object compute(final Map combinedAggregators) { final KllFloatsSketch sketch = (KllFloatsSketch) field.compute(combinedAggregators); - final int numBins = splitPoints != null ? splitPoints.length + 1 : + final int numBins = this.splitPoints != null ? this.splitPoints.length + 1 : (this.numBins != null ? this.numBins.intValue() : DEFAULT_NUM_BINS); if (numBins < 2) { throw new IAE("at least 2 bins expected"); @@ -77,8 +77,23 @@ public class KllFloatsSketchToHistogramPostAggregator implements PostAggregator Arrays.fill(histogram, Double.NaN); return histogram; } - final double[] histogram = sketch.getPMF(splitPoints != null ? splitPoints : - equallySpacedPoints(numBins, sketch.getMinItem(), sketch.getMaxItem())); + final float[] splitPoints; + if (this.splitPoints != null) { + splitPoints = this.splitPoints; + } else { + final float min = sketch.getMinItem(); + final float max = sketch.getMaxItem(); + if (min == max) { + // if min is equal to max, we can't create an array of equally spaced points. + // all values would go into the first bucket anyway, and the remaining + // buckets are left as zero. + final double[] histogram = new double[numBins]; + histogram[0] = sketch.getN(); + return histogram; + } + splitPoints = equallySpacedPoints(numBins, min, max); + } + final double[] histogram = sketch.getPMF(splitPoints); for (int i = 0; i < histogram.length; i++) { histogram[i] *= sketch.getN(); // scale fractions to counts } diff --git a/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregator.java b/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregator.java index 49d4f84cd4f..7598561f273 100644 --- a/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregator.java +++ b/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregator.java @@ -67,7 +67,7 @@ public class DoublesSketchToHistogramPostAggregator implements PostAggregator public Object compute(final Map combinedAggregators) { final DoublesSketch sketch = (DoublesSketch) field.compute(combinedAggregators); - final int numBins = splitPoints != null ? splitPoints.length + 1 : + final int numBins = this.splitPoints != null ? this.splitPoints.length + 1 : (this.numBins != null ? this.numBins.intValue() : DEFAULT_NUM_BINS); if (numBins < 2) { throw new IAE("at least 2 bins expected"); @@ -77,8 +77,23 @@ public class DoublesSketchToHistogramPostAggregator implements PostAggregator Arrays.fill(histogram, Double.NaN); return histogram; } - final double[] histogram = sketch.getPMF(splitPoints != null ? splitPoints : - equallySpacedPoints(numBins, sketch.getMinItem(), sketch.getMaxItem())); + final double[] splitPoints; + if (this.splitPoints != null) { + splitPoints = this.splitPoints; + } else { + final double min = sketch.getMinItem(); + final double max = sketch.getMaxItem(); + if (min == max) { + // if min is equal to max, we can't create an array of equally spaced points. + // all values would go into the first bucket anyway, and the remaining + // buckets are left as zero. + final double[] histogram = new double[numBins]; + histogram[0] = sketch.getN(); + return histogram; + } + splitPoints = equallySpacedPoints(numBins, min, max); + } + final double[] histogram = sketch.getPMF(splitPoints); for (int i = 0; i < histogram.length; i++) { histogram[i] *= sketch.getN(); // scale fractions to counts } diff --git a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/kll/KllDoublesSketchToHistogramPostAggregatorTest.java b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/kll/KllDoublesSketchToHistogramPostAggregatorTest.java index e3f55b4fb79..bfb125b42b5 100644 --- a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/kll/KllDoublesSketchToHistogramPostAggregatorTest.java +++ b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/kll/KllDoublesSketchToHistogramPostAggregatorTest.java @@ -157,6 +157,36 @@ public class KllDoublesSketchToHistogramPostAggregatorTest Assert.assertEquals(3.0, histogram[1], 0); } + @Test + public void splitPointsEqualValues() + { + final double[] values = new double[] {6, 6, 6, 6, 6, 6}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new KllDoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllDoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + new double[] {3.5}, // all values are in the second bin + null + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(2, histogram.length); + Assert.assertEquals(0.0, histogram[0], 0); + Assert.assertEquals(6.0, histogram[1], 0); + } + @Test public void numBins() { @@ -187,6 +217,128 @@ public class KllDoublesSketchToHistogramPostAggregatorTest Assert.assertEquals(3.0, histogram[1], 0); } + @Test + public void oneValueTwoBins() + { + final double[] values = new double[] {1}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new KllDoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllDoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 2 // two bins, the second is empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(2, histogram.length); + Assert.assertEquals(1.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + } + + @Test + public void oneValueThreeBins() + { + final double[] values = new double[] {1}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new KllDoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllDoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 3 // three bins, the second and third are empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(3, histogram.length); + Assert.assertEquals(1.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + Assert.assertEquals(0.0, histogram[2], 0); + } + + @Test + public void equalValuesTwoBins() + { + final double[] values = new double[] {1, 1, 1}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new KllDoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllDoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 2 // two bins, the second is empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(2, histogram.length); + Assert.assertEquals(3.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + } + + @Test + public void equalValuesThreeBins() + { + final double[] values = new double[] {1, 1, 1}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new KllDoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllDoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 3 // three bins, the second and third are empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(3, histogram.length); + Assert.assertEquals(3.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + Assert.assertEquals(0.0, histogram[2], 0); + } + @Test public void testResultArraySignature() { diff --git a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/kll/KllFloatsSketchToHistogramPostAggregatorTest.java b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/kll/KllFloatsSketchToHistogramPostAggregatorTest.java index 5b970816f3f..bf5862bec88 100644 --- a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/kll/KllFloatsSketchToHistogramPostAggregatorTest.java +++ b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/kll/KllFloatsSketchToHistogramPostAggregatorTest.java @@ -157,6 +157,36 @@ public class KllFloatsSketchToHistogramPostAggregatorTest Assert.assertEquals(3.0, histogram[1], 0); } + @Test + public void splitPointsEqualValues() + { + final float[] values = new float[] {6, 6, 6, 6, 6, 6}; + final TestFloatColumnSelector selector = new TestFloatColumnSelector(values); + + final Aggregator agg = new KllFloatsSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllFloatsSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + new float[] {3.5f}, // all values are in the second bin + null + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(2, histogram.length); + Assert.assertEquals(0.0, histogram[0], 0); + Assert.assertEquals(6.0, histogram[1], 0); + } + @Test public void numBins() { @@ -187,6 +217,128 @@ public class KllFloatsSketchToHistogramPostAggregatorTest Assert.assertEquals(3.0, histogram[1], 0); } + @Test + public void oneValueTwoBins() + { + final float[] values = new float[] {1}; + final TestFloatColumnSelector selector = new TestFloatColumnSelector(values); + + final Aggregator agg = new KllFloatsSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllFloatsSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 2 // two bins, the second is empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(2, histogram.length); + Assert.assertEquals(1.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + } + + @Test + public void oneValueThreeBins() + { + final float[] values = new float[] {1}; + final TestFloatColumnSelector selector = new TestFloatColumnSelector(values); + + final Aggregator agg = new KllFloatsSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllFloatsSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 3 // three bins, the second and third are empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(3, histogram.length); + Assert.assertEquals(1.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + Assert.assertEquals(0.0, histogram[2], 0); + } + + @Test + public void equalValuesTwoBins() + { + final float[] values = new float[] {1, 1, 1}; + final TestFloatColumnSelector selector = new TestFloatColumnSelector(values); + + final Aggregator agg = new KllFloatsSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllFloatsSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 2 // two bins, the second is empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(2, histogram.length); + Assert.assertEquals(3.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + } + + @Test + public void equalValuesThreeBins() + { + final float[] values = new float[] {1, 1, 1}; + final TestFloatColumnSelector selector = new TestFloatColumnSelector(values); + + final Aggregator agg = new KllFloatsSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new KllFloatsSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 3 // three bins, the second and third are empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(3, histogram.length); + Assert.assertEquals(3.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + Assert.assertEquals(0.0, histogram[2], 0); + } + @Test public void testResultArraySignature() { diff --git a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregatorTest.java b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregatorTest.java index 2bfc0e53539..94bc7fa1b9d 100644 --- a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregatorTest.java +++ b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/quantiles/DoublesSketchToHistogramPostAggregatorTest.java @@ -157,6 +157,36 @@ public class DoublesSketchToHistogramPostAggregatorTest Assert.assertEquals(3.0, histogram[1], 0); } + @Test + public void splitPointsEqualValues() + { + final double[] values = new double[] {6, 6, 6, 6, 6, 6}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new DoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new DoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + new double[] {3.5}, // all values are in the second bin + null + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(2, histogram.length); + Assert.assertEquals(0.0, histogram[0], 0); + Assert.assertEquals(6.0, histogram[1], 0); + } + @Test public void numBins() { @@ -187,6 +217,128 @@ public class DoublesSketchToHistogramPostAggregatorTest Assert.assertEquals(3.0, histogram[1], 0); } + @Test + public void oneValueTwoBins() + { + final double[] values = new double[] {1}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new DoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new DoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 2 // two bins, the second is empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(2, histogram.length); + Assert.assertEquals(1.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + } + + @Test + public void oneValueThreeBins() + { + final double[] values = new double[] {1}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new DoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new DoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 3 // three bins, the second and third are empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(3, histogram.length); + Assert.assertEquals(1.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + Assert.assertEquals(0.0, histogram[2], 0); + } + + @Test + public void equalValuesTwoBins() + { + final double[] values = new double[] {1, 1, 1}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new DoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new DoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 2 // two bins, the second is empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(2, histogram.length); + Assert.assertEquals(3.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + } + + @Test + public void equalValuesThreeBins() + { + final double[] values = new double[] {1, 1, 1}; + final TestDoubleColumnSelectorImpl selector = new TestDoubleColumnSelectorImpl(values); + + final Aggregator agg = new DoublesSketchBuildAggregator(selector, 8); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < values.length; i++) { + agg.aggregate(); + selector.increment(); + } + + final Map fields = new HashMap<>(); + fields.put("sketch", agg.get()); + + final PostAggregator postAgg = new DoublesSketchToHistogramPostAggregator( + "histogram", + new FieldAccessPostAggregator("field", "sketch"), + null, + 3 // three bins, the second and third are empty + ); + + final double[] histogram = (double[]) postAgg.compute(fields); + Assert.assertNotNull(histogram); + Assert.assertEquals(3, histogram.length); + Assert.assertEquals(3.0, histogram[0], 0); + Assert.assertEquals(0.0, histogram[1], 0); + Assert.assertEquals(0.0, histogram[2], 0); + } + @Test public void testResultArraySignature() {