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