Merge pull request #28 from DataSketches/hll-udfs

HLL sketch UDFs
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/DataToSketchUDAF.java b/src/main/java/com/yahoo/sketches/hive/hll/DataToSketchUDAF.java
new file mode 100644
index 0000000..6733f7f
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/DataToSketchUDAF.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import java.util.Arrays;
+
+import org.apache.hadoop.hive.ql.exec.Description;
+import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
+import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
+import org.apache.hadoop.hive.ql.metadata.HiveException;
+import org.apache.hadoop.hive.ql.parse.SemanticException;
+import org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorUtils;
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory;
+import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorUtils;
+
+import com.yahoo.sketches.hll.TgtHllType;
+
+/**
+ * Hive UDAF to create an HllSketch from raw data.
+ */
+@Description(
+    name = "dataToSketch",
+    value = "_FUNC_(expr, lgK, type) - "
+        + "Compute a sketch on data 'expr' with given parameters lgK and target type",
+    extended = "Example:\n"
+    + "> SELECT dataToSketch(val, 12) FROM src;\n"
+    + "The return value is a binary blob that can be operated on by other sketch related functions."
+    + " The lgK parameter controls the sketch size and rlative error expected from the sketch."
+    + " It is optional an must be from 4 to 21. The default is 12, which is expected to yield errors"
+    + " of roughly +-3% in the estimation of uniques with 95% confidence."
+    + " The target type parameter is optional and must be 'HLL_4', 'HLL_6' or 'HLL_8'."
+    + " The default is 'HLL_4'")
+public class DataToSketchUDAF extends AbstractGenericUDAFResolver {
+
+  /**
+   * Performs argument number and type validation. DataToSketch expects
+   * to receive between one and three arguments.
+   * <ul>
+   * <li>The first (required) is the value to add to the sketch and must be a primitive.</li>
+   *
+   * <li>The second (optional) is the lgK from 4 to 21 (default 12).
+   * This must be an integral value and must be constant.</li>
+   *
+   * <li>The third (optional) is the target HLL type and must be a string 'HLL_4',
+   * 'HLL_6' or 'HLL_8' (default 'HLL_4').</li>
+   * </ul>
+   *
+   * @see org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver
+   * #getEvaluator(org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo)
+   *
+   * @param info Parameter info to validate
+   * @return The GenericUDAFEvaluator that should be used to calculate the function.
+   */
+  @Override
+  public GenericUDAFEvaluator getEvaluator(final GenericUDAFParameterInfo info) throws SemanticException {
+    final ObjectInspector[] inspectors = info.getParameterObjectInspectors();
+
+    // Validate the correct number of parameters
+    if (inspectors.length < 1) {
+      throw new UDFArgumentException("Please specify at least 1 argument");
+    }
+
+    if (inspectors.length > 3) {
+      throw new UDFArgumentException("Please specify no more than 3 arguments");
+    }
+
+    // Validate first parameter type
+    ObjectInspectorValidator.validateCategoryPrimitive(inspectors[0], 0);
+
+    // Validate second argument if present
+    if (inspectors.length > 1) {
+      ObjectInspectorValidator.validateIntegralParameter(inspectors[1], 1);
+      if (!ObjectInspectorUtils.isConstantObjectInspector(inspectors[1])) {
+        throw new UDFArgumentTypeException(1, "The second argument must be a constant");
+      }
+    }
+
+    // Validate third argument if present
+    if (inspectors.length > 2) {
+      ObjectInspectorValidator.validateGivenPrimitiveCategory(inspectors[2], 2, PrimitiveCategory.STRING);
+      if (!ObjectInspectorUtils.isConstantObjectInspector(inspectors[2])) {
+        throw new UDFArgumentTypeException(2, "The third argument must be a constant");
+      }
+    }
+
+    return new DataToSketchEvaluator();
+  }
+
+  public static class DataToSketchEvaluator extends SketchEvaluator {
+
+    private Mode mode_;
+
+    @Override
+    public AggregationBuffer getNewAggregationBuffer() throws HiveException {
+      if (mode_ == Mode.PARTIAL1 || mode_ == Mode.COMPLETE) {
+        return new SketchState();
+      }
+      return new UnionState();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see
+     * org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator#init(org.apache
+     * .hadoop.hive.ql.udf.generic.GenericUDAFEvaluator.Mode,
+     * org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector[])
+     */
+    @Override
+    public ObjectInspector init(final Mode mode, final ObjectInspector[] parameters) throws HiveException {
+      super.init(mode, parameters);
+      mode_ = mode;
+      if (mode == Mode.PARTIAL1 || mode == Mode.COMPLETE) {
+        // input is original data
+        inputInspector_ = (PrimitiveObjectInspector) parameters[0];
+        if (parameters.length > 1) {
+          lgKInspector_ = (PrimitiveObjectInspector) parameters[1];
+        }
+        if (parameters.length > 2) {
+          hllTypeInspector_ = (PrimitiveObjectInspector) parameters[2];
+        }
+      } else {
+        // input for PARTIAL2 and FINAL is the output from PARTIAL1
+        intermediateInspector_ = (StructObjectInspector) parameters[0];
+      }
+
+      if (mode == Mode.PARTIAL1 || mode == Mode.PARTIAL2) {
+        // intermediate results need to include the lgK and the target HLL type
+        return ObjectInspectorFactory.getStandardStructObjectInspector(
+          Arrays.asList(LG_K_FIELD, HLL_TYPE_FIELD, SKETCH_FIELD),
+          Arrays.asList(
+            PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.INT),
+            PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.STRING),
+            PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.BINARY)
+          )
+        );
+      }
+      // final results include just the sketch
+      return PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.BINARY);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see
+     * org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator#iterate(org
+     * .apache
+     * .hadoop.hive.ql.udf.generic.GenericUDAFEvaluator.AggregationBuffer,
+     * java.lang.Object[])
+     */
+    @Override
+    public void iterate(final @SuppressWarnings("deprecation") AggregationBuffer agg,
+        final Object[] parameters) throws HiveException {
+      if (parameters[0] == null) { return; }
+      final SketchState state = (SketchState) agg;
+      if (!state.isInitialized()) {
+        initializeState(state, parameters);
+      }
+      state.update(parameters[0], inputInspector_);
+    }
+
+    private void initializeState(final SketchState state, final Object[] parameters) {
+      int lgK = DEFAULT_LG_K;
+      if (lgKInspector_ != null) {
+        lgK = PrimitiveObjectInspectorUtils.getInt(parameters[1], lgKInspector_);
+      }
+      TgtHllType type = DEFAULT_HLL_TYPE;
+      if (hllTypeInspector_ != null) {
+        type = TgtHllType.valueOf(PrimitiveObjectInspectorUtils.getString(parameters[2], hllTypeInspector_));
+      }
+      state.init(lgK, type);
+    }
+
+  }
+
+}
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/ObjectInspectorValidator.java b/src/main/java/com/yahoo/sketches/hive/hll/ObjectInspectorValidator.java
new file mode 100644
index 0000000..0d04b2e
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/ObjectInspectorValidator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
+
+class ObjectInspectorValidator {
+
+  static void validateCategoryPrimitive(final ObjectInspector inspector, final int index)
+      throws UDFArgumentTypeException {
+    if (inspector.getCategory() != ObjectInspector.Category.PRIMITIVE) {
+      throw new UDFArgumentTypeException(index, "Primitive parameter expected, but "
+          + inspector.getCategory().name() + " was recieved as parameter " + (index + 1));
+    }
+  }
+
+  static void validateGivenPrimitiveCategory(final ObjectInspector inspector, final int index,
+      final PrimitiveObjectInspector.PrimitiveCategory category) throws UDFArgumentTypeException
+  {
+    validateCategoryPrimitive(inspector, index);
+    final PrimitiveObjectInspector primitiveInspector = (PrimitiveObjectInspector) inspector;
+    if (primitiveInspector.getPrimitiveCategory() != category) {
+      throw new UDFArgumentTypeException(index, category.name() + " value expected as parameter "
+          + (index + 1) + " but " + primitiveInspector.getPrimitiveCategory().name() + " was received");
+    }
+  }
+
+  static void validateIntegralParameter(final ObjectInspector inspector, final int index)
+      throws UDFArgumentTypeException {
+    validateCategoryPrimitive(inspector, index);
+    final PrimitiveObjectInspector primitiveInspector = (PrimitiveObjectInspector) inspector;
+    switch (primitiveInspector.getPrimitiveCategory()) {
+    case BYTE:
+    case SHORT:
+    case INT:
+    case LONG:
+      break;
+    // all other types are invalid
+    default:
+      throw new UDFArgumentTypeException(index, "Only integral type parameters are expected but "
+          + primitiveInspector.getPrimitiveCategory().name() + " was passed as parameter " + (index + 1));
+    }
+  }
+
+}
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/SketchEvaluator.java b/src/main/java/com/yahoo/sketches/hive/hll/SketchEvaluator.java
new file mode 100644
index 0000000..94d075e
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/SketchEvaluator.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import java.util.Arrays;
+
+import org.apache.hadoop.hive.ql.metadata.HiveException;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
+import org.apache.hadoop.io.BytesWritable;
+import org.apache.hadoop.io.IntWritable;
+import org.apache.hadoop.io.Text;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.hll.HllSketch;
+import com.yahoo.sketches.hll.TgtHllType;
+
+// This class implements functionality common to DataToSketch and UnionSketch
+
+abstract class SketchEvaluator extends GenericUDAFEvaluator {
+
+  static final int DEFAULT_LG_K = 12;
+  static final TgtHllType DEFAULT_HLL_TYPE = TgtHllType.HLL_4;
+
+  protected static final String LG_K_FIELD = "lgK";
+  protected static final String HLL_TYPE_FIELD = "type";
+  protected static final String SKETCH_FIELD = "sketch";
+
+  protected PrimitiveObjectInspector inputInspector_;
+  protected PrimitiveObjectInspector lgKInspector_;
+  protected PrimitiveObjectInspector hllTypeInspector_;
+  protected StructObjectInspector intermediateInspector_;
+
+  @Override
+  public Object terminatePartial(final @SuppressWarnings("deprecation") AggregationBuffer buf)
+      throws HiveException {
+    final State state = (State) buf;
+    final HllSketch intermediate = state.getResult();
+    if (intermediate == null) { return null; }
+    final byte[] bytes = intermediate.toCompactByteArray();
+    return Arrays.asList(
+      new IntWritable(state.getLgK()),
+      new Text(state.getType().toString()),
+      new BytesWritable(bytes)
+    );
+  }
+
+  @Override
+  public void merge(final @SuppressWarnings("deprecation") AggregationBuffer buf, final Object data)
+      throws HiveException {
+    if (data == null) { return; }
+    final UnionState state = (UnionState) buf;
+    if (!state.isInitialized()) {
+      initializeState(state, data);
+    }
+    final BytesWritable serializedSketch = (BytesWritable) intermediateInspector_.getStructFieldData(
+        data, intermediateInspector_.getStructFieldRef(SKETCH_FIELD));
+    state.update(HllSketch.heapify(Memory.wrap(serializedSketch.getBytes())));
+  }
+
+  private void initializeState(final UnionState state, final Object data) {
+    final int lgK = ((IntWritable) intermediateInspector_.getStructFieldData(
+        data, intermediateInspector_.getStructFieldRef(LG_K_FIELD))).get();
+    final TgtHllType type = TgtHllType.valueOf(((Text) intermediateInspector_.getStructFieldData(
+        data, intermediateInspector_.getStructFieldRef(HLL_TYPE_FIELD))).toString());
+    state.init(lgK, type);
+  }
+
+  @Override
+  public Object terminate(final @SuppressWarnings("deprecation") AggregationBuffer buf)
+      throws HiveException {
+    final State state = (State) buf;
+    if (state == null) { return null; }
+    final HllSketch result = state.getResult();
+    if (result == null) { return null; }
+    return new BytesWritable(result.toCompactByteArray());
+  }
+
+  @Override
+  public void reset(@SuppressWarnings("deprecation") final AggregationBuffer buf)
+      throws HiveException {
+    final State state = (State) buf;
+    state.reset();
+  }
+
+}
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/SketchState.java b/src/main/java/com/yahoo/sketches/hive/hll/SketchState.java
new file mode 100644
index 0000000..9c190b4
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/SketchState.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorUtils;
+
+import com.yahoo.sketches.hll.HllSketch;
+import com.yahoo.sketches.hll.TgtHllType;
+
+class SketchState extends State {
+
+  private HllSketch sketch_;
+
+  boolean isInitialized() {
+    return sketch_ != null;
+  }
+
+  @Override
+  void init(final int logK, final TgtHllType type) {
+    super.init(logK, type);
+    sketch_ = new HllSketch(logK, type);
+  }
+
+  void update(final Object data, final PrimitiveObjectInspector keyObjectInspector) {
+    switch (keyObjectInspector.getPrimitiveCategory()) {
+    case BINARY:
+      sketch_.update(PrimitiveObjectInspectorUtils.getBinary(data, keyObjectInspector).getBytes());
+      return;
+    case BYTE:
+      sketch_.update(PrimitiveObjectInspectorUtils.getByte(data, keyObjectInspector));
+      return;
+    case DOUBLE:
+      sketch_.update(PrimitiveObjectInspectorUtils.getDouble(data, keyObjectInspector));
+      return;
+    case FLOAT:
+      sketch_.update(PrimitiveObjectInspectorUtils.getFloat(data, keyObjectInspector));
+      return;
+    case INT:
+      sketch_.update(PrimitiveObjectInspectorUtils.getInt(data, keyObjectInspector));
+      return;
+    case LONG:
+      sketch_.update(PrimitiveObjectInspectorUtils.getLong(data, keyObjectInspector));
+      return;
+    case STRING:
+      sketch_.update(PrimitiveObjectInspectorUtils.getString(data, keyObjectInspector));
+      return;
+    default:
+      throw new IllegalArgumentException(
+          "Unrecongnized input data type, please use data of type: "
+      + "byte, double, float, int, long, or string only.");
+    }
+  }
+
+  @Override
+  HllSketch getResult() {
+    if (sketch_ == null) { return null; }
+    return sketch_;
+  }
+
+  @Override
+  void reset() {
+    sketch_ = null;
+  }
+
+}
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/SketchToEstimateUDF.java b/src/main/java/com/yahoo/sketches/hive/hll/SketchToEstimateUDF.java
new file mode 100644
index 0000000..ea193bb
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/SketchToEstimateUDF.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import org.apache.hadoop.hive.ql.exec.Description;
+import org.apache.hadoop.hive.ql.exec.UDF;
+import org.apache.hadoop.io.BytesWritable;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.hll.HllSketch;
+
+@Description(
+    name = "SketchToEstimate",
+    value = "_FUNC_(sketch)",
+    extended = "Returns an estimate of unique count from a given HllSketch."
+    + " The result is a double value.")
+public class SketchToEstimateUDF extends UDF {
+
+  /**
+   * Get an estimate from a given HllSketch
+   * @param serializedSketch HllSketch in a serialized binary form
+   * @return estimate of unique count
+   */
+  public Double evaluate(final BytesWritable serializedSketch) {
+    if (serializedSketch == null) { return null; }
+    final HllSketch sketch = HllSketch.heapify(Memory.wrap(serializedSketch.getBytes()));
+    return sketch.getEstimate();
+  }
+
+}
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/State.java b/src/main/java/com/yahoo/sketches/hive/hll/State.java
new file mode 100644
index 0000000..226d7bd
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/State.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator.AbstractAggregationBuffer;
+
+import com.yahoo.sketches.hll.HllSketch;
+import com.yahoo.sketches.hll.TgtHllType;
+
+abstract class State extends AbstractAggregationBuffer {
+
+  private int lgK_;
+  private TgtHllType type_;
+
+  void init(final int lgK, final TgtHllType type) {
+    lgK_ = lgK;
+    type_ = type;
+  }
+
+  int getLgK() {
+    return lgK_;
+  }
+
+  TgtHllType getType() {
+    return type_;
+  }
+
+  abstract HllSketch getResult();
+
+  abstract void reset();
+
+}
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/UnionSketchUDAF.java b/src/main/java/com/yahoo/sketches/hive/hll/UnionSketchUDAF.java
new file mode 100644
index 0000000..fca0e81
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/UnionSketchUDAF.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import java.util.Arrays;
+
+import org.apache.hadoop.hive.ql.exec.Description;
+import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
+import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
+import org.apache.hadoop.hive.ql.metadata.HiveException;
+import org.apache.hadoop.hive.ql.parse.SemanticException;
+import org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorUtils;
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory;
+import org.apache.hadoop.hive.serde2.objectinspector.StandardStructObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorUtils;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.hll.HllSketch;
+import com.yahoo.sketches.hll.TgtHllType;
+
+/**
+ * Hive UDAF to compute union of HllSketch objects
+ */
+@Description(
+    name = "unionSketch",
+    value = "_FUNC_(sketch, lgK, type) - Compute the union of sketches of given size and seed",
+    extended = "Example:\n"
+    + "> SELECT UnionSketch(sketch) FROM src;\n"
+    + "The return value is a binary blob that can be operated on by other sketch related functions."
+    + " The lgK parameter controls the sketch size and rlative error expected from the sketch."
+    + " It is optional an must be from 4 to 21. The default is 12, which is expected to yield errors"
+    + " of roughly +-3% in the estimation of uniques with 95% confidence."
+    + " The target type parameter is optional and must be 'HLL_4', 'HLL_6' or 'HLL_8'."
+    + " The default is 'HLL_4'")
+public class UnionSketchUDAF extends AbstractGenericUDAFResolver {
+
+  /**
+   * Perform argument count check and argument type checking, returns an
+   * appropriate evaluator to perform based on input type (which should always
+   * be BINARY sketch). Also check lgK and target HLL type parameters if they are passed in.
+   *
+   * @see org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver
+   * #getEvaluator(org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo)
+   *
+   * @param info The parameter info to validate
+   * @return The GenericUDAFEvaluator to use to compute the function.
+   */
+  @Override
+  public GenericUDAFEvaluator getEvaluator(final GenericUDAFParameterInfo info) throws SemanticException {
+    final ObjectInspector[] inspectors = info.getParameterObjectInspectors();
+
+    if (inspectors.length < 1) {
+      throw new UDFArgumentException("Please specify at least 1 argument");
+    }
+
+    if (inspectors.length > 3) {
+      throw new UDFArgumentTypeException(inspectors.length - 1, "Please specify no more than 3 arguments");
+    }
+
+    ObjectInspectorValidator.validateGivenPrimitiveCategory(inspectors[0], 0, PrimitiveCategory.BINARY);
+
+    // Validate second argument if present
+    if (inspectors.length > 1) {
+      ObjectInspectorValidator.validateIntegralParameter(inspectors[1], 1);
+      if (!ObjectInspectorUtils.isConstantObjectInspector(inspectors[1])) {
+        throw new UDFArgumentTypeException(1, "The second argument must be a constant");
+      }
+    }
+
+    // Validate third argument if present
+    if (inspectors.length > 2) {
+      ObjectInspectorValidator.validateGivenPrimitiveCategory(inspectors[2], 2, PrimitiveCategory.STRING);
+      if (!ObjectInspectorUtils.isConstantObjectInspector(inspectors[2])) {
+        throw new UDFArgumentTypeException(2, "The third argument must be a constant");
+      }
+    }
+
+    return new UnionSketchUDAFEvaluator();
+  }
+
+  /**
+   * Evaluator class, main logic of our UDAF.
+   *
+   */
+  public static class UnionSketchUDAFEvaluator extends SketchEvaluator {
+
+    @Override
+    public AggregationBuffer getNewAggregationBuffer() throws HiveException {
+      return new UnionState();
+    }
+
+    /**
+     * Receives the passed in argument object inspectors and returns the desired
+     * return type's object inspector to inform hive of return type of UDAF.
+     *
+     * @param mode
+     *          Mode (i.e. PARTIAL 1, COMPLETE...) for determining input and output
+     *          object inspector type.
+     * @param parameters
+     *          List of object inspectors for input arguments.
+     * @return The object inspector type indicates the UDAF return type (i.e.
+     *         returned type of terminate(...)).
+     */
+    @Override
+    public ObjectInspector init(final Mode mode, final ObjectInspector[] parameters) throws HiveException {
+      super.init(mode, parameters);
+
+      if (mode == Mode.PARTIAL1 || mode == Mode.COMPLETE) {
+        inputInspector_ = (PrimitiveObjectInspector) parameters[0];
+        if (parameters.length > 1) {
+          lgKInspector_ = (PrimitiveObjectInspector) parameters[1];
+        }
+        if (parameters.length > 2) {
+          hllTypeInspector_ = (PrimitiveObjectInspector) parameters[2];
+        }
+      } else {
+        // mode = partial2 || final
+        intermediateInspector_ = (StandardStructObjectInspector) parameters[0];
+      }
+
+      if (mode == Mode.PARTIAL1 || mode == Mode.PARTIAL2) {
+        // intermediate results need to include the lgK and the target HLL type
+        return ObjectInspectorFactory.getStandardStructObjectInspector(
+          Arrays.asList(LG_K_FIELD, HLL_TYPE_FIELD, SKETCH_FIELD),
+          Arrays.asList(
+            PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.INT),
+            PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.STRING),
+            PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.BINARY)
+          )
+        );
+      }
+      // final results include just the sketch
+      return PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.BINARY);
+    }
+
+    /**
+     * Add the incoming sketch into the internal state.
+     *
+     * @param buf
+     *          aggregation buffer storing intermediate results.
+     * @param parameters
+     *          sketches in the form of Object passed in to be merged.
+     */
+    @Override
+    public void iterate(final @SuppressWarnings("deprecation") AggregationBuffer buf,
+        final Object[] parameters) throws HiveException {
+      if (parameters[0] == null) { return; }
+      final UnionState state = (UnionState) buf;
+      if (!state.isInitialized()) {
+        initializeState(state, parameters);
+      }
+      final byte[] serializedSketch = (byte[]) inputInspector_.getPrimitiveJavaObject(parameters[0]);
+      if (serializedSketch == null) { return; }
+      state.update(HllSketch.heapify(Memory.wrap(serializedSketch)));
+    }
+
+    private void initializeState(final UnionState state, final Object[] parameters) {
+      int lgK = DEFAULT_LG_K;
+      if (lgKInspector_ != null) {
+        lgK = PrimitiveObjectInspectorUtils.getInt(parameters[1], lgKInspector_);
+      }
+      TgtHllType type = DEFAULT_HLL_TYPE;
+      if (hllTypeInspector_ != null) {
+        type = TgtHllType.valueOf(PrimitiveObjectInspectorUtils.getString(parameters[2], hllTypeInspector_));
+      }
+      state.init(lgK, type);
+    }
+
+  }
+
+}
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/UnionSketchUDF.java b/src/main/java/com/yahoo/sketches/hive/hll/UnionSketchUDF.java
new file mode 100644
index 0000000..21de859
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/UnionSketchUDF.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import org.apache.hadoop.hive.ql.exec.UDF;
+import org.apache.hadoop.io.BytesWritable;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.hll.HllSketch;
+import com.yahoo.sketches.hll.TgtHllType;
+import com.yahoo.sketches.hll.Union;
+
+/**
+ * Hive union sketch UDF.
+ */
+public class UnionSketchUDF extends UDF {
+
+  /**
+   * Union two sketches given explicit lgK and target HLL type
+   *
+   * @param firstSketch
+   *   first sketch to be unioned.
+   * @param secondSketch
+   *          second sketch to be unioned.
+   * @param lgK
+   *   final output lgK
+   *   This must be between 4 and 21.
+   * @param type
+   *   final output HLL type
+   * @return resulting sketch of union.
+   */
+  public BytesWritable evaluate(final BytesWritable firstSketch, final BytesWritable secondSketch,
+      final int lgK, final String type) {
+
+    final TgtHllType hllType = TgtHllType.valueOf(type);
+    final Union union = new Union(lgK);
+
+    if (firstSketch != null) {
+      union.update(HllSketch.heapify(Memory.wrap(firstSketch.getBytes())));
+    }
+
+    if (secondSketch != null) {
+      union.update(HllSketch.heapify(Memory.wrap(secondSketch.getBytes())));
+    }
+
+    return new BytesWritable(union.getResult(hllType).toCompactByteArray());
+  }
+
+  /**
+   * Union two sketches given explicit lgK and using default target HLL type
+   *
+   * @param firstSketch
+   *   first sketch to be unioned.
+   * @param secondSketch
+   *   second sketch to be unioned.
+   * @param lgK
+   *   final output lgK
+   *   This must be between 4 and 21.
+   * @return resulting sketch of union.
+   */
+  public BytesWritable evaluate(final BytesWritable firstSketch, final BytesWritable secondSketch,
+      final int lgK) {
+    return evaluate(firstSketch, secondSketch, lgK, SketchEvaluator.DEFAULT_HLL_TYPE.toString());
+  }
+
+  /**
+   * Union two sketches using default lgK an target HLL type
+   *
+   * @param firstSketch
+   *          first sketch to be unioned.
+   * @param secondSketch
+   *          second sketch to be unioned.
+   * @return resulting sketch of union.
+   */
+  public BytesWritable evaluate(final BytesWritable firstSketch, final BytesWritable secondSketch) {
+    return evaluate(firstSketch, secondSketch, SketchEvaluator.DEFAULT_LG_K,
+        SketchEvaluator.DEFAULT_HLL_TYPE.toString());
+  }
+
+}
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/UnionState.java b/src/main/java/com/yahoo/sketches/hive/hll/UnionState.java
new file mode 100644
index 0000000..31650e2
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/UnionState.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import com.yahoo.sketches.hll.HllSketch;
+import com.yahoo.sketches.hll.TgtHllType;
+import com.yahoo.sketches.hll.Union;
+
+class UnionState extends State {
+
+  private Union union_;
+
+  boolean isInitialized() {
+    return union_ != null;
+  }
+
+  @Override
+  void init(final int lgK, final TgtHllType type) {
+    super.init(lgK, type);
+    union_ = new Union(lgK);
+  }
+
+  void update(final HllSketch sketch) {
+    union_.update(sketch);
+  }
+
+  @Override
+  HllSketch getResult() {
+    if (union_ == null) { return null; }
+    return union_.getResult(this.getType());
+  }
+
+  @Override
+  void reset() {
+    union_ = null;
+  }
+
+}
diff --git a/src/main/java/com/yahoo/sketches/hive/hll/package-info.java b/src/main/java/com/yahoo/sketches/hive/hll/package-info.java
new file mode 100644
index 0000000..605aebe
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/hive/hll/package-info.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2017, Yahoo! Inc. Licensed under the terms of the Apache License 2.0. See LICENSE file
+ * at the project root for terms.
+ */
+/**
+ * Hive UDFs for HLL sketches.
+ *
+ * @author Alexander Saydakov
+ */
+package com.yahoo.sketches.hive.hll;
diff --git a/src/test/java/com/yahoo/sketches/hive/hll/DataToSketchUDAFTest.java b/src/test/java/com/yahoo/sketches/hive/hll/DataToSketchUDAFTest.java
new file mode 100644
index 0000000..4c38d80
--- /dev/null
+++ b/src/test/java/com/yahoo/sketches/hive/hll/DataToSketchUDAFTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
+import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
+import org.apache.hadoop.hive.ql.parse.SemanticException;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator.Mode;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo;
+import org.apache.hadoop.hive.ql.udf.generic.SimpleGenericUDAFParameterInfo;
+import org.apache.hadoop.hive.serde2.io.DoubleWritable;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory;
+import org.apache.hadoop.hive.serde2.objectinspector.StructField;
+import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
+import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory;
+import org.apache.hadoop.io.BytesWritable;
+import org.apache.hadoop.io.IntWritable;
+import org.apache.hadoop.io.Text;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.hll.HllSketch;
+import com.yahoo.sketches.hll.TgtHllType;
+
+public class DataToSketchUDAFTest {
+
+  private static final ObjectInspector intInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.INT);
+
+  private static final ObjectInspector doubleInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.DOUBLE);
+
+  private static final ObjectInspector stringInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.STRING);
+
+  private static final ObjectInspector binaryInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.BINARY);
+
+  private static final ObjectInspector structInspector = ObjectInspectorFactory.getStandardStructObjectInspector(
+      Arrays.asList("lgK", "type", "sketch"),
+      Arrays.asList(intInspector, stringInspector, binaryInspector)
+    );
+
+  static final ObjectInspector intConstantInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableConstantObjectInspector(TypeInfoFactory.intTypeInfo, null);
+
+  static final ObjectInspector stringConstantInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableConstantObjectInspector(TypeInfoFactory.stringTypeInfo, null);
+
+  @Test(expectedExceptions = { UDFArgumentException.class })
+  public void tooFewArguments() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new DataToSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentException.class })
+  public void tooManyArguments() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector, intConstantInspector, stringConstantInspector, stringConstantInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new DataToSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void invalidCategoryArg1() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { structInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new DataToSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentException.class })
+  public void invalidCategoryArg2() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector, structInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new DataToSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentException.class })
+  public void invalidTypeArg2() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector, stringInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new DataToSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentException.class })
+  public void arg2notConst() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector, intInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new DataToSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void invalidCategoryArg3() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector, intConstantInspector, structInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new DataToSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void invalidTypeArg3() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector, intConstantInspector, doubleInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new DataToSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void arg3notConst() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector, intConstantInspector, stringInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new DataToSketchUDAF().getEvaluator(params);
+  }
+
+  // PARTIAL1 mode (Map phase in Map-Reduce): iterate + terminatePartial
+  @Test
+  public void partial1ModeIntKeysDefaultParams() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new DataToSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.PARTIAL1, inspectors);
+    checkIntermediateResultInspector(resultInspector);
+
+    State state = (State) eval.getNewAggregationBuffer();
+    eval.iterate(state, new Object[] {new IntWritable(1)});
+    eval.iterate(state, new Object[] {new IntWritable(2)});
+
+    Object result = eval.terminatePartial(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof List);
+    List<?> r = (List<?>) result;
+    Assert.assertEquals(r.size(), 3);
+    Assert.assertEquals(((IntWritable) r.get(0)).get(), SketchEvaluator.DEFAULT_LG_K);
+    Assert.assertEquals(((Text) r.get(1)).toString(), SketchEvaluator.DEFAULT_HLL_TYPE.toString());
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) r.get(2)).getBytes()));
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.close();
+  }
+
+  @Test
+  public void partial1ModeStringKeysExplicitParams() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { stringInspector, intConstantInspector, stringConstantInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new DataToSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.PARTIAL1, inspectors);
+    checkIntermediateResultInspector(resultInspector);
+
+    final int lgK = 10;
+    final TgtHllType hllType = TgtHllType.HLL_8;
+
+    State state = (State) eval.getNewAggregationBuffer();
+    eval.iterate(state, new Object[] {new Text("a"), new IntWritable(lgK), new Text(hllType.toString())});
+    eval.iterate(state, new Object[] {new Text("b"), new IntWritable(lgK), new Text(hllType.toString())});
+
+    Object result = eval.terminatePartial(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof List);
+    List<?> r = (List<?>) result;
+    Assert.assertEquals(r.size(), 3);
+    Assert.assertEquals(((IntWritable) r.get(0)).get(), lgK);
+    Assert.assertEquals(((Text) r.get(1)).toString(), hllType.toString());
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) r.get(2)).getBytes()));
+    Assert.assertEquals(resultSketch.getLgConfigK(), lgK);
+    Assert.assertEquals(resultSketch.getTgtHllType(), hllType);
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.close();
+  }
+
+  // PARTIAL2 mode (Combine phase in Map-Reduce): merge + terminatePartial
+  @Test
+  public void partial2Mode() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new DataToSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.PARTIAL2, new ObjectInspector[] {structInspector});
+    checkIntermediateResultInspector(resultInspector);
+
+    State state = (State) eval.getNewAggregationBuffer();
+
+    HllSketch sketch1 = new HllSketch(SketchEvaluator.DEFAULT_LG_K, SketchEvaluator.DEFAULT_HLL_TYPE);
+    sketch1.update(1);
+    eval.merge(state, Arrays.asList(
+      new IntWritable(SketchEvaluator.DEFAULT_LG_K),
+      new Text(SketchEvaluator.DEFAULT_HLL_TYPE.toString()),
+      new BytesWritable(sketch1.toCompactByteArray()))
+    );
+
+    HllSketch sketch2 = new HllSketch(SketchEvaluator.DEFAULT_LG_K, SketchEvaluator.DEFAULT_HLL_TYPE);
+    sketch2.update(2);
+    eval.merge(state, Arrays.asList(
+        new IntWritable(SketchEvaluator.DEFAULT_LG_K),
+        new Text(SketchEvaluator.DEFAULT_HLL_TYPE.toString()),
+        new BytesWritable(sketch2.toCompactByteArray()))
+    );
+
+    Object result = eval.terminatePartial(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof List);
+    List<?> r = (List<?>) result;
+    Assert.assertEquals(r.size(), 3);
+    Assert.assertEquals(((IntWritable) r.get(0)).get(), SketchEvaluator.DEFAULT_LG_K);
+    Assert.assertEquals(((Text) r.get(1)).toString(), SketchEvaluator.DEFAULT_HLL_TYPE.toString());
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) r.get(2)).getBytes()));
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.reset(state);
+    result = eval.terminate(state);
+    Assert.assertNull(result);
+
+    eval.close();
+  }
+
+  // FINAL mode (Reduce phase in Map-Reduce): merge + terminate
+  @Test
+  public void finalMode() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new DataToSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.FINAL, new ObjectInspector[] {structInspector});
+    checkFinalResultInspector(resultInspector);
+
+    State state = (State) eval.getNewAggregationBuffer();
+
+    HllSketch sketch1 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch1.update(1);
+    eval.merge(state, Arrays.asList(
+      new IntWritable(SketchEvaluator.DEFAULT_LG_K),
+      new Text(SketchEvaluator.DEFAULT_HLL_TYPE.toString()),
+      new BytesWritable(sketch1.toCompactByteArray()))
+    );
+
+    HllSketch sketch2 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch2.update(2);
+    eval.merge(state, Arrays.asList(
+        new IntWritable(SketchEvaluator.DEFAULT_LG_K),
+        new Text(SketchEvaluator.DEFAULT_HLL_TYPE.toString()),
+        new BytesWritable(sketch2.toCompactByteArray()))
+    );
+
+    Object result = eval.terminate(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof BytesWritable);
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) result).getBytes()));
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.close();
+  }
+
+  // COMPLETE mode (single mode, alternative to MapReduce): iterate + terminate
+  @Test
+  public void completeModeIntKeysDefaultParams() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new DataToSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.COMPLETE, inspectors);
+    checkFinalResultInspector(resultInspector);
+
+    State state = (State) eval.getNewAggregationBuffer();
+    eval.iterate(state, new Object[] {new IntWritable(1)});
+    eval.iterate(state, new Object[] {new IntWritable(2)});
+
+    Object result = eval.terminate(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof BytesWritable);
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) result).getBytes()));
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.reset(state);
+    result = eval.terminate(state);
+    Assert.assertNull(result);
+
+    eval.close();
+  }
+
+  // COMPLETE mode (single mode, alternative to MapReduce): iterate + terminate
+  @Test
+  public void completeModeDoubleKeysExplicitParams() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { doubleInspector, intConstantInspector, stringConstantInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new DataToSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.COMPLETE, inspectors);
+    checkFinalResultInspector(resultInspector);
+
+    final int lgK = 4;
+    final TgtHllType hllType = TgtHllType.HLL_6;
+
+    State state = (State) eval.getNewAggregationBuffer();
+    eval.iterate(state, new Object[] {new DoubleWritable(1), new IntWritable(lgK), new Text(hllType.toString())});
+    eval.iterate(state, new Object[] {new DoubleWritable(2), new IntWritable(lgK), new Text(hllType.toString())});
+
+    Object result = eval.terminate(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof BytesWritable);
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) result).getBytes()));
+    Assert.assertEquals(resultSketch.getLgConfigK(), lgK);
+    Assert.assertEquals(resultSketch.getTgtHllType(), hllType);
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.reset(state);
+    result = eval.terminate(state);
+    Assert.assertNull(result);
+
+    eval.close();
+  }
+
+  static void checkIntermediateResultInspector(ObjectInspector resultInspector) {
+    Assert.assertNotNull(resultInspector);
+    Assert.assertEquals(resultInspector.getCategory(), ObjectInspector.Category.STRUCT);
+    StructObjectInspector structResultInspector = (StructObjectInspector) resultInspector;
+    List<?> fields = structResultInspector.getAllStructFieldRefs();
+    Assert.assertEquals(fields.size(), 3);
+
+    ObjectInspector inspector1 = ((StructField) fields.get(0)).getFieldObjectInspector();
+    Assert.assertEquals(inspector1.getCategory(), ObjectInspector.Category.PRIMITIVE);
+    PrimitiveObjectInspector primitiveInspector1 = (PrimitiveObjectInspector) inspector1;
+    Assert.assertEquals(primitiveInspector1.getPrimitiveCategory(), PrimitiveCategory.INT);
+
+    ObjectInspector inspector2 = ((StructField) fields.get(1)).getFieldObjectInspector();
+    Assert.assertEquals(inspector2.getCategory(), ObjectInspector.Category.PRIMITIVE);
+    PrimitiveObjectInspector primitiveInspector2 = (PrimitiveObjectInspector) inspector2;
+    Assert.assertEquals(primitiveInspector2.getPrimitiveCategory(), PrimitiveCategory.STRING);
+
+    ObjectInspector inspector3 = ((StructField) fields.get(2)).getFieldObjectInspector();
+    Assert.assertEquals(inspector3.getCategory(), ObjectInspector.Category.PRIMITIVE);
+    PrimitiveObjectInspector primitiveInspector3 = (PrimitiveObjectInspector) inspector3;
+    Assert.assertEquals(primitiveInspector3.getPrimitiveCategory(), PrimitiveCategory.BINARY);
+  }
+
+  static void checkFinalResultInspector(ObjectInspector resultInspector) {
+    Assert.assertNotNull(resultInspector);
+    Assert.assertEquals(resultInspector.getCategory(), ObjectInspector.Category.PRIMITIVE);
+    Assert.assertEquals(
+      ((PrimitiveObjectInspector) resultInspector).getPrimitiveCategory(),
+      PrimitiveObjectInspector.PrimitiveCategory.BINARY
+    );
+  }
+
+}
diff --git a/src/test/java/com/yahoo/sketches/hive/hll/SketchToEstimateUDFTest.java b/src/test/java/com/yahoo/sketches/hive/hll/SketchToEstimateUDFTest.java
new file mode 100644
index 0000000..c298f73
--- /dev/null
+++ b/src/test/java/com/yahoo/sketches/hive/hll/SketchToEstimateUDFTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import org.apache.hadoop.io.BytesWritable;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.yahoo.sketches.hll.HllSketch;
+
+public class SketchToEstimateUDFTest {
+
+  @Test
+  public void nullSketch() {
+    Double result = new SketchToEstimateUDF().evaluate(null);
+    Assert.assertNull(result);
+  }
+
+  @Test
+  public void emptySketch() {
+    HllSketch sketch = new HllSketch(10);
+    Double result = new SketchToEstimateUDF().evaluate(new BytesWritable(sketch.toCompactByteArray()));
+    Assert.assertNotNull(result);
+    Assert.assertEquals(result, 0.0);
+  }
+
+  @Test
+  public void normalCase() {
+    HllSketch sketch = new HllSketch(10);
+    sketch.update(1);
+    sketch.update(2);
+    Double result = new SketchToEstimateUDF().evaluate(new BytesWritable(sketch.toCompactByteArray()));
+    Assert.assertNotNull(result);
+    Assert.assertEquals(result, 2.0, 0.01);
+  }
+
+}
diff --git a/src/test/java/com/yahoo/sketches/hive/hll/UnionSketchUDAFTest.java b/src/test/java/com/yahoo/sketches/hive/hll/UnionSketchUDAFTest.java
new file mode 100644
index 0000000..a21ef6c
--- /dev/null
+++ b/src/test/java/com/yahoo/sketches/hive/hll/UnionSketchUDAFTest.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.hive.hll;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
+import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
+import org.apache.hadoop.hive.ql.parse.SemanticException;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator.Mode;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo;
+import org.apache.hadoop.hive.ql.udf.generic.SimpleGenericUDAFParameterInfo;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
+import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
+import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory;
+import org.apache.hadoop.io.BytesWritable;
+import org.apache.hadoop.io.IntWritable;
+import org.apache.hadoop.io.Text;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.hll.HllSketch;
+import com.yahoo.sketches.hll.TgtHllType;
+
+public class UnionSketchUDAFTest {
+
+  private static final ObjectInspector intInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.INT);
+
+  private static final ObjectInspector stringInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.STRING);
+
+  private static final ObjectInspector binaryInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableObjectInspector(PrimitiveCategory.BINARY);
+
+  private static final ObjectInspector structInspector = ObjectInspectorFactory.getStandardStructObjectInspector(
+      Arrays.asList("lgK", "type", "sketch"),
+      Arrays.asList(intInspector, stringInspector, binaryInspector)
+    );
+
+  static final ObjectInspector intConstantInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableConstantObjectInspector(TypeInfoFactory.intTypeInfo, null);
+
+  static final ObjectInspector stringConstantInspector =
+      PrimitiveObjectInspectorFactory.getPrimitiveWritableConstantObjectInspector(TypeInfoFactory.stringTypeInfo, null);
+
+  @Test(expectedExceptions = { UDFArgumentException.class })
+  public void tooFewArguments() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentException.class })
+  public void tooManyArguments() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector, intConstantInspector, stringConstantInspector, stringConstantInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void invalidCategoryArg1() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { structInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void invalidTypeArg1() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { intInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void invalidCategoryArg2() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector, structInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void invalidTypeArg2() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector, stringInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void arg2NotConstant() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector, intInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void invalidCategoryArg3() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector, intConstantInspector, structInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void invalidTypeArg3() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector, intConstantInspector, intInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  @Test(expectedExceptions = { UDFArgumentTypeException.class })
+  public void arg3NotConstant() throws SemanticException {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector, intConstantInspector, stringInspector };
+    GenericUDAFParameterInfo params = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    new UnionSketchUDAF().getEvaluator(params);
+  }
+
+  // PARTIAL1 mode (Map phase in Map-Reduce): iterate + terminatePartial
+  @Test
+  public void partial1ModeDefaultParams() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new UnionSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.PARTIAL1, inspectors);
+    DataToSketchUDAFTest.checkIntermediateResultInspector(resultInspector);
+
+    State state = (State) eval.getNewAggregationBuffer();
+
+    HllSketch sketch1 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch1.update(1);
+    eval.iterate(state, new Object[] {new BytesWritable(sketch1.toCompactByteArray())});
+
+    HllSketch sketch2 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch2.update(2);
+    eval.iterate(state, new Object[] {new BytesWritable(sketch2.toCompactByteArray())});
+
+    Object result = eval.terminatePartial(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof List);
+    List<?> r = (List<?>) result;
+    Assert.assertEquals(r.size(), 3);
+    Assert.assertEquals(((IntWritable) (r.get(0))).get(), SketchEvaluator.DEFAULT_LG_K);
+    Assert.assertEquals(((Text) (r.get(1))).toString(), SketchEvaluator.DEFAULT_HLL_TYPE.toString());
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) (r.get(2))).getBytes()));
+    Assert.assertEquals(resultSketch.getLgConfigK(), SketchEvaluator.DEFAULT_LG_K);
+    Assert.assertEquals(resultSketch.getTgtHllType(), SketchEvaluator.DEFAULT_HLL_TYPE);
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.close();
+  }
+
+  @Test
+  public void partial1ModeExplicitParams() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector, intConstantInspector, stringConstantInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new UnionSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.PARTIAL1, inspectors);
+    DataToSketchUDAFTest.checkIntermediateResultInspector(resultInspector);
+
+    final int lgK = 10;
+    final TgtHllType hllType = TgtHllType.HLL_6;
+    State state = (State) eval.getNewAggregationBuffer();
+
+    HllSketch sketch1 = new HllSketch(lgK, hllType);
+    sketch1.update(1);
+    eval.iterate(state, new Object[] {new BytesWritable(sketch1.toCompactByteArray()),
+        new IntWritable(lgK), new Text(hllType.toString())});
+
+    HllSketch sketch2 = new HllSketch(lgK, hllType);
+    sketch2.update(2);
+    eval.iterate(state, new Object[] {new BytesWritable(sketch2.toCompactByteArray()),
+        new IntWritable(lgK), new Text(hllType.toString())});
+
+    Object result = eval.terminatePartial(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof List);
+    List<?> r = (List<?>) result;
+    Assert.assertEquals(r.size(), 3);
+    Assert.assertEquals(((IntWritable) (r.get(0))).get(), lgK);
+    Assert.assertEquals(((Text) (r.get(1))).toString(), hllType.toString());
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) (r.get(2))).getBytes()));
+    Assert.assertEquals(resultSketch.getLgConfigK(), lgK);
+    Assert.assertEquals(resultSketch.getTgtHllType(), hllType);
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.close();
+  }
+
+  // PARTIAL2 mode (Combine phase in Map-Reduce): merge + terminatePartial
+  @Test
+  public void partial2Mode() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new UnionSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.PARTIAL2, new ObjectInspector[] {structInspector});
+    DataToSketchUDAFTest.checkIntermediateResultInspector(resultInspector);
+
+    State state = (State) eval.getNewAggregationBuffer();
+
+    HllSketch sketch1 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch1.update(1);
+    eval.merge(state, Arrays.asList(
+      new IntWritable(SketchEvaluator.DEFAULT_LG_K),
+      new Text(SketchEvaluator.DEFAULT_HLL_TYPE.toString()),
+      new BytesWritable(sketch1.toCompactByteArray()))
+    );
+
+    HllSketch sketch2 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch2.update(2);
+    eval.merge(state, Arrays.asList(
+      new IntWritable(SketchEvaluator.DEFAULT_LG_K),
+      new Text(SketchEvaluator.DEFAULT_HLL_TYPE.toString()),
+      new BytesWritable(sketch2.toCompactByteArray()))
+    );
+
+    Object result = eval.terminatePartial(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof List);
+    List<?> r = (List<?>) result;
+    Assert.assertEquals(r.size(), 3);
+    Assert.assertEquals(((IntWritable) (r.get(0))).get(), SketchEvaluator.DEFAULT_LG_K);
+    Assert.assertEquals(((Text) (r.get(1))).toString(), SketchEvaluator.DEFAULT_HLL_TYPE.toString());
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) (r.get(2))).getBytes()));
+    Assert.assertEquals(resultSketch.getLgConfigK(), SketchEvaluator.DEFAULT_LG_K);
+    Assert.assertEquals(resultSketch.getTgtHllType(), SketchEvaluator.DEFAULT_HLL_TYPE);
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.reset(state);
+    result = eval.terminate(state);
+    Assert.assertNull(result);
+
+    eval.close();
+  }
+
+  // FINAL mode (Reduce phase in Map-Reduce): merge + terminate
+  @Test
+  public void finalMode() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new UnionSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.FINAL, new ObjectInspector[] {structInspector});
+    DataToSketchUDAFTest.checkFinalResultInspector(resultInspector);
+
+    State state = (State) eval.getNewAggregationBuffer();
+
+    HllSketch sketch1 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch1.update(1);
+    eval.merge(state, Arrays.asList(
+      new IntWritable(SketchEvaluator.DEFAULT_LG_K),
+      new Text(SketchEvaluator.DEFAULT_HLL_TYPE.toString()),
+      new BytesWritable(sketch1.toCompactByteArray()))
+    );
+
+    HllSketch sketch2 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch2.update(2);
+    eval.merge(state, Arrays.asList(
+      new IntWritable(SketchEvaluator.DEFAULT_LG_K),
+      new Text(SketchEvaluator.DEFAULT_HLL_TYPE.toString()),
+      new BytesWritable(sketch2.toCompactByteArray()))
+    );
+
+    Object result = eval.terminate(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof BytesWritable);
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) result).getBytes()));
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.close();
+  }
+
+  // COMPLETE mode (single mode, alternative to MapReduce): iterate + terminate
+  @Test
+  public void completeModeDefaultParams() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new UnionSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.COMPLETE, inspectors);
+    DataToSketchUDAFTest.checkFinalResultInspector(resultInspector);
+
+    State state = (State) eval.getNewAggregationBuffer();
+
+    HllSketch sketch1 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch1.update(1);
+    eval.iterate(state, new Object[] {new BytesWritable(sketch1.toCompactByteArray())});
+
+    HllSketch sketch2 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    sketch2.update(2);
+    eval.iterate(state, new Object[] {new BytesWritable(sketch2.toCompactByteArray())});
+
+    Object result = eval.terminate(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof BytesWritable);
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) result).getBytes()));
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.reset(state);
+    result = eval.terminate(state);
+    Assert.assertNull(result);
+
+    eval.close();
+  }
+
+  @Test
+  public void completeModeExplicitParams() throws Exception {
+    ObjectInspector[] inspectors = new ObjectInspector[] { binaryInspector, intConstantInspector, stringConstantInspector };
+    GenericUDAFParameterInfo info = new SimpleGenericUDAFParameterInfo(inspectors, false, false);
+    GenericUDAFEvaluator eval = new UnionSketchUDAF().getEvaluator(info);
+    ObjectInspector resultInspector = eval.init(Mode.COMPLETE, inspectors);
+    DataToSketchUDAFTest.checkFinalResultInspector(resultInspector);
+
+    final int lgK = 4;
+    final TgtHllType hllType = TgtHllType.HLL_6;
+    State state = (State) eval.getNewAggregationBuffer();
+
+    HllSketch sketch1 = new HllSketch(lgK, hllType);
+    sketch1.update(1);
+    eval.iterate(state, new Object[] {new BytesWritable(sketch1.toCompactByteArray()),
+        new IntWritable(lgK), new Text(hllType.toString())});
+
+    HllSketch sketch2 = new HllSketch(lgK, hllType);
+    sketch2.update(2);
+    eval.iterate(state, new Object[] {new BytesWritable(sketch2.toCompactByteArray()),
+        new IntWritable(lgK), new Text(hllType.toString())});
+
+    Object result = eval.terminate(state);
+    Assert.assertNotNull(result);
+    Assert.assertTrue(result instanceof BytesWritable);
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(((BytesWritable) result).getBytes()));
+    Assert.assertEquals(resultSketch.getLgConfigK(), lgK);
+    Assert.assertEquals(resultSketch.getTgtHllType(), hllType);
+    Assert.assertEquals(resultSketch.getEstimate(), 2.0, 0.01);
+
+    eval.reset(state);
+    result = eval.terminate(state);
+    Assert.assertNull(result);
+
+    eval.close();
+  }
+
+}
diff --git a/src/test/java/com/yahoo/sketches/hive/hll/UnionSketchUDFTest.java b/src/test/java/com/yahoo/sketches/hive/hll/UnionSketchUDFTest.java
new file mode 100644
index 0000000..96f534a
--- /dev/null
+++ b/src/test/java/com/yahoo/sketches/hive/hll/UnionSketchUDFTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+package com.yahoo.sketches.hive.hll;
+
+import org.apache.hadoop.io.BytesWritable;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.hll.HllSketch;
+import com.yahoo.sketches.hll.TgtHllType;
+
+public class UnionSketchUDFTest {
+
+  @Test
+  public void nullInputs() {
+    UnionSketchUDF udf = new UnionSketchUDF();
+    BytesWritable result = udf.evaluate(null, null);
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(result.getBytes()));
+    Assert.assertTrue(resultSketch.isEmpty());
+    Assert.assertEquals(resultSketch.getEstimate(), 0.0);
+  }
+
+  @Test
+  public void validSketches() {
+    UnionSketchUDF udf = new UnionSketchUDF();
+
+    HllSketch sketch1 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    for (int i = 0; i < 128; i++) {
+      sketch1.update(i);
+    }
+
+    HllSketch sketch2 = new HllSketch(SketchEvaluator.DEFAULT_LG_K);
+    for (int i = 100; i < 256; i++) {
+      sketch2.update(i);
+    }
+
+    BytesWritable input1 = new BytesWritable(sketch1.toCompactByteArray());
+    BytesWritable input2 = new BytesWritable(sketch2.toCompactByteArray());
+
+    BytesWritable result = udf.evaluate(input1, input2);
+
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(result.getBytes()));
+
+    Assert.assertEquals(resultSketch.getEstimate(), 256.0, 256 * 0.01);
+  }
+
+  @Test
+  public void validSketchesExplicitParams () {
+    UnionSketchUDF udf = new UnionSketchUDF();
+
+    final int lgK = 10;
+    final TgtHllType type = TgtHllType.HLL_6;
+
+    HllSketch sketch1 = new HllSketch(10);
+    for (int i = 0; i < 128; i++) {
+      sketch1.update(i);
+    }
+
+    HllSketch sketch2 = new HllSketch(10);
+    for (int i = 100; i < 256; i++) {
+      sketch2.update(i);
+    }
+
+    BytesWritable input1 = new BytesWritable(sketch1.toCompactByteArray());
+    BytesWritable input2 = new BytesWritable(sketch2.toCompactByteArray());
+
+    BytesWritable result = udf.evaluate(input1, input2, lgK, type.toString());
+
+    HllSketch resultSketch = HllSketch.heapify(Memory.wrap(result.getBytes()));
+
+    Assert.assertEquals(resultSketch.getLgConfigK(), lgK);
+    Assert.assertEquals(resultSketch.getTgtHllType(), type);
+    Assert.assertEquals(resultSketch.getEstimate(), 256.0, 256 * 0.02);
+  }
+
+}