You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datasketches.apache.org by jm...@apache.org on 2021/08/03 21:13:50 UTC
[datasketches-server] branch test_coverage updated: Tests for query
handling. Related,
change KLL queries to handle CDF and PMF separately to allow a single query
to request both
This is an automated email from the ASF dual-hosted git repository.
jmalkin pushed a commit to branch test_coverage
in repository https://gitbox.apache.org/repos/asf/datasketches-server.git
The following commit(s) were added to refs/heads/test_coverage by this push:
new 3a7da15 Tests for query handling. Related, change KLL queries to handle CDF and PMF separately to allow a single query to request both
3a7da15 is described below
commit 3a7da156b5f5958da3644fc59061a1b794ae5eaa
Author: Jon Malkin <jm...@users.noreply.github.com>
AuthorDate: Tue Aug 3 14:13:37 2021 -0700
Tests for query handling. Related, change KLL queries to handle CDF and PMF separately to allow a single query to request both
---
README.md | 8 +-
example/query.json | 3 +-
.../datasketches/server/DataQueryHandler.java | 178 ++++-----
.../datasketches/server/SketchConstants.java | 6 +-
.../datasketches/server/DataQueryHandlerTest.java | 433 +++++++++++++++++++++
.../datasketches/server/UpdateHandlerTest.java | 1 -
src/test/resources/test_config.json | 2 +-
7 files changed, 517 insertions(+), 114 deletions(-)
diff --git a/README.md b/README.md
index 14f5224..a117f9f 100644
--- a/README.md
+++ b/README.md
@@ -196,8 +196,7 @@ A sample query may be found in [query.json][example/query.json]:
"errorType": "noFalsePositives"
},
{ "name": "duration",
- "resultType": "cdf",
- "values": [500, 800, 1000],
+ "cdfValues": [500, 800, 1000],
"fractions": [0.2, 0.5, 0.8]
},
{ "name": "cpcOfNumbers",
@@ -212,9 +211,8 @@ primarily for debugging. The other properties supported in a query are specitic
* theta, cpc, hll
* No additional fields; returns all estimates
* kll
- * `resultType`: indicates `pmf` or `cdf` for rank results with a `values` query. A single
- query may include only one.
- * `values`: specifies split points in value space when querying ranks (as pmf or cdf)
+ * `cdfValues`: specifies split points in value space when querying ranks in a CDF
+ * `pmfValues`: specifies split points in value space when querying masses in a PMF
* `fractions`: specifies split points in rank space when querying values from the sketch
* frequency
* `errorType`: specifies `noFalsePositives` or `noFalseNegatives`
diff --git a/example/query.json b/example/query.json
index 53a1e04..0e82935 100644
--- a/example/query.json
+++ b/example/query.json
@@ -3,8 +3,7 @@
"errorType": "noFalsePositives"
},
{ "name": "duration",
- "resultType": "cdf",
- "values": [500, 800, 1000],
+ "cdfValues": [500, 800, 1000],
"fractions": [0.2, 0.5, 0.8]
},
{ "name": "cpcOfNumbers",
diff --git a/src/main/java/org/apache/datasketches/server/DataQueryHandler.java b/src/main/java/org/apache/datasketches/server/DataQueryHandler.java
index cac8f58..64b4870 100644
--- a/src/main/java/org/apache/datasketches/server/DataQueryHandler.java
+++ b/src/main/java/org/apache/datasketches/server/DataQueryHandler.java
@@ -31,9 +31,9 @@ import org.apache.datasketches.sampling.VarOptItemsSamples;
import org.apache.datasketches.sampling.VarOptItemsSketch;
import org.apache.datasketches.theta.CompactSketch;
import org.apache.datasketches.theta.Union;
+import org.checkerframework.checker.nullness.qual.NonNull;
import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import static org.apache.datasketches.server.SketchConstants.*;
@@ -54,39 +54,35 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
throw new IllegalArgumentException("Query missing sketch name field");
}
- final String key = query.get(QUERY_NAME_FIELD).getAsString();
- if (!sketches.contains(key)) {
- throw new IllegalArgumentException("Invalid sketch name: " + key);
+ final String sketchName = query.get(QUERY_NAME_FIELD).getAsString();
+ if (!sketches.contains(sketchName)) {
+ throw new IllegalArgumentException("Invalid sketch name: " + sketchName);
}
- JsonObject result = new JsonObject();
+ final JsonObject result;
- // we do need to lock the sketch even for query processing
- synchronized (key.intern()) {
- final SketchStorage.SketchEntry se = sketches.getSketch(key);
-
- // pre-populate with the sketch name, but may be overwritten with
- // null depending on the query
- result.addProperty(QUERY_NAME_FIELD, key);
+ // we need to lock the sketch even for query processing
+ synchronized (sketchName.intern()) {
+ final SketchStorage.SketchEntry se = sketches.getSketch(sketchName);
switch (se.family_) {
case UNION:
case HLL:
case CPC:
- result = processDistinctQuery(result, query, se.family_, se.sketch_);
+ result = processDistinctQuery(sketchName, query, se.family_, se.sketch_);
break;
case KLL:
- result = processQuantilesQuery(result, query, se.family_, se.sketch_);
+ result = processQuantilesQuery(sketchName, query, se.family_, se.sketch_);
break;
case FREQUENCY:
- result = processFrequencyQuery(result, query, se.family_, se.sketch_);
+ result = processFrequencyQuery(sketchName, query, se.family_, se.sketch_);
break;
case RESERVOIR:
case VAROPT:
- result = processSamplingQuery(result, query, se.family_, se.sketch_);
+ result = processSamplingQuery(sketchName, query, se.family_, se.sketch_);
break;
default:
@@ -97,23 +93,19 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
return result;
}
- private static boolean checkSummaryFlag(final JsonObject query) {
+ private static boolean checkSummaryFlag(@NonNull final JsonObject query) {
boolean addSummary = false;
- if (query != null && query.has(QUERY_SUMMARY_FIELD)) {
+ if (query.has(QUERY_SUMMARY_FIELD)) {
addSummary = query.get(QUERY_SUMMARY_FIELD).getAsBoolean();
}
return addSummary;
}
- private static JsonObject processDistinctQuery(final JsonObject result, final JsonObject query, final Family type, final Object sketch) {
+ private static JsonObject processDistinctQuery(final String sketchName, final JsonObject query, final Family type, final Object sketch) {
if (query == null || type == null || sketch == null) {
return null;
}
- // check if we need a summary
- final boolean addSummary = checkSummaryFlag(query);
- String summary = null;
-
final double estimate;
final boolean isEstimationMode;
final double p1StdDev;
@@ -134,7 +126,6 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
m1StdDev = thetaSketch.getLowerBound(1);
m2StdDev = thetaSketch.getLowerBound(2);
m3StdDev = thetaSketch.getLowerBound(3);
- if (addSummary) { summary = thetaSketch.toString(); }
break;
case CPC:
@@ -147,7 +138,6 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
m1StdDev = cpcSketch.getLowerBound(1);
m2StdDev = cpcSketch.getLowerBound(2);
m3StdDev = cpcSketch.getLowerBound(3);
- if (addSummary) { summary = cpcSketch.toString(); }
break;
case HLL:
@@ -160,14 +150,14 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
m1StdDev = hllSketch.getLowerBound(1);
m2StdDev = hllSketch.getLowerBound(2);
m3StdDev = hllSketch.getLowerBound(3);
- if (addSummary) { summary = hllSketch.toString(); }
break;
default:
throw new IllegalArgumentException("Unknown distinct counting sketch type: " + type);
}
- //final JsonObject result = new JsonObject();
+ final JsonObject result = new JsonObject();
+ result.addProperty(QUERY_NAME_FIELD, sketchName);
result.addProperty(RESPONSE_ESTIMATE_FIELD, estimate);
result.addProperty(RESPONSE_ESTIMATION_MODE_FIELD, isEstimationMode);
result.addProperty(RESPONSE_P1STDEV_FIELD, p1StdDev);
@@ -176,21 +166,17 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
result.addProperty(RESPONSE_M1STDEV_FIELD, m1StdDev);
result.addProperty(RESPONSE_M2STDEV_FIELD, m2StdDev);
result.addProperty(RESPONSE_M3STDEV_FIELD, m3StdDev);
- if (addSummary)
- result.addProperty(RESPONSE_SUMMARY_FIELD, summary);
+ if (checkSummaryFlag(query))
+ result.addProperty(RESPONSE_SUMMARY_FIELD, sketch.toString());
return result;
}
- private static JsonObject processQuantilesQuery(final JsonObject result, final JsonObject query, final Family type, final Object sketch) {
+ private static JsonObject processQuantilesQuery(final String sketchName, final JsonObject query, final Family type, final Object sketch) {
if (query == null || type == null || sketch == null) {
return null;
}
- // check if we need a summary
- final boolean addSummary = checkSummaryFlag(query);
- String summary = null;
-
final boolean isEstimationMode;
final float maxValue;
final float minValue;
@@ -199,9 +185,11 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
final double[] fractions = getFractionsArray(query);
float[] quantiles = null;
- final float[] values = getValuesArray(query);
- final String resultType = getResultType(query);
+ final float[] pmfValues = getValuesArray(QUERY_PMF_VALUES_FIELD_NAME, query);
+ double[] masses = null;
+ final float[] cdfValues = getValuesArray(QUERY_CDF_VALUES_FIELD_NAME, query);
double[] ranks = null;
+ //final String resultType = getResultType(query);
// since we know REQ is coming
switch (type) {
@@ -212,47 +200,59 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
minValue = kll.getMinValue();
streamLength = kll.getN();
- // TODO: consider valuesPMF vs valuesCDF calls to allow a mix?
- if (values != null) {
- ranks = resultType.equals(QUERY_RESULT_TYPE_CDF) ? kll.getCDF(values) : kll.getPMF(values);
+ if (pmfValues != null) {
+ masses = kll.getPMF(pmfValues);
+ }
+
+ if (cdfValues != null) {
+ ranks = kll.getCDF(cdfValues);
}
if (fractions != null) {
quantiles = kll.getQuantiles(fractions);
}
-
- if (addSummary)
- summary = kll.toString();
break;
default:
throw new SketchesException("processQuantilesQuery() received a non-quantiles sketch: " + type);
}
- //final JsonObject result = new JsonObject();
+ final JsonObject result = new JsonObject();
+ result.addProperty(QUERY_NAME_FIELD, sketchName);
result.addProperty(RESPONSE_STREAM_LENGTH, streamLength);
result.addProperty(RESPONSE_ESTIMATION_MODE_FIELD, isEstimationMode);
result.addProperty(RESPONSE_MIN_VALUE, minValue);
result.addProperty(RESPONSE_MAX_VALUE, maxValue);
if (ranks != null) {
- final String label = resultType.equals(QUERY_RESULT_TYPE_PMF) ? RESPONSE_RESULT_MASS : RESPONSE_RESULT_RANK;
+ //final String label = resultType.equals(QUERY_RESULT_TYPE_PMF) ? RESPONSE_RESULT_MASS : RESPONSE_RESULT_RANK;
final JsonArray rankArray = new JsonArray();
for (int i = 0; i < ranks.length; ++i) {
final JsonObject rankPair = new JsonObject();
- if (i == values.length) {
+ if (i == cdfValues.length) {
rankPair.addProperty(RESPONSE_RESULT_VALUE, maxValue);
} else {
- rankPair.addProperty(RESPONSE_RESULT_VALUE, values[i]);
+ rankPair.addProperty(RESPONSE_RESULT_VALUE, cdfValues[i]);
}
- rankPair.addProperty(label, ranks[i]);
+ rankPair.addProperty(RESPONSE_RESULT_RANK, ranks[i]);
rankArray.add(rankPair);
}
+ result.add(RESPONSE_CDF_LIST, rankArray);
+ }
- if (resultType.equals(QUERY_RESULT_TYPE_CDF))
- result.add(RESPONSE_CDF_LIST, rankArray);
- else
- result.add(RESPONSE_PMF_LIST, rankArray);
+ if (masses != null) {
+ final JsonArray rankArray = new JsonArray();
+ for (int i = 0; i < masses.length; ++i) {
+ final JsonObject rankPair = new JsonObject();
+ if (i == pmfValues.length) {
+ rankPair.addProperty(RESPONSE_RESULT_VALUE, maxValue);
+ } else {
+ rankPair.addProperty(RESPONSE_RESULT_VALUE, pmfValues[i]);
+ }
+ rankPair.addProperty(RESPONSE_RESULT_MASS, masses[i]);
+ rankArray.add(rankPair);
+ }
+ result.add(RESPONSE_PMF_LIST, rankArray);
}
if (quantiles != null) {
@@ -266,8 +266,8 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
result.add(RESPONSE_QUANTILE_LIST, quantileArray);
}
- if (addSummary)
- result.addProperty(RESPONSE_SUMMARY_FIELD, summary);
+ if (checkSummaryFlag(query))
+ result.addProperty(RESPONSE_SUMMARY_FIELD, sketch.toString());
return result;
}
@@ -275,16 +275,13 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
// only one sketch type here so could use ItemsSketch<String> instead of Object, but
// we'll eep the signatures generic here
@SuppressWarnings("unchecked")
- private static JsonObject processFrequencyQuery(final JsonObject result, final JsonObject query, final Family type, final Object sketch) {
+ private static JsonObject processFrequencyQuery(final String sketchName, final JsonObject query, final Family type, final Object sketch) {
if (query == null || type != Family.FREQUENCY || sketch == null) {
return null;
}
final ItemsSketch<String> sk = (ItemsSketch<String>) sketch;
- // check if we need a summary
- final boolean addSummary = checkSummaryFlag(query);
-
if (!query.has(QUERY_ERRORTYPE_FIELD)) {
throw new SketchesException("Must specify a value for " + QUERY_ERRORTYPE_FIELD
+ " for Frequent Items queries");
@@ -312,25 +309,22 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
itemArray.add(row);
}
- //final JsonObject result = new JsonObject();
+ final JsonObject result = new JsonObject();
+ result.addProperty(QUERY_NAME_FIELD, sketchName);
result.add(RESPONSE_ITEMS_ARRAY, itemArray);
- if (addSummary)
+ if (checkSummaryFlag(query))
result.addProperty(RESPONSE_SUMMARY_FIELD, sk.toString());
return result;
}
@SuppressWarnings("unchecked")
- private static JsonObject processSamplingQuery(final JsonObject result, final JsonObject query, final Family type, final Object sketch) {
+ private static JsonObject processSamplingQuery(final String sketchName, final JsonObject query, final Family type, final Object sketch) {
if (query == null || type == null || sketch == null) {
return null;
}
- // check if we need a summary
- final boolean addSummary = checkSummaryFlag(query);
- String summary = null;
-
- final long streamWeight;
+ final long streamLength;
final int k;
final JsonArray itemArray = new JsonArray();
@@ -340,10 +334,8 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
for (final String item : ris.getSamples()) {
itemArray.add(item);
}
- streamWeight = ris.getN();
+ streamLength = ris.getN();
k = ris.getK();
- if (addSummary)
- summary = ris.toString();
break;
case VAROPT:
@@ -354,37 +346,36 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
item.addProperty(RESPONSE_ITEM_WEIGHT, ws.getWeight());
itemArray.add(item);
}
- streamWeight = vis.getN();
+ streamLength = vis.getN();
k = vis.getK();
- if (addSummary)
- summary = vis.toString();
break;
default:
throw new SketchesException("processSamplingQuery() received a non-sampling sketch: " + type);
}
- //final JsonObject result = new JsonObject();
+ final JsonObject result = new JsonObject();
+ result.addProperty(QUERY_NAME_FIELD, sketchName);
result.addProperty(RESPONSE_SKETCH_K, k);
- result.addProperty(RESPONSE_STREAM_WEIGHT, streamWeight);
+ result.addProperty(RESPONSE_STREAM_LENGTH, streamLength);
result.add(RESPONSE_ITEMS_ARRAY, itemArray);
- if (addSummary)
- result.addProperty(RESPONSE_SUMMARY_FIELD, summary);
+ if (checkSummaryFlag(query))
+ result.addProperty(RESPONSE_SUMMARY_FIELD, sketch.toString());
return result;
}
// returns an array of rank points, or null if none in query
- private static float[] getValuesArray(final JsonObject query) {
- if (query == null || !query.has(QUERY_VALUES_FIELD_NAME)) {
+ private static float[] getValuesArray(final String fieldName, final JsonObject query) {
+ if (query == null || !query.has(fieldName)) {
return null;
}
- final JsonArray valuesArray = query.get(QUERY_VALUES_FIELD_NAME).getAsJsonArray();
+ final JsonArray valuesArray = query.get(fieldName).getAsJsonArray();
final float[] values = new float[valuesArray.size()];
for (int i = 0; i < values.length; ++i) {
- if (!valuesArray.get(i).isJsonPrimitive()) {
+ if (!valuesArray.get(i).isJsonPrimitive() || !valuesArray.get(i).getAsJsonPrimitive().isNumber()) {
throw new SketchesException("Invalid value in array. Must be a floating point value, found: " + valuesArray.get(i));
}
values[i] = valuesArray.get(i).getAsFloat();
@@ -393,24 +384,6 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
return values;
}
- // returns QUERY_RESULT_TYPE_PMF if specified in QUERY_RESULT_TYPE_NAME_FIELD, otherwise returns default of
- // QUERY_RESULT_TYPE_CDF
- private static String getResultType(final JsonObject query) {
- if (query == null || !query.has(QUERY_RESULT_TYPE_NAME_FIELD)) {
- return QUERY_RESULT_TYPE_CDF;
- }
-
- final JsonElement resultTypeElement = query.get(QUERY_RESULT_TYPE_NAME_FIELD);
- if (!resultTypeElement.isJsonPrimitive())
- return QUERY_RESULT_TYPE_CDF;
-
- final String resultType = resultTypeElement.getAsString().toLowerCase();
- if (resultType.equals(QUERY_RESULT_TYPE_PMF))
- return QUERY_RESULT_TYPE_PMF;
- else
- return QUERY_RESULT_TYPE_CDF;
- }
-
// returns an array of provided split points, or null if none in query
private static double[] getFractionsArray(final JsonObject query) {
if (query == null || !query.has(QUERY_FRACTIONS_NAME_FIELD)) {
@@ -421,13 +394,16 @@ public class DataQueryHandler extends BaseSketchesQueryHandler {
final double[] fractions = new double[fractionsArray.size()];
for (int i = 0; i < fractions.length; ++i) {
- if (!fractionsArray.get(i).isJsonPrimitive()) {
- throw new SketchesException("Invalid value in array. Must be float in [0.0, 1.0], found: " + fractionsArray.get(i));
+ if (!fractionsArray.get(i).isJsonPrimitive() || !fractionsArray.get(i).getAsJsonPrimitive().isNumber()) {
+ throw new SketchesException("Invalid value in array. Must be a floating point value, found: " + fractionsArray.get(i));
+ }
+ final double value = fractionsArray.get(i).getAsDouble();
+ if (value < 0.0 || value > 1.0) {
+ throw new SketchesException("Invalid value in array. Must be in [0.0, 1.0], found: " + value);
}
- fractions[i] = fractionsArray.get(i).getAsFloat();
+ fractions[i] = value;
}
return fractions;
}
-
}
diff --git a/src/main/java/org/apache/datasketches/server/SketchConstants.java b/src/main/java/org/apache/datasketches/server/SketchConstants.java
index c363148..7dede84 100644
--- a/src/main/java/org/apache/datasketches/server/SketchConstants.java
+++ b/src/main/java/org/apache/datasketches/server/SketchConstants.java
@@ -42,11 +42,9 @@ public final class SketchConstants {
public static final String QUERY_ERRORTYPE_FIELD = "errorType";
public static final String QUERY_ERRORTYPE_NO_FP = "noFalsePositives";
public static final String QUERY_ERRORTYPE_NO_FN = "noFalseNegatives";
- public static final String QUERY_VALUES_FIELD_NAME = "values";
+ public static final String QUERY_PMF_VALUES_FIELD_NAME = "pmfValues";
+ public static final String QUERY_CDF_VALUES_FIELD_NAME = "cdfValues";
public static final String QUERY_FRACTIONS_NAME_FIELD = "fractions";
- public static final String QUERY_RESULT_TYPE_NAME_FIELD = "resultType";
- public static final String QUERY_RESULT_TYPE_PMF = "pmf";
- public static final String QUERY_RESULT_TYPE_CDF = "cdf";
// JSON Query Response Field Names
public static final String RESPONSE_SUMMARY_FIELD = QUERY_SUMMARY_FIELD;
diff --git a/src/test/java/org/apache/datasketches/server/DataQueryHandlerTest.java b/src/test/java/org/apache/datasketches/server/DataQueryHandlerTest.java
new file mode 100644
index 0000000..43922e5
--- /dev/null
+++ b/src/test/java/org/apache/datasketches/server/DataQueryHandlerTest.java
@@ -0,0 +1,433 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.datasketches.server;
+
+import static org.apache.datasketches.server.SketchConstants.QUERY_CDF_VALUES_FIELD_NAME;
+import static org.apache.datasketches.server.SketchConstants.QUERY_ERRORTYPE_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_ERRORTYPE_NO_FN;
+import static org.apache.datasketches.server.SketchConstants.QUERY_ERRORTYPE_NO_FP;
+import static org.apache.datasketches.server.SketchConstants.QUERY_FRACTIONS_NAME_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_NAME_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_PATH;
+import static org.apache.datasketches.server.SketchConstants.QUERY_PMF_VALUES_FIELD_NAME;
+import static org.apache.datasketches.server.SketchConstants.QUERY_SUMMARY_FIELD;
+import static org.apache.datasketches.server.SketchConstants.RESET_PATH;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_CDF_LIST;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_ESTIMATE_FIELD;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_ESTIMATION_MODE_FIELD;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_ITEMS_ARRAY;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_ITEM_ESTIMATE;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_ITEM_LOWER_BOUND;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_ITEM_UPPER_BOUND;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_ITEM_VALUE;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_ITEM_WEIGHT;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_M1STDEV_FIELD;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_M2STDEV_FIELD;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_M3STDEV_FIELD;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_MAX_VALUE;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_MIN_VALUE;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_P1STDEV_FIELD;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_P2STDEV_FIELD;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_P3STDEV_FIELD;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_PMF_LIST;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_QUANTILE_LIST;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_RESULT_MASS;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_RESULT_QUANTILE;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_RESULT_RANK;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_RESULT_VALUE;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_SKETCH_K;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_STREAM_LENGTH;
+import static org.apache.datasketches.server.SketchConstants.RESPONSE_SUMMARY_FIELD;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.apache.datasketches.cpc.CpcSketch;
+import org.apache.datasketches.frequencies.ItemsSketch;
+import org.apache.datasketches.hll.HllSketch;
+import org.apache.datasketches.kll.KllFloatsSketch;
+import org.apache.datasketches.sampling.ReservoirItemsSketch;
+import org.apache.datasketches.sampling.VarOptItemsSketch;
+import org.apache.datasketches.theta.CompactSketch;
+import org.apache.datasketches.theta.Union;
+import org.eclipse.jetty.http.HttpStatus;
+import org.testng.annotations.Test;
+
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+public class DataQueryHandlerTest extends ServerTestBase {
+ @Test
+ public void emptyQuery() {
+ final JsonObject response = new JsonObject();
+ JsonObject request = new JsonObject();
+
+ // completely empty request cannot be handled
+ assertEquals(postData(QUERY_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+ // invalid name field with real sketch name
+ request.addProperty("notAName", "theta0");
+ assertEquals(postData(QUERY_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+ // valid name, empty value
+ request = new JsonObject();
+ request.add(QUERY_NAME_FIELD, new JsonObject());
+ assertEquals(postData(QUERY_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+ // valid name, invalid sketch name
+ request = new JsonObject();
+ request.addProperty(QUERY_NAME_FIELD, "sketchDoesNotExist");
+ assertEquals(postData(QUERY_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+ }
+
+ @Test
+ public void cpcQuery() {
+ final String sketchName = "cpcOfLongs";
+ final JsonObject response = new JsonObject();
+ final JsonObject request = new JsonObject();
+
+ // add data directly to sketch, ensure it exists
+ CpcSketch sk = (CpcSketch) server_.getSketch(sketchName).sketch_;
+ for (int i = 0; i < 5; ++i) { sk.update(i); }
+ assertFalse(sk.isEmpty());
+
+ request.addProperty(QUERY_NAME_FIELD, sketchName);
+ request.addProperty(QUERY_SUMMARY_FIELD, true);
+ assertEquals(getData(QUERY_PATH, request, response), HttpStatus.OK_200);
+
+ // get sketch again before testing
+ sk = (CpcSketch) server_.getSketch(sketchName).sketch_;
+ final JsonObject queryData = response.getAsJsonObject(RESPONSE_FIELD);
+ assertEquals(queryData.get(QUERY_NAME_FIELD).getAsString(), sketchName);
+ // JSON does not guarantee full double precision so allow tolerance
+ assertEquals(queryData.get(RESPONSE_ESTIMATE_FIELD).getAsDouble(), sk.getEstimate());
+ assertTrue(queryData.get(RESPONSE_ESTIMATION_MODE_FIELD).getAsBoolean());
+ assertEquals(queryData.get(RESPONSE_P1STDEV_FIELD).getAsDouble(), sk.getUpperBound(1));
+ assertEquals(queryData.get(RESPONSE_P2STDEV_FIELD).getAsDouble(), sk.getUpperBound(2));
+ assertEquals(queryData.get(RESPONSE_P3STDEV_FIELD).getAsDouble(), sk.getUpperBound(3));
+ assertEquals(queryData.get(RESPONSE_M1STDEV_FIELD).getAsDouble(), sk.getLowerBound(1));
+ assertEquals(queryData.get(RESPONSE_M2STDEV_FIELD).getAsDouble(), sk.getLowerBound(2));
+ assertEquals(queryData.get(RESPONSE_M3STDEV_FIELD).getAsDouble(), sk.getLowerBound(3));
+ assertTrue(queryData.has(RESPONSE_SUMMARY_FIELD));
+ assertFalse(queryData.get(RESPONSE_SUMMARY_FIELD).getAsString().isEmpty());
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void fiQuery() {
+ final String sketchName = "topItems";
+ final JsonObject response = new JsonObject();
+ final JsonObject request = new JsonObject();
+
+ // add data directly to sketch, with a significant weight difference
+ // and enough items that we should trigger a purge
+ final ItemsSketch<String> sk = (ItemsSketch<String>) server_.getSketch(sketchName).sketch_;
+ final int n = 8;
+ for (int i = 0; i < n; ++i)
+ sk.update(Integer.toString(i), Math.round(Math.pow(2, n - i)));
+
+ // missing errorType
+ request.addProperty(QUERY_NAME_FIELD, sketchName);
+ assertEquals(getData(QUERY_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+ // invalid errorType
+ request.addProperty(QUERY_ERRORTYPE_FIELD, "invalid");
+ assertEquals(getData(QUERY_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+ // no false positives, no summary
+ request.addProperty(QUERY_ERRORTYPE_FIELD, QUERY_ERRORTYPE_NO_FP);
+ assertEquals(getData(QUERY_PATH, request, response), HttpStatus.OK_200);
+ JsonObject responseData = response.getAsJsonObject(RESPONSE_FIELD);
+ assertTrue(responseData.has(RESPONSE_ITEMS_ARRAY));
+ final int numNoFPItems = responseData.get(RESPONSE_ITEMS_ARRAY).getAsJsonArray().size();
+ for (final JsonElement elmt : responseData.get(RESPONSE_ITEMS_ARRAY).getAsJsonArray()) {
+ final JsonObject item = elmt.getAsJsonObject();
+ assertTrue(item.has(RESPONSE_ITEM_VALUE));
+ assertTrue(item.has(RESPONSE_ITEM_ESTIMATE));
+ assertTrue(item.has(RESPONSE_ITEM_UPPER_BOUND));
+ assertTrue(item.has(RESPONSE_ITEM_LOWER_BOUND));
+ }
+
+ // no false negatives, with summary
+ request.addProperty(QUERY_ERRORTYPE_FIELD, QUERY_ERRORTYPE_NO_FN);
+ request.addProperty(QUERY_SUMMARY_FIELD, true);
+ assertEquals(getData(QUERY_PATH, request, response), HttpStatus.OK_200);
+ responseData = response.getAsJsonObject(RESPONSE_FIELD);
+ assertTrue(responseData.has(RESPONSE_ITEMS_ARRAY));
+ final int numNoFNItems = responseData.get(RESPONSE_ITEMS_ARRAY).getAsJsonArray().size();
+ for (final JsonElement elmt : responseData.get(RESPONSE_ITEMS_ARRAY).getAsJsonArray()) {
+ final JsonObject item = elmt.getAsJsonObject();
+ assertTrue(item.has(RESPONSE_ITEM_VALUE));
+ assertTrue(item.has(RESPONSE_ITEM_ESTIMATE));
+ assertTrue(item.has(RESPONSE_ITEM_UPPER_BOUND));
+ assertTrue(item.has(RESPONSE_ITEM_LOWER_BOUND));
+ }
+
+ // should be strictly greater by construction
+ assertTrue(numNoFNItems > numNoFPItems);
+ }
+
+ @Test
+ public void hllQuery() {
+ final String sketchName = "hll2";
+ final JsonObject response = new JsonObject();
+ final JsonObject request = new JsonObject();
+
+ HllSketch sk = (HllSketch) server_.getSketch(sketchName).sketch_;
+ sk.update("itemA");
+ sk.update("itemB");
+ assertFalse(sk.isEmpty());
+
+ request.addProperty(QUERY_NAME_FIELD, sketchName);
+ request.addProperty(QUERY_SUMMARY_FIELD, false);
+ assertEquals(getData(QUERY_PATH, request, response), HttpStatus.OK_200);
+
+ // get sketch again before testing
+ sk = (HllSketch) server_.getSketch(sketchName).sketch_;
+ final JsonObject queryData = response.getAsJsonObject(RESPONSE_FIELD);
+ assertEquals(queryData.get(QUERY_NAME_FIELD).getAsString(), sketchName);
+ assertEquals(queryData.get(RESPONSE_ESTIMATE_FIELD).getAsDouble(), sk.getEstimate());
+ assertEquals(queryData.get(RESPONSE_ESTIMATION_MODE_FIELD).getAsBoolean(), sk.isEstimationMode());
+ assertEquals(queryData.get(RESPONSE_P1STDEV_FIELD).getAsDouble(), sk.getUpperBound(1));
+ assertEquals(queryData.get(RESPONSE_P2STDEV_FIELD).getAsDouble(), sk.getUpperBound(2));
+ assertEquals(queryData.get(RESPONSE_P3STDEV_FIELD).getAsDouble(), sk.getUpperBound(3));
+ assertEquals(queryData.get(RESPONSE_M1STDEV_FIELD).getAsDouble(), sk.getLowerBound(1));
+ assertEquals(queryData.get(RESPONSE_M2STDEV_FIELD).getAsDouble(), sk.getLowerBound(2));
+ assertEquals(queryData.get(RESPONSE_M3STDEV_FIELD).getAsDouble(), sk.getLowerBound(3));
+ assertFalse(queryData.has(RESPONSE_SUMMARY_FIELD));
+ }
+
+ @Test
+ public void kllQuery() {
+ final String sketchName = "duration";
+ final JsonObject response = new JsonObject();
+ JsonObject request = new JsonObject();
+
+ // add N(0,1) Gaussian data directly to sketch for non-uniform results
+ final KllFloatsSketch sk = (KllFloatsSketch) server_.getSketch(sketchName).sketch_;
+ final int nPoints = 10000;
+ for (int i = 0; i < nPoints; ++i) {
+ sk.update((float) ThreadLocalRandom.current().nextGaussian());
+ }
+
+ // quantiles and CDF query
+ request.addProperty(QUERY_NAME_FIELD, sketchName);
+
+ final int numCdfPoints = 7;
+ final JsonArray cdfRequestValues = new JsonArray(numCdfPoints);
+ final float[] cdfRequestData = new float[numCdfPoints];
+ int j = 0;
+ for (int i = -3; i <= 3; ++i, ++j) {
+ cdfRequestValues.add(1.0 * i);
+ cdfRequestData[j] = 1.0f * i;
+ }
+ request.add(QUERY_CDF_VALUES_FIELD_NAME, cdfRequestValues);
+
+ // 4 values, should be able to check results against 0
+ final int numFractionsPoints = 4;
+ final JsonArray fractionsRequestValues = new JsonArray(numFractionsPoints);
+ final double[] fractionsRequestData = new double[numFractionsPoints];
+ fractionsRequestValues.add(0.1); fractionsRequestData[0] = 0.1;
+ fractionsRequestValues.add(0.3); fractionsRequestData[1] = 0.3;
+ fractionsRequestValues.add(0.7); fractionsRequestData[2] = 0.7;
+ fractionsRequestValues.add(0.9); fractionsRequestData[3] = 0.9;
+ request.add(QUERY_FRACTIONS_NAME_FIELD, fractionsRequestValues);
+
+ assertEquals(postData(QUERY_PATH, request, response), HttpStatus.OK_200);
+ JsonObject responseData = response.get(RESPONSE_FIELD).getAsJsonObject();
+ assertEquals(responseData.get(QUERY_NAME_FIELD).getAsString(), sketchName);
+ assertEquals(responseData.get(RESPONSE_MIN_VALUE).getAsFloat(), sk.getMinValue(), 1e-12);
+ assertEquals(responseData.get(RESPONSE_MAX_VALUE).getAsFloat(), sk.getMaxValue(), 1e-12);
+ assertTrue(responseData.get(RESPONSE_ESTIMATION_MODE_FIELD).getAsBoolean());
+ assertEquals(responseData.get(RESPONSE_STREAM_LENGTH).getAsLong(), sk.getN());
+
+ // ensure we get the expected values back by comparing results from querying
+ // the sketch directly
+ final double[] ranks = sk.getCDF(cdfRequestData);
+ final JsonArray ranksResult = responseData.get(RESPONSE_CDF_LIST).getAsJsonArray();
+ assertEquals(ranksResult.size(), ranks.length);
+ for (int i = 0; i < numCdfPoints; ++i) {
+ final JsonObject entry = ranksResult.get(i).getAsJsonObject();
+ assertEquals(entry.get(RESPONSE_RESULT_VALUE).getAsFloat(), cdfRequestData[i], 1e-6);
+ assertEquals(entry.get(RESPONSE_RESULT_RANK).getAsFloat(), ranks[i], 1e-6);
+ }
+ // not trying to test the sketch, so we'll assume the last value is correct
+
+ final float[] quantiles = sk.getQuantiles(fractionsRequestData);
+ final JsonArray quantilesResult = responseData.get(RESPONSE_QUANTILE_LIST).getAsJsonArray();
+ assertEquals(quantilesResult.size(), quantiles.length);
+ for (int i = 0; i < numFractionsPoints; ++i) {
+ final JsonObject entry = quantilesResult.get(i).getAsJsonObject();
+ assertEquals(entry.get(RESPONSE_RESULT_RANK).getAsFloat(), fractionsRequestData[i], 1e-6);
+ assertEquals(entry.get(RESPONSE_RESULT_QUANTILE).getAsFloat(), quantiles[i], 1e-6);
+ }
+
+
+ // PMF query (not in above to ensure inputs and outputs properly align)
+ request = new JsonObject();
+ request.addProperty(QUERY_NAME_FIELD, sketchName);
+ request.addProperty(QUERY_SUMMARY_FIELD, true);
+
+ final int numPmfPoints = 5;
+ final JsonArray pmfRequestValues = new JsonArray(numPmfPoints);
+ final float[] pmfRequestData = new float[numPmfPoints];
+ pmfRequestValues.add(-1.5); pmfRequestData[0] = -1.5f;
+ pmfRequestValues.add(-0.5); pmfRequestData[1] = -0.5f;
+ pmfRequestValues.add(0.0); pmfRequestData[2] = 0.0f;
+ pmfRequestValues.add(0.5); pmfRequestData[3] = 0.5f;
+ pmfRequestValues.add(1.5); pmfRequestData[4] = 1.5f;
+ request.add(QUERY_PMF_VALUES_FIELD_NAME, pmfRequestValues);
+
+ assertEquals(postData(QUERY_PATH, request, response), HttpStatus.OK_200);
+ responseData = response.get(RESPONSE_FIELD).getAsJsonObject();
+
+ final double[] mass = sk.getPMF(pmfRequestData);
+ final JsonArray massResult = responseData.get(RESPONSE_PMF_LIST).getAsJsonArray();
+ assertEquals(massResult.size(), mass.length);
+ for (int i = 0; i < numPmfPoints; ++i) {
+ final JsonObject entry = massResult.get(i).getAsJsonObject();
+ assertEquals(entry.get(RESPONSE_RESULT_VALUE).getAsFloat(), pmfRequestData[i], 1e-6);
+ assertEquals(entry.get(RESPONSE_RESULT_MASS).getAsFloat(), mass[i], 1e-6);
+ }
+
+ assertTrue(responseData.has(RESPONSE_SUMMARY_FIELD));
+ }
+
+ @Test
+ public void klllQueryErrors() {
+ final String sketchName = "duration";
+ final JsonObject response = new JsonObject();
+ final JsonObject request = new JsonObject();
+
+ // don't need data in the sketch since we're presenting invalid query parameters
+ request.addProperty(QUERY_NAME_FIELD, sketchName);
+
+ final JsonArray invalidValues = new JsonArray(1);
+ invalidValues.add("Not a number");
+ request.add(QUERY_FRACTIONS_NAME_FIELD, invalidValues);
+ assertEquals(postData(QUERY_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+ request.remove(QUERY_FRACTIONS_NAME_FIELD);
+ request.add(QUERY_PMF_VALUES_FIELD_NAME, invalidValues);
+ assertEquals(postData(QUERY_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+ invalidValues.remove(0);
+ invalidValues.add(-1);
+ request.remove(QUERY_PMF_VALUES_FIELD_NAME);
+ request.add(QUERY_FRACTIONS_NAME_FIELD, invalidValues);
+ assertEquals(postData(QUERY_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+ }
+
+ @Test
+ public void thetaQuery() {
+ final String sketchName = "theta1";
+ final JsonObject response = new JsonObject();
+ final JsonObject request = new JsonObject();
+
+ final Union sk = (Union) server_.getSketch(sketchName).sketch_;
+ sk.update("item");
+ assertFalse(sk.getResult().isEmpty());
+
+ request.addProperty(QUERY_NAME_FIELD, sketchName);
+ assertEquals(getData(QUERY_PATH, request, response), HttpStatus.OK_200);
+
+ // get sketch again before testing
+ final CompactSketch theta = ((Union) server_.getSketch(sketchName).sketch_).getResult();
+ final JsonObject queryData = response.getAsJsonObject(RESPONSE_FIELD);
+ assertEquals(queryData.get(QUERY_NAME_FIELD).getAsString(), sketchName);
+ assertEquals(queryData.get(RESPONSE_ESTIMATE_FIELD).getAsDouble(), theta.getEstimate());
+ assertEquals(queryData.get(RESPONSE_ESTIMATION_MODE_FIELD).getAsBoolean(), theta.isEstimationMode());
+ assertEquals(queryData.get(RESPONSE_P1STDEV_FIELD).getAsDouble(), theta.getUpperBound(1));
+ assertEquals(queryData.get(RESPONSE_P2STDEV_FIELD).getAsDouble(), theta.getUpperBound(2));
+ assertEquals(queryData.get(RESPONSE_P3STDEV_FIELD).getAsDouble(), theta.getUpperBound(3));
+ assertEquals(queryData.get(RESPONSE_M1STDEV_FIELD).getAsDouble(), theta.getLowerBound(1));
+ assertEquals(queryData.get(RESPONSE_M2STDEV_FIELD).getAsDouble(), theta.getLowerBound(2));
+ assertEquals(queryData.get(RESPONSE_M3STDEV_FIELD).getAsDouble(), theta.getLowerBound(3));
+ assertFalse(queryData.has(RESPONSE_SUMMARY_FIELD));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void reservoirQuery() {
+ final String sketchName = "rs";
+ final JsonObject response = new JsonObject();
+ final JsonObject request = new JsonObject();
+
+ // add data directly to sketch, ensure it exists
+ final int nItems = 300;
+ final ReservoirItemsSketch<String> sk = (ReservoirItemsSketch<String>) server_.getSketch(sketchName).sketch_;
+ for (int i = 0; i < nItems; ++i) { sk.update("item" + i); }
+
+ request.addProperty(QUERY_NAME_FIELD, sketchName);
+ request.addProperty(QUERY_SUMMARY_FIELD, true);
+ assertEquals(getData(QUERY_PATH, request, response), HttpStatus.OK_200);
+
+ final JsonObject responseData = response.get(RESPONSE_FIELD).getAsJsonObject();
+ assertEquals(responseData.get(QUERY_NAME_FIELD).getAsString(), sketchName);
+ assertFalse(responseData.get(RESPONSE_SUMMARY_FIELD).getAsString().isEmpty());
+ assertEquals(responseData.get(RESPONSE_SKETCH_K).getAsInt(), sk.getK());
+ assertEquals(responseData.get(RESPONSE_STREAM_LENGTH).getAsLong(), sk.getN());
+ assertEquals(responseData.get(RESPONSE_SKETCH_K).getAsInt(), sk.getK());
+ assertEquals(responseData.get(RESPONSE_ITEMS_ARRAY).getAsJsonArray().size(), Math.min(sk.getN(), sk.getK()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void varOptQuery() {
+ final String sketchName = "vo";
+ final JsonObject response = new JsonObject();
+ final JsonObject request = new JsonObject();
+
+ // add data directly to sketch, ensure it exists
+ final int nItems = 50;
+ final VarOptItemsSketch<String> sk = (VarOptItemsSketch<String>) server_.getSketch(sketchName).sketch_;
+ double cumulativeWeight = 0.0;
+ for (int i = 1; i <= nItems; ++i) { // starting at 1 to avoid a 0 weight
+ final double weight = Math.pow(1.0 * i, 3);
+ cumulativeWeight += weight;
+ sk.update("item", weight);
+ }
+
+ // reset, then check sketch is again empty
+ request.addProperty(QUERY_NAME_FIELD, sketchName);
+ assertEquals(getData(QUERY_PATH, request, response), HttpStatus.OK_200);
+
+ final JsonObject responseData = response.get(RESPONSE_FIELD).getAsJsonObject();
+ assertEquals(responseData.get(QUERY_NAME_FIELD).getAsString(), sketchName);
+ assertEquals(responseData.get(RESPONSE_SKETCH_K).getAsInt(), sk.getK());
+ assertEquals(responseData.get(RESPONSE_STREAM_LENGTH).getAsLong(), sk.getN());
+ assertEquals(responseData.get(RESPONSE_SKETCH_K).getAsInt(), sk.getK());
+
+ final JsonArray itemData = responseData.get(RESPONSE_ITEMS_ARRAY).getAsJsonArray();
+ assertEquals(itemData.size(), Math.min(sk.getN(), sk.getK()));
+ double totalResponseWeight = 0.0;
+ for (final JsonElement elmt : itemData) {
+ final JsonObject obj = elmt.getAsJsonObject();
+ assertTrue(obj.has(RESPONSE_ITEM_VALUE));
+ assertTrue(obj.has(RESPONSE_ITEM_WEIGHT));
+ totalResponseWeight += obj.get(RESPONSE_ITEM_WEIGHT).getAsDouble();
+ }
+ assertEquals(totalResponseWeight, cumulativeWeight, 1e-15);
+ }
+}
diff --git a/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java b/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java
index 5455543..5436bcc 100644
--- a/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java
+++ b/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java
@@ -36,7 +36,6 @@ import org.eclipse.jetty.http.HttpStatus;
import org.testng.annotations.Test;
import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
diff --git a/src/test/resources/test_config.json b/src/test/resources/test_config.json
index 2acd6bd..df1752c 100644
--- a/src/test/resources/test_config.json
+++ b/src/test/resources/test_config.json
@@ -63,7 +63,7 @@
},
{
"name": "topItems",
- "k": "128",
+ "k": "8",
"family": "frequency"
},
{
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datasketches.apache.org
For additional commands, e-mail: commits-help@datasketches.apache.org