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/07 01:32:55 UTC

[datasketches-server] branch test_coverage updated: fix errors in merge (incomplete synchronization, incorrect handling of k in some cases), refactor and use imports to improve code readability a bit, create merge tests

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 8b3d633  fix errors in merge (incomplete synchronization, incorrect handling of k in some cases), refactor and use imports to improve code readability a bit, create merge tests
8b3d633 is described below

commit 8b3d63367e52b6f9b0bd1f722bcf3507f1b3db76
Author: Jon Malkin <jm...@users.noreply.github.com>
AuthorDate: Fri Aug 6 18:32:43 2021 -0700

    fix errors in merge (incomplete synchronization, incorrect handling of k in some cases), refactor and use imports to improve code readability a bit, create merge tests
---
 .../apache/datasketches/server/MergeHandler.java   |  68 ++-
 .../datasketches/server/DataQueryHandlerTest.java  |   1 -
 .../datasketches/server/MergeHandlerTest.java      | 512 ++++++++++++++++++++-
 .../datasketches/server/ResetHandlerTest.java      |  14 +-
 .../apache/datasketches/server/ServerTestBase.java |  16 +-
 .../datasketches/server/UpdateHandlerTest.java     |  62 +--
 6 files changed, 597 insertions(+), 76 deletions(-)

diff --git a/src/main/java/org/apache/datasketches/server/MergeHandler.java b/src/main/java/org/apache/datasketches/server/MergeHandler.java
index d75fad1..116febf 100644
--- a/src/main/java/org/apache/datasketches/server/MergeHandler.java
+++ b/src/main/java/org/apache/datasketches/server/MergeHandler.java
@@ -19,6 +19,13 @@
 
 package org.apache.datasketches.server;
 
+import static org.apache.datasketches.server.SketchConstants.QUERY_DATA_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_FAMILY_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_MERGE_K_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_MERGE_SRC_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_MERGE_TGT_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_SKETCH_FIELD;
+
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.HashSet;
@@ -83,7 +90,7 @@ public class MergeHandler extends BaseSketchesQueryHandler {
     // optional targets:
     // If no QUERY_MERGE_TGT_FIELD serialize the result, but then need specify QUERY_MERGE_K_FIELD.
     // If a valid target is present, any value of QUERY_MERGE_K_FIELD is ignored
-    final JsonElement dstElement = query.get(SketchConstants.QUERY_MERGE_TGT_FIELD);
+    final JsonElement dstElement = query.get(QUERY_MERGE_TGT_FIELD);
     final String dst = dstElement != null ? dstElement.getAsString() : null;
     if (dst != null && !sketches.contains(dst)) {
       throw new IllegalArgumentException("Specified target sketch does not exist: " + dst);
@@ -91,15 +98,15 @@ public class MergeHandler extends BaseSketchesQueryHandler {
 
     int k = 0;
     if (dst == null) {
-      final JsonElement kElement = query.get(SketchConstants.QUERY_MERGE_K_FIELD);
-      if (kElement == null) {
-        throw new IllegalArgumentException("Must specify either \"" + SketchConstants.QUERY_MERGE_TGT_FIELD
-            + "\" or \"" + SketchConstants.QUERY_MERGE_K_FIELD + "\". Neither found.");
+      final JsonElement kElement = query.get(QUERY_MERGE_K_FIELD);
+      if (kElement == null || !kElement.isJsonPrimitive() || !kElement.getAsJsonPrimitive().isNumber()) {
+        throw new IllegalArgumentException("Must specify either \"" + QUERY_MERGE_TGT_FIELD
+            + "\" or a numeric value for \"" + QUERY_MERGE_K_FIELD + "\". Neither found.");
       }
       k = kElement.getAsInt();
     }
 
-    final JsonElement srcElement = query.get(SketchConstants.QUERY_MERGE_SRC_FIELD);
+    final JsonElement srcElement = query.get(QUERY_MERGE_SRC_FIELD);
     if (srcElement == null || !srcElement.isJsonArray()) {
       throw new IllegalArgumentException("Merge source data must be a JSON Array");
     }
@@ -110,6 +117,7 @@ public class MergeHandler extends BaseSketchesQueryHandler {
     if (dst != null) {
       se = sketches.getSketch(dst);
       dstFamily = se.family_;
+      k = se.configK_;
     }
 
     // we'll process (and dedup) any stored sketches before we handle encoded inputs
@@ -130,7 +138,7 @@ public class MergeHandler extends BaseSketchesQueryHandler {
     // skBytes == null if merging into another sketch; only non-null if returning a serialized image
     if (skBytes != null) {
       final JsonObject result = new JsonObject();
-      result.addProperty(SketchConstants.QUERY_SKETCH_FIELD, Base64.getUrlEncoder().encodeToString(skBytes));
+      result.addProperty(QUERY_SKETCH_FIELD, Base64.getUrlEncoder().encodeToString(skBytes));
       return result;
     } else {
       return null;
@@ -174,14 +182,14 @@ public class MergeHandler extends BaseSketchesQueryHandler {
       } else { // is JsonObject
         // need special handling for theta as we store Unions?
         final JsonObject sourceObj = elmt.getAsJsonObject();
-        if (!sourceObj.has(SketchConstants.QUERY_FAMILY_FIELD)
-            || !sourceObj.has(SketchConstants.QUERY_DATA_FIELD)) {
+        if (!sourceObj.has(QUERY_FAMILY_FIELD)
+            || !sourceObj.has(QUERY_DATA_FIELD)) {
           throw new SketchesException("Base64 sketch used as merge input must specify both \""
-              + SketchConstants.QUERY_FAMILY_FIELD + "\" and \"" + SketchConstants.QUERY_DATA_FIELD + "\"");
+              + QUERY_FAMILY_FIELD + "\" and \"" + QUERY_DATA_FIELD + "\"");
         }
 
-        final Family skFamily = familyFromString(sourceObj.get(SketchConstants.QUERY_FAMILY_FIELD).getAsString());
-        final String skString = sourceObj.get(SketchConstants.QUERY_DATA_FIELD).getAsString();
+        final Family skFamily = familyFromString(sourceObj.get(QUERY_FAMILY_FIELD).getAsString());
+        final String skString = sourceObj.get(QUERY_DATA_FIELD).getAsString();
         if (skString == null || (family != null
             && ((family != Family.UNION && family != skFamily) || (family == Family.UNION && skFamily != Family.QUICKSELECT)))) {
           throw new SketchesException("Input sketches must exist and be of the same family as the target");
@@ -266,8 +274,10 @@ public class MergeHandler extends BaseSketchesQueryHandler {
         if (dstEntry != null) {
           union.update((HllSketch) dstEntry.sketch_);
         }
-        for (final Object obj : sketchList) {
-          union.update((HllSketch) obj);
+        for (final MergeEntry me : sketchList) {
+          synchronized (me.name_.intern()) {
+            union.update((HllSketch) me.sketch_);
+          }
         }
 
         if (dstEntry == null) {
@@ -283,8 +293,10 @@ public class MergeHandler extends BaseSketchesQueryHandler {
         if (dstEntry != null) {
           union.update((CpcSketch) dstEntry.sketch_);
         }
-        for (final Object obj : sketchList) {
-          union.update((CpcSketch) obj);
+        for (final MergeEntry me : sketchList) {
+          synchronized (me.name_.intern()) {
+            union.update((CpcSketch) me.sketch_);
+          }
         }
 
         if (dstEntry == null) {
@@ -299,8 +311,10 @@ public class MergeHandler extends BaseSketchesQueryHandler {
         // Only merge(), no separate union. Slightly abusing terminology to call it union
         final KllFloatsSketch union = dstEntry == null ? new KllFloatsSketch(k) : (KllFloatsSketch) dstEntry.sketch_;
 
-        for (final Object obj : sketchList) {
-          union.merge((KllFloatsSketch) obj);
+        for (final MergeEntry me : sketchList) {
+          synchronized (me.name_.intern()) {
+            union.merge((KllFloatsSketch) me.sketch_);
+          }
         }
 
         if (dstEntry == null) {
@@ -315,8 +329,10 @@ public class MergeHandler extends BaseSketchesQueryHandler {
         // Only merge(), no separate union. Slightly abusing terminology to call it union
         final ItemsSketch<String> union = dstEntry == null ? new ItemsSketch<>(k) : (ItemsSketch<String>) dstEntry.sketch_;
 
-        for (final Object obj : sketchList) {
-          union.merge((ItemsSketch<String>) obj);
+        for (final MergeEntry me : sketchList) {
+          synchronized (me.name_.intern()) {
+            union.merge((ItemsSketch<String>) me.sketch_);
+          }
         }
 
         if (dstEntry == null) {
@@ -333,8 +349,10 @@ public class MergeHandler extends BaseSketchesQueryHandler {
           union.update((ReservoirItemsSketch<String>) dstEntry.sketch_);
         }
 
-        for (final Object obj : sketchList) {
-          union.update((ReservoirItemsSketch<String>) obj);
+        for (final MergeEntry me : sketchList) {
+          synchronized (me.name_.intern()) {
+            union.update((ReservoirItemsSketch<String>) me.sketch_);
+          }
         }
 
         if (dstEntry == null) {
@@ -351,8 +369,10 @@ public class MergeHandler extends BaseSketchesQueryHandler {
           union.update((VarOptItemsSketch<String>) dstEntry.sketch_);
         }
 
-        for (final Object obj : sketchList) {
-          union.update((VarOptItemsSketch<String>) obj);
+        for (final MergeEntry me : sketchList) {
+          synchronized (me.name_.intern()) {
+            union.update((VarOptItemsSketch<String>) me.sketch_);
+          }
         }
 
         if (dstEntry == null) {
diff --git a/src/test/java/org/apache/datasketches/server/DataQueryHandlerTest.java b/src/test/java/org/apache/datasketches/server/DataQueryHandlerTest.java
index 43922e5..f38e344 100644
--- a/src/test/java/org/apache/datasketches/server/DataQueryHandlerTest.java
+++ b/src/test/java/org/apache/datasketches/server/DataQueryHandlerTest.java
@@ -28,7 +28,6 @@ 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;
diff --git a/src/test/java/org/apache/datasketches/server/MergeHandlerTest.java b/src/test/java/org/apache/datasketches/server/MergeHandlerTest.java
index 291138e..bb3e83a 100644
--- a/src/test/java/org/apache/datasketches/server/MergeHandlerTest.java
+++ b/src/test/java/org/apache/datasketches/server/MergeHandlerTest.java
@@ -1,2 +1,512 @@
-package org.apache.datasketches.server;public class MergeHandlerTest {
+/*
+ * 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.MERGE_PATH;
+import static org.apache.datasketches.server.SketchConstants.QUERY_DATA_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_FAMILY_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_MERGE_K_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_MERGE_SRC_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_MERGE_TGT_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_SKETCH_FIELD;
+import static org.apache.datasketches.server.SketchConstants.SKETCH_FAMILY_CPC;
+import static org.apache.datasketches.server.SketchConstants.SKETCH_FAMILY_FREQUENCY;
+import static org.apache.datasketches.server.SketchConstants.SKETCH_FAMILY_HLL;
+import static org.apache.datasketches.server.SketchConstants.SKETCH_FAMILY_KLL;
+import static org.apache.datasketches.server.SketchConstants.SKETCH_FAMILY_RESERVOIR;
+import static org.apache.datasketches.server.SketchConstants.SKETCH_FAMILY_THETA;
+import static org.apache.datasketches.server.SketchConstants.SKETCH_FAMILY_VAROPT;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Base64;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.apache.datasketches.ArrayOfStringsSerDe;
+import org.apache.datasketches.cpc.CpcSketch;
+import org.apache.datasketches.cpc.CpcUnion;
+import org.apache.datasketches.hll.HllSketch;
+import org.apache.datasketches.kll.KllFloatsSketch;
+import org.apache.datasketches.frequencies.ItemsSketch;
+import org.apache.datasketches.memory.Memory;
+import org.apache.datasketches.sampling.ReservoirItemsSketch;
+import org.apache.datasketches.sampling.ReservoirItemsUnion;
+import org.apache.datasketches.sampling.VarOptItemsSketch;
+import org.apache.datasketches.sampling.VarOptItemsUnion;
+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.JsonObject;
+
+public class MergeHandlerTest extends ServerTestBase {
+  /* Using knowledge of the implementation of merging, much of the merging code is generic
+   * and shared across sketch types. These tests will cover some features only with a single
+   * sketch type (theta) in order to avoid lots of repetitive tests, without sacrificing
+   * functional coverage.
+   */
+
+  @Test
+  public void errorMerges() {
+    final JsonObject response = new JsonObject();
+    JsonObject request = new JsonObject();
+
+    // completely empty request cannot be handled
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+    // invalid name field with real sketch name
+    request.addProperty("notAName", "theta0");
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+    // valid name, empty value
+    request = new JsonObject();
+    request.add(QUERY_MERGE_TGT_FIELD, new JsonObject());
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+    // valid "name" field, invalid sketch name
+    request = new JsonObject();
+    request.addProperty(QUERY_MERGE_TGT_FIELD, "sketchDoesNotExist");
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+    // no name, k is not a number
+    request = new JsonObject();
+    request.addProperty(QUERY_MERGE_K_FIELD, "isNotNumber");
+
+    // valid name with real sketch, no source sketches
+    request.addProperty(QUERY_MERGE_TGT_FIELD, "theta2");
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+    // valid name with real sketch, source is not JsonArray
+    request.addProperty(QUERY_MERGE_SRC_FIELD, "notAnArray");
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+    // serialized input sketch missing family type
+    JsonArray inputSketches = new JsonArray(1);
+    JsonObject sketchInfo = new JsonObject();
+    final CpcSketch cpc = new CpcSketch(10);
+    sketchInfo.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(cpc.toByteArray()));
+    inputSketches.add(sketchInfo);
+    request.add(QUERY_MERGE_SRC_FIELD, inputSketches);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+    // serialized input sketch missing value
+    inputSketches = new JsonArray(1);
+    sketchInfo = new JsonObject();
+    sketchInfo.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_CPC);
+    inputSketches.add(sketchInfo);
+    request.add(QUERY_MERGE_SRC_FIELD, inputSketches);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+    // serialized input with conflicting sketch types
+    inputSketches = new JsonArray(1);
+    sketchInfo.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(cpc.toByteArray()));
+    inputSketches.add(sketchInfo);
+    request.add(QUERY_MERGE_SRC_FIELD, inputSketches);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+
+    // input as existing sketch of wrong family
+    inputSketches = new JsonArray(1);
+    inputSketches.add("cpcOfLongs");
+    request.add(QUERY_MERGE_SRC_FIELD, inputSketches);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.UNPROCESSABLE_ENTITY_422);
+  }
+
+  @Test
+  public void cpcMerge() {
+    final String tgtName = "cpcOfLongs";
+    final JsonObject response = new JsonObject();
+    final JsonObject request = new JsonObject();
+
+    // add data to a couple sketches that we'll pass in via serialization
+    final int n = 5000;
+    final int offset = n / 2;
+    final int k = server_.getSketch(tgtName).configK_;
+    final CpcSketch sk1 = new CpcSketch(k);
+    final CpcSketch sk2 = new CpcSketch(k + 1);
+    for (int i = 0; i < n; ++i) {
+      sk1.update(i);
+      sk2.update(offset + i);
+    }
+    final CpcUnion union = new CpcUnion(k);
+    union.update(sk1);
+    union.update(sk2);
+    final byte[] tgtBytes = union.getResult().toByteArray();
+
+    // add sketches to the request
+    final JsonArray srcSketches = new JsonArray(2);
+
+    final JsonObject serializedSketch1 = new JsonObject();
+    serializedSketch1.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_CPC);
+    serializedSketch1.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk1.toByteArray()));
+    srcSketches.add(serializedSketch1);
+
+    final JsonObject serializedSketch2 = new JsonObject();
+    serializedSketch2.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_CPC);
+    serializedSketch2.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk2.toByteArray()));
+    srcSketches.add(serializedSketch2);
+    request.add(QUERY_MERGE_SRC_FIELD, srcSketches);
+
+    // serialized sketch, so set the k value
+    request.addProperty(QUERY_MERGE_K_FIELD, k);
+
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    final JsonObject responseData = response.getAsJsonObject(RESPONSE_FIELD).getAsJsonObject();
+    assertTrue(responseData.has(QUERY_SKETCH_FIELD));
+    assertEquals(Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString()), tgtBytes);
+
+    // save into a target
+    request.addProperty(QUERY_MERGE_TGT_FIELD, tgtName);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
+    assertEquals(((CpcSketch) server_.getSketch(tgtName).sketch_).toByteArray(), tgtBytes);
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void fiMerge() {
+    final String tgtName = "topItems";
+    final JsonObject response = new JsonObject();
+    final JsonObject request = new JsonObject();
+
+    // add data to a couple sketches that we'll pass in via serialization
+    final int n = 25;
+    final int offset = 5;
+    final int k = server_.getSketch(tgtName).configK_;
+    final ItemsSketch<String> sk1 = new ItemsSketch<>(k * 2);
+    final ItemsSketch<String> sk2 = new ItemsSketch<>(k);
+    for (int i = 1; i <= n; ++i) {
+      sk1.update(Integer.toString(i), i * i);
+      sk2.update(Integer.toString(offset + i), (n - i) * (n - i));
+    }
+    final ItemsSketch<String> union = new ItemsSketch<>(k);
+    union.merge(sk1);
+    union.merge(sk2);
+    final byte[] tgtBytes = union.toByteArray(new ArrayOfStringsSerDe());
+
+    // add sketches to the request
+    final JsonArray srcSketches = new JsonArray(2);
+
+    final JsonObject serializedSketch1 = new JsonObject();
+    serializedSketch1.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_FREQUENCY);
+    serializedSketch1.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk1.toByteArray(new ArrayOfStringsSerDe())));
+    srcSketches.add(serializedSketch1);
+
+    final JsonObject serializedSketch2 = new JsonObject();
+    serializedSketch2.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_FREQUENCY);
+    serializedSketch2.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk2.toByteArray(new ArrayOfStringsSerDe())));
+    srcSketches.add(serializedSketch2);
+    request.add(QUERY_MERGE_SRC_FIELD, srcSketches);
+
+    // serialized sketch, so set the k value
+    request.addProperty(QUERY_MERGE_K_FIELD, k);
+
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    final JsonObject responseData = response.getAsJsonObject(RESPONSE_FIELD).getAsJsonObject();
+    assertTrue(responseData.has(QUERY_SKETCH_FIELD));
+    assertEquals(Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString()), tgtBytes);
+
+    // save into a target
+    request.addProperty(QUERY_MERGE_TGT_FIELD, tgtName);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
+    assertEquals(((ItemsSketch<String>) server_.getSketch(tgtName).sketch_).toByteArray(new ArrayOfStringsSerDe()), tgtBytes);
+  }
+
+  @Test
+  public void hllMerge() {
+    final String tgtName = "hll3";
+    final JsonObject response = new JsonObject();
+    final JsonObject request = new JsonObject();
+
+    // add data to a couple sketches that we'll pass in via serialization
+    final int n = 7500;
+    final int offset = n / 3;
+    final int k = server_.getSketch(tgtName).configK_;
+    final HllSketch sk1 = (HllSketch) server_.getSketch("hll1").sketch_;
+    final HllSketch sk2 = (HllSketch) server_.getSketch("hll2").sketch_;
+    for (int i = 0; i < n; ++i) {
+      sk1.update(i);
+      sk2.update(offset + i);
+    }
+    final org.apache.datasketches.hll.Union union = new org.apache.datasketches.hll.Union(k);
+    union.update(sk1);
+    union.update(sk2);
+    final byte[] tgtBytes = union.getResult().toCompactByteArray();
+
+    // add sketches to the request
+    final JsonArray srcSketches = new JsonArray(2);
+    final JsonObject serializedSketch = new JsonObject();
+    serializedSketch.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_HLL);
+    serializedSketch.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk2.toCompactByteArray()));
+    srcSketches.add(serializedSketch);
+    srcSketches.add("hll1");
+    request.add(QUERY_MERGE_SRC_FIELD, srcSketches);
+
+    // serialized sketch, so set the k value
+    request.addProperty(QUERY_MERGE_K_FIELD, k);
+
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    final JsonObject responseData = response.getAsJsonObject(RESPONSE_FIELD).getAsJsonObject();
+    assertTrue(responseData.has(QUERY_SKETCH_FIELD));
+    assertEquals(Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString()), tgtBytes);
+
+    // save into a target
+    request.remove(QUERY_MERGE_K_FIELD);
+    request.addProperty(QUERY_MERGE_TGT_FIELD, tgtName);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
+    assertEquals(((HllSketch) server_.getSketch(tgtName).sketch_).toCompactByteArray(), tgtBytes);
+
+  }
+
+  @Test
+  public void kllMerge() {
+    final String tgtName = "duration";
+    final JsonObject response = new JsonObject();
+    final JsonObject request = new JsonObject();
+
+    // add data to a couple sketches that we'll pass in via serialization
+    final int n = 6000;
+    final int k = server_.getSketch(tgtName).configK_;
+    final KllFloatsSketch sk1 = new KllFloatsSketch(k + 20);
+    final KllFloatsSketch sk2 = new KllFloatsSketch(k);
+    final ThreadLocalRandom rand = ThreadLocalRandom.current();
+    for (int i = 1; i <= n; ++i) {
+      // unequal number of input points for the sketches
+      sk1.update((float) rand.nextGaussian());
+      sk2.update((float) rand.nextGaussian() + 2);
+      sk2.update((float) rand.nextGaussian() + 3);
+    }
+    final KllFloatsSketch union = new KllFloatsSketch(k);
+    union.merge(sk1);
+    union.merge(sk2);
+
+    // add sketches to the request
+    final JsonArray srcSketches = new JsonArray(2);
+
+    final JsonObject serializedSketch1 = new JsonObject();
+    serializedSketch1.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_KLL);
+    serializedSketch1.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk1.toByteArray()));
+    srcSketches.add(serializedSketch1);
+
+    final JsonObject serializedSketch2 = new JsonObject();
+    serializedSketch2.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_KLL);
+    serializedSketch2.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk2.toByteArray()));
+    srcSketches.add(serializedSketch2);
+    request.add(QUERY_MERGE_SRC_FIELD, srcSketches);
+
+    // serialized sketch, so set the k value
+    request.addProperty(QUERY_MERGE_K_FIELD, k);
+
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    final JsonObject responseData = response.getAsJsonObject(RESPONSE_FIELD).getAsJsonObject();
+    assertTrue(responseData.has(QUERY_SKETCH_FIELD));
+    byte[] skBytes =  Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString());
+    KllFloatsSketch rebuilt = KllFloatsSketch.heapify(Memory.wrap(skBytes));
+    assertEquals(rebuilt.getMinValue(), union.getMinValue());
+    assertEquals(rebuilt.getMaxValue(), union.getMaxValue());
+    assertEquals(rebuilt.getN(), union.getN());
+
+    // save into a target
+    request.addProperty(QUERY_MERGE_TGT_FIELD, tgtName);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
+    skBytes =  Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString());
+    rebuilt = KllFloatsSketch.heapify(Memory.wrap(skBytes));
+    assertEquals(rebuilt.getMinValue(), union.getMinValue());
+    assertEquals(rebuilt.getMaxValue(), union.getMaxValue());
+    assertEquals(rebuilt.getN(), union.getN());
+  }
+
+  @Test
+  public void thetaMerge() {
+    final String tgtName = "theta2";
+    final JsonObject response = new JsonObject();
+    final JsonObject request = new JsonObject();
+
+    // add data to multiple sketches, including one passed in serialized
+    final int n = 5;
+    int idx = 0;
+
+    final int k = server_.getSketch("theta0").configK_;
+    Union sk = (Union) server_.getSketch("theta0").sketch_;
+    final Union tgtUnion = Union.builder().setNominalEntries(1 << k).buildUnion();
+    for (int i = 0; i < n; ++i) {
+      sk.update(idx++);
+    }
+    tgtUnion.union(sk.getResult());
+
+    sk = (Union) server_.getSketch("theta1").sketch_;
+    for (int i = 0; i < n; ++i) {
+      sk.update(idx++);
+    }
+    tgtUnion.union(sk.getResult());
+
+    sk = Union.builder().setNominalEntries(1 << k).buildUnion();
+    for (int i = 0; i < n; ++i) {
+      sk.update(idx++);
+    }
+    final String skEncoded = Base64.getUrlEncoder().encodeToString(sk.getResult().toByteArray());
+
+    // we'll compare the result vs a reference serialized sketch
+    tgtUnion.union(sk.getResult());
+    final byte[] tgtBytes = tgtUnion.getResult().toByteArray();
+
+    final JsonArray srcSketches = new JsonArray(3);
+    srcSketches.add("theta0");
+    srcSketches.add("theta1");
+    final JsonObject serializedSketch = new JsonObject();
+    serializedSketch.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_THETA);
+    serializedSketch.addProperty(QUERY_DATA_FIELD, skEncoded);
+    srcSketches.add(serializedSketch);
+    request.add(QUERY_MERGE_SRC_FIELD, srcSketches);
+
+    // serialized sketch, so set the k value
+    request.addProperty(QUERY_MERGE_K_FIELD, k);
+
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    final JsonObject responseData = response.getAsJsonObject(RESPONSE_FIELD).getAsJsonObject();
+    assertTrue(responseData.has(QUERY_SKETCH_FIELD));
+    assertEquals(Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString()), tgtBytes);
+
+    // save into a target
+    request.addProperty(QUERY_MERGE_TGT_FIELD, tgtName);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
+    assertEquals(((Union) server_.getSketch(tgtName).sketch_).getResult().toByteArray(), tgtBytes);
+  }
+
+  @Test
+  public void reservoirMerge() {
+    final String tgtName = "rs";
+    final JsonObject response = new JsonObject();
+    final JsonObject request = new JsonObject();
+
+    // add data to a couple sketches that we'll pass in via serialization
+    final int k = server_.getSketch(tgtName).configK_;
+    final int n = (k - 1) / 2;
+    final ReservoirItemsSketch<String> sk1 = ReservoirItemsSketch.newInstance(k);
+    final ReservoirItemsSketch<String> sk2 = ReservoirItemsSketch.newInstance(k);
+    for (int i = 0; i < n; ++i) {
+      sk1.update(Integer.toString(i));
+      sk2.update(Integer.toString(n + i));
+    }
+    final ReservoirItemsUnion<String> union = ReservoirItemsUnion.newInstance(k);
+    union.update(sk1);
+    union.update(sk2);
+    final ReservoirItemsSketch<String> result = union.getResult();
+
+    // add sketches to the request
+    final JsonArray srcSketches = new JsonArray(2);
+
+    final JsonObject serializedSketch1 = new JsonObject();
+    serializedSketch1.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_RESERVOIR);
+    serializedSketch1.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk1.toByteArray(new ArrayOfStringsSerDe())));
+    srcSketches.add(serializedSketch1);
+
+    final JsonObject serializedSketch2 = new JsonObject();
+    serializedSketch2.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_RESERVOIR);
+    serializedSketch2.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk2.toByteArray(new ArrayOfStringsSerDe())));
+    srcSketches.add(serializedSketch2);
+    request.add(QUERY_MERGE_SRC_FIELD, srcSketches);
+
+    // serialized sketch, so set the k value
+    request.addProperty(QUERY_MERGE_K_FIELD, k);
+
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    final JsonObject responseData = response.getAsJsonObject(RESPONSE_FIELD).getAsJsonObject();
+    assertTrue(responseData.has(QUERY_SKETCH_FIELD));
+    byte[] skBytes =  Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString());
+    ReservoirItemsSketch<String> rebuilt = ReservoirItemsSketch.heapify(Memory.wrap(skBytes), new ArrayOfStringsSerDe());
+    assertEquals(rebuilt.getN(), result.getN());
+    assertEquals(rebuilt.estimateSubsetSum(x -> true).getTotalSketchWeight(),
+        result.estimateSubsetSum(x -> true).getTotalSketchWeight());
+
+    // save into a target
+    request.addProperty(QUERY_MERGE_TGT_FIELD, tgtName);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
+    skBytes =  Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString());
+    rebuilt = ReservoirItemsSketch.heapify(Memory.wrap(skBytes), new ArrayOfStringsSerDe());
+    assertEquals(rebuilt.getN(), result.getN());
+    assertEquals(rebuilt.estimateSubsetSum(x -> true).getTotalSketchWeight(),
+        result.estimateSubsetSum(x -> true).getTotalSketchWeight());
+  }
+
+  @Test
+  public void varOptMerge() {
+    final String tgtName = "vo";
+    final JsonObject response = new JsonObject();
+    final JsonObject request = new JsonObject();
+
+    // add data to a couple sketches that we'll pass in via serialization
+    final int k = server_.getSketch(tgtName).configK_;
+    final int n = 5 * k;
+    final VarOptItemsSketch<String> sk1 = VarOptItemsSketch.newInstance(k);
+    final VarOptItemsSketch<String> sk2 = VarOptItemsSketch.newInstance(k);
+    final ThreadLocalRandom rand = ThreadLocalRandom.current();
+    for (int i = 0; i < n; ++i) {
+      sk1.update(Integer.toString(i), rand.nextInt(25000));
+      sk2.update(Integer.toString(n + i), rand.nextInt(5000));
+    }
+    final VarOptItemsUnion<String> union = VarOptItemsUnion.newInstance(k);
+    union.update(sk1);
+    union.update(sk2);
+    final VarOptItemsSketch<String> result = union.getResult();
+
+    // add sketches to the request
+    final JsonArray srcSketches = new JsonArray(2);
+
+    final JsonObject serializedSketch1 = new JsonObject();
+    serializedSketch1.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_VAROPT);
+    serializedSketch1.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk1.toByteArray(new ArrayOfStringsSerDe())));
+    srcSketches.add(serializedSketch1);
+
+    final JsonObject serializedSketch2 = new JsonObject();
+    serializedSketch2.addProperty(QUERY_FAMILY_FIELD, SKETCH_FAMILY_VAROPT);
+    serializedSketch2.addProperty(QUERY_DATA_FIELD, Base64.getUrlEncoder().encodeToString(sk2.toByteArray(new ArrayOfStringsSerDe())));
+    srcSketches.add(serializedSketch2);
+    request.add(QUERY_MERGE_SRC_FIELD, srcSketches);
+
+    // serialized sketch, so set the k value
+    request.addProperty(QUERY_MERGE_K_FIELD, k);
+
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    final JsonObject responseData = response.getAsJsonObject(RESPONSE_FIELD).getAsJsonObject();
+    assertTrue(responseData.has(QUERY_SKETCH_FIELD));
+    byte[] skBytes =  Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString());
+    VarOptItemsSketch<String> rebuilt = VarOptItemsSketch.heapify(Memory.wrap(skBytes), new ArrayOfStringsSerDe());
+    assertEquals(rebuilt.getN(), result.getN());
+    assertEquals(rebuilt.estimateSubsetSum(x -> true).getTotalSketchWeight(),
+        result.estimateSubsetSum(x -> true).getTotalSketchWeight());
+
+    // save into a target
+    request.addProperty(QUERY_MERGE_TGT_FIELD, tgtName);
+    assertEquals(postData(MERGE_PATH, request, response), HttpStatus.OK_200);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
+    skBytes =  Base64.getUrlDecoder().decode(responseData.get(QUERY_SKETCH_FIELD).getAsString());
+    rebuilt = VarOptItemsSketch.heapify(Memory.wrap(skBytes), new ArrayOfStringsSerDe());
+    assertEquals(rebuilt.getN(), result.getN());
+    assertEquals(rebuilt.estimateSubsetSum(x -> true).getTotalSketchWeight(),
+        result.estimateSubsetSum(x -> true).getTotalSketchWeight());
+  }
 }
diff --git a/src/test/java/org/apache/datasketches/server/ResetHandlerTest.java b/src/test/java/org/apache/datasketches/server/ResetHandlerTest.java
index 0ead4d2..c2dcaff 100644
--- a/src/test/java/org/apache/datasketches/server/ResetHandlerTest.java
+++ b/src/test/java/org/apache/datasketches/server/ResetHandlerTest.java
@@ -75,7 +75,7 @@ public class ResetHandlerTest extends ServerTestBase {
     // reset, then check sketch is again empty
     request.addProperty(QUERY_NAME_FIELD, sketchName);
     assertEquals(getData(RESET_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // get sketch again before testing
     sk = (CpcSketch) server_.getSketch(sketchName).sketch_;
@@ -98,7 +98,7 @@ public class ResetHandlerTest extends ServerTestBase {
     // reset, then check sketch is again empty
     request.addProperty(QUERY_NAME_FIELD, sketchName);
     assertEquals(getData(RESET_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // get sketch again before testing
     sk = (ItemsSketch<String>) server_.getSketch(sketchName).sketch_;
@@ -120,7 +120,7 @@ public class ResetHandlerTest extends ServerTestBase {
     // reset, then check sketch is again empty
     request.addProperty(QUERY_NAME_FIELD, sketchName);
     assertEquals(getData(RESET_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // get sketch again before testing
     sk = (HllSketch) server_.getSketch(sketchName).sketch_;
@@ -141,7 +141,7 @@ public class ResetHandlerTest extends ServerTestBase {
     // reset, then check sketch is again empty
     request.addProperty(QUERY_NAME_FIELD, sketchName);
     assertEquals(getData(RESET_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // get sketch again before testing
     sk = (KllFloatsSketch) server_.getSketch(sketchName).sketch_;
@@ -162,7 +162,7 @@ public class ResetHandlerTest extends ServerTestBase {
     // reset, then check sketch is again empty
     request.addProperty(QUERY_NAME_FIELD, sketchName);
     assertEquals(getData(RESET_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // get sketch again before testing
     sk = (Union) server_.getSketch(sketchName).sketch_;
@@ -184,7 +184,7 @@ public class ResetHandlerTest extends ServerTestBase {
     // reset, then check sketch is again empty
     request.addProperty(QUERY_NAME_FIELD, sketchName);
     assertEquals(getData(RESET_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // get sketch again before testing
     sk = (ReservoirItemsSketch<String>) server_.getSketch(sketchName).sketch_;
@@ -206,7 +206,7 @@ public class ResetHandlerTest extends ServerTestBase {
     // reset, then check sketch is again empty
     request.addProperty(QUERY_NAME_FIELD, sketchName);
     assertEquals(getData(RESET_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // get sketch again before testing
     sk = (VarOptItemsSketch<String>) server_.getSketch(sketchName).sketch_;
diff --git a/src/test/java/org/apache/datasketches/server/ServerTestBase.java b/src/test/java/org/apache/datasketches/server/ServerTestBase.java
index e6e41bf..dc27f50 100644
--- a/src/test/java/org/apache/datasketches/server/ServerTestBase.java
+++ b/src/test/java/org/apache/datasketches/server/ServerTestBase.java
@@ -118,17 +118,13 @@ public class ServerTestBase {
         try (final InputStreamReader isr = new InputStreamReader(http.getInputStream())) {
           responseData = JsonParser.parseReader(isr);
         }
-        if (!responseData.isJsonNull()) {
-          response.add(RESPONSE_FIELD, responseData);
-        }
+        response.add(RESPONSE_FIELD, responseData);
       } else if (status == HttpStatus.UNPROCESSABLE_ENTITY_422) {
         // read error response and put into a JSON element
         try (final InputStreamReader isr = new InputStreamReader(http.getErrorStream())) {
           responseData = JsonParser.parseReader(isr);
         }
-        if (!responseData.isJsonNull()) {
-          response.add(ERROR_KEY, responseData);
-        }
+        response.add(ERROR_KEY, responseData);
       }
     } catch (final IOException e) {
         fail();
@@ -175,17 +171,13 @@ public class ServerTestBase {
         try (final InputStreamReader isr = new InputStreamReader(http.getInputStream())) {
           responseData = JsonParser.parseReader(isr);
         }
-        if (!responseData.isJsonNull()) {
-          response.add(RESPONSE_FIELD, responseData);
-        }
+        response.add(RESPONSE_FIELD, responseData);
       } else if (status == HttpStatus.UNPROCESSABLE_ENTITY_422) {
         // read error response and put into a JSON element
         try (final InputStreamReader isr = new InputStreamReader(http.getErrorStream())) {
           responseData = JsonParser.parseReader(isr);
         }
-        if (!responseData.isJsonNull()) {
-          response.add(ERROR_KEY, responseData);
-        }
+        response.add(ERROR_KEY, responseData);
       }
     } catch (final IOException e) {
       fail();
diff --git a/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java b/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java
index 5436bcc..ced786b 100644
--- a/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java
+++ b/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java
@@ -74,7 +74,7 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i);
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     request = new JsonObject();
     data = new JsonArray();
@@ -82,13 +82,13 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i);
     request.add(sketchName, data);
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // single-item update
     request = new JsonObject();
     request.add(sketchName, new JsonPrimitive(-1));
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final CpcSketch sk = (CpcSketch) entry.sketch_;
@@ -109,12 +109,12 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i); // not converting to String since should happen upon parsing
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     request = new JsonObject();
     request.add(sketchName, new JsonPrimitive(-1));
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final CpcSketch sk = (CpcSketch) entry.sketch_;
@@ -136,12 +136,12 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i * 10.0);
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     request = new JsonObject();
     request.add(sketchName, new JsonPrimitive(-1));
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final CpcSketch sk = (CpcSketch) entry.sketch_;
@@ -160,7 +160,7 @@ public class UpdateHandlerTest extends ServerTestBase {
     JsonObject request = new JsonObject();
     request.addProperty(sketchName, "item1");
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // item with weight
     request = new JsonObject();
@@ -169,7 +169,7 @@ public class UpdateHandlerTest extends ServerTestBase {
     data.addProperty(QUERY_PAIR_WEIGHT_FIELD, 5);
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // array of items with and without weights
     request = new JsonObject();
@@ -181,7 +181,7 @@ public class UpdateHandlerTest extends ServerTestBase {
     dataArray.add(data);
     request.add(sketchName, dataArray);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final org.apache.datasketches.frequencies.ItemsSketch<String> sk = (org.apache.datasketches.frequencies.ItemsSketch<String>) entry.sketch_;
@@ -216,12 +216,12 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i); // not converting to String since should happen upon parsing
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     request = new JsonObject();
     request.add(sketchName, new JsonPrimitive(-1));
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final HllSketch sk = (HllSketch) entry.sketch_;
@@ -242,7 +242,7 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i);
     request.add(sketchName, data);
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     request = new JsonObject();
     data = new JsonArray();
@@ -250,13 +250,13 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i);
     request.add(sketchName, data);
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // single-item update
     request = new JsonObject();
     request.add(sketchName, new JsonPrimitive(-1));
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final HllSketch sk = (HllSketch) entry.sketch_;
@@ -277,12 +277,12 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i * 10.0);
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     request = new JsonObject();
     request.add(sketchName, new JsonPrimitive(-1));
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final HllSketch sk = (HllSketch) entry.sketch_;
@@ -306,13 +306,13 @@ public class UpdateHandlerTest extends ServerTestBase {
         data.add(i * 10.0);
       request.add(sketchName, data);
       assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-      assertEquals(response.size(), 0);
+      assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
       // single update
       request = new JsonObject();
       request.add(sketchName, new JsonPrimitive(-1));
       assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-      assertEquals(response.size(), 0);
+      assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
     }
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
@@ -332,7 +332,7 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i);
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     request = new JsonObject();
     data = new JsonArray();
@@ -340,13 +340,13 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i);
     request.add(sketchName, data);
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // single-item update
     request = new JsonObject();
     request.add(sketchName, new JsonPrimitive(-1));
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final CompactSketch sk = ((Union) entry.sketch_).getResult();
@@ -367,12 +367,12 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i); // not converting to String since should happen upon parsing
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     request = new JsonObject();
     request.add(sketchName, new JsonPrimitive(-1));
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final CompactSketch sk = ((Union) entry.sketch_).getResult();
@@ -393,12 +393,12 @@ public class UpdateHandlerTest extends ServerTestBase {
       data.add(i * 10.0);
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     request = new JsonObject();
     request.add(sketchName, new JsonPrimitive(-1));
     assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final CompactSketch sk = ((Union) entry.sketch_).getResult();
@@ -423,13 +423,13 @@ public class UpdateHandlerTest extends ServerTestBase {
         data.add(Integer.toString(i));
       request.add(sketchName, data);
       assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-      assertEquals(response.size(), 0);
+      assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
       // single update
       request = new JsonObject();
       request.add(sketchName, new JsonPrimitive("-1"));
       assertEquals(getData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-      assertEquals(response.size(), 0);
+      assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
     }
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
@@ -447,7 +447,7 @@ public class UpdateHandlerTest extends ServerTestBase {
     JsonObject request = new JsonObject();
     request.addProperty(sketchName, "item1");
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // item with weight
     request = new JsonObject();
@@ -456,7 +456,7 @@ public class UpdateHandlerTest extends ServerTestBase {
     data.addProperty(QUERY_PAIR_WEIGHT_FIELD, 5);
     request.add(sketchName, data);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     // array of items with and without weights
     request = new JsonObject();
@@ -468,7 +468,7 @@ public class UpdateHandlerTest extends ServerTestBase {
     dataArray.add(data);
     request.add(sketchName, dataArray);
     assertEquals(postData(UPDATE_PATH, request, response), HttpStatus.OK_200);
-    assertEquals(response.size(), 0);
+    assertTrue(response.get(RESPONSE_FIELD).isJsonNull());
 
     final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
     final VarOptItemsSketch<String> sk = (VarOptItemsSketch<String>) entry.sketch_;

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datasketches.apache.org
For additional commands, e-mail: commits-help@datasketches.apache.org