prototype
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8098b63
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+EXTENSION = datasketches
+MODULE_big = datasketches
+
+OBJS = src/base64.o src/common.o \
+  src/kll_float_sketch_pg_functions.o src/kll_float_sketch_c_adapter.o \
+  src/cpc_sketch_pg_functions.o src/cpc_sketch_c_adapter.o
+
+DATA = sql/datasketches_cpc_sketch.sql sql/datasketches_kll_float_sketch.sql
+
+CXX = g++-8
+PG_CPPFLAGS = -I/usr/local/include -I../sketches-core-cpp/kll/include -I../sketches-core-cpp/common/include -I../sketches-core-cpp/cpc/include
+SHLIB_LINK = -L../sketches-core-cpp/lib -ldatasketches -lstdc++ -L/usr/local/lib -lnettle
+
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3752330
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+Prototype of a module for PostgreSQL
diff --git a/datasketches.control b/datasketches.control
new file mode 100644
index 0000000..72634af
--- /dev/null
+++ b/datasketches.control
@@ -0,0 +1,6 @@
+# Datasketches module
+comment = 'Aggregation functions and data types for approximate algorithms.'
+default_version = '1.0.0'
+relocatable = true
+
+module_pathname = '$libdir/datasketches'
diff --git a/sql/datasketches_cpc_sketch.sql b/sql/datasketches_cpc_sketch.sql
new file mode 100644
index 0000000..844c94a
--- /dev/null
+++ b/sql/datasketches_cpc_sketch.sql
@@ -0,0 +1,75 @@
+CREATE TYPE cpc_sketch;
+
+CREATE FUNCTION cpc_sketch_in(cstring) RETURNS cpc_sketch
+     AS '$libdir/datasketches', 'pg_sketch_in'
+     LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION cpc_sketch_out(cpc_sketch) RETURNS cstring
+     AS '$libdir/datasketches', 'pg_sketch_out'
+     LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION cpc_sketch_recv(internal) RETURNS cpc_sketch
+     AS '$libdir/datasketches', 'pg_cpc_sketch_recv'
+     LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION cpc_sketch_send(cpc_sketch) RETURNS bytea
+     AS '$libdir/datasketches', 'pg_cpc_sketch_send'
+     LANGUAGE C STRICT IMMUTABLE;
+
+-- actual LogLog counter data type
+CREATE TYPE cpc_sketch (
+    INPUT = cpc_sketch_in,
+    OUTPUT = cpc_sketch_out,
+    STORAGE = EXTERNAL,
+    RECEIVE = cpc_sketch_recv,
+    SEND = cpc_sketch_send
+);
+
+CREATE CAST (bytea as cpc_sketch) WITHOUT FUNCTION AS ASSIGNMENT;
+CREATE CAST (cpc_sketch as bytea) WITHOUT FUNCTION AS ASSIGNMENT;
+
+CREATE OR REPLACE FUNCTION cpc_sketch_add_item_default(internal, anyelement) RETURNS internal
+    AS '$libdir/datasketches', 'pg_cpc_sketch_add_item_default'
+    LANGUAGE C IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION cpc_sketch_get_estimate(cpc_sketch) RETURNS double precision
+    AS '$libdir/datasketches', 'pg_cpc_sketch_get_estimate'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION cpc_sketch_from_internal(internal) RETURNS cpc_sketch
+    AS '$libdir/datasketches', 'pg_cpc_sketch_from_internal'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION cpc_sketch_get_estimate_from_internal(internal) RETURNS double precision
+    AS '$libdir/datasketches', 'pg_cpc_sketch_get_estimate_from_internal'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION cpc_sketch_to_string(cpc_sketch) RETURNS TEXT
+    AS '$libdir/datasketches', 'pg_cpc_sketch_to_string'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION cpc_sketch_merge_default(internal, cpc_sketch) RETURNS internal
+    AS '$libdir/datasketches', 'pg_cpc_sketch_merge_default'
+    LANGUAGE C IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION cpc_union_get_result(internal) RETURNS cpc_sketch
+    AS '$libdir/datasketches', 'pg_cpc_union_get_result'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE AGGREGATE cpc_sketch_distinct(anyelement) (
+    sfunc = cpc_sketch_add_item_default,
+    stype = internal,
+    finalfunc = cpc_sketch_get_estimate_from_internal
+);
+
+CREATE AGGREGATE cpc_sketch_build(anyelement) (
+    sfunc = cpc_sketch_add_item_default,
+    stype = internal,
+    finalfunc = cpc_sketch_from_internal
+);
+
+CREATE AGGREGATE cpc_sketch_merge(cpc_sketch) (
+    sfunc = cpc_sketch_merge_default,
+    stype = internal,
+    finalfunc = cpc_union_get_result
+);
diff --git a/sql/datasketches_kll_float_sketch.sql b/sql/datasketches_kll_float_sketch.sql
new file mode 100644
index 0000000..2a0ed8c
--- /dev/null
+++ b/sql/datasketches_kll_float_sketch.sql
@@ -0,0 +1,65 @@
+CREATE TYPE kll_float_sketch;
+
+CREATE FUNCTION kll_float_sketch_in(cstring) RETURNS kll_float_sketch
+     AS '$libdir/datasketches', 'pg_sketch_in'
+     LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION kll_float_sketch_out(kll_float_sketch) RETURNS cstring
+     AS '$libdir/datasketches', 'pg_sketch_out'
+     LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION kll_float_sketch_recv(internal) RETURNS kll_float_sketch
+     AS '$libdir/datasketches', 'pg_kll_float_sketch_recv'
+     LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION kll_float_sketch_send(kll_float_sketch) RETURNS bytea
+     AS '$libdir/datasketches', 'pg_kll_float_sketch_send'
+     LANGUAGE C STRICT IMMUTABLE;
+
+-- actual LogLog counter data type
+CREATE TYPE kll_float_sketch (
+    INPUT = kll_float_sketch_in,
+    OUTPUT = kll_float_sketch_out,
+    STORAGE = EXTERNAL,
+    RECEIVE = kll_float_sketch_recv,
+    SEND = kll_float_sketch_send
+);
+
+CREATE CAST (bytea as kll_float_sketch) WITHOUT FUNCTION AS ASSIGNMENT;
+CREATE CAST (kll_float_sketch as bytea) WITHOUT FUNCTION AS ASSIGNMENT;
+
+CREATE OR REPLACE FUNCTION kll_float_sketch_add_item_default(internal, real) RETURNS internal
+    AS '$libdir/datasketches', 'pg_kll_float_sketch_add_item_default'
+    LANGUAGE C IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION kll_float_sketch_get_rank(kll_float_sketch, real) RETURNS double precision
+    AS '$libdir/datasketches', 'pg_kll_float_sketch_get_rank'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION kll_float_sketch_get_quantile(kll_float_sketch, double precision) RETURNS real
+    AS '$libdir/datasketches', 'pg_kll_float_sketch_get_quantile'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION kll_float_sketch_to_string(kll_float_sketch) RETURNS TEXT
+    AS '$libdir/datasketches', 'pg_kll_float_sketch_to_string'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION kll_float_sketch_merge_default(internal, kll_float_sketch) RETURNS internal
+    AS '$libdir/datasketches', 'pg_kll_float_sketch_merge_default'
+    LANGUAGE C IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION kll_float_sketch_from_internal(internal) RETURNS kll_float_sketch
+    AS '$libdir/datasketches', 'pg_kll_float_sketch_from_internal'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE AGGREGATE kll_float_sketch_build(real) (
+    sfunc = kll_float_sketch_add_item_default,
+    stype = internal,
+    finalfunc = kll_float_sketch_from_internal
+);
+
+CREATE AGGREGATE kll_float_sketch_merge(kll_float_sketch) (
+    sfunc = kll_float_sketch_merge_default,
+    stype = internal,
+    finalfunc = kll_float_sketch_from_internal
+);
diff --git a/src/allocator.h b/src/allocator.h
new file mode 100644
index 0000000..2388f98
--- /dev/null
+++ b/src/allocator.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+extern "C" {
+#include <postgres.h>
+}
+
+#include <new>
+#include <utility>
+
+template <class T> class palloc_allocator {
+public:
+  typedef T                 value_type;
+  typedef value_type*       pointer;
+  typedef const value_type* const_pointer;
+  typedef value_type&       reference;
+  typedef const value_type& const_reference;
+  typedef std::size_t       size_type;
+  typedef std::ptrdiff_t    difference_type;
+
+  template <class U>
+  struct rebind { typedef palloc_allocator<U> other; };
+
+  palloc_allocator() {}
+  palloc_allocator(const palloc_allocator&) {}
+  template <class U>
+  palloc_allocator(const palloc_allocator<U>&) {}
+  ~palloc_allocator() {}
+
+  pointer address(reference x) const { return &x; }
+  const_pointer address(const_reference x) const {
+    return x;
+  }
+
+  pointer allocate(size_type n, const_pointer = 0) {
+    void* p = palloc(n * sizeof(T));
+    if (!p) throw std::bad_alloc();
+    return static_cast<pointer>(p);
+  }
+
+  void deallocate(pointer p, size_type) { pfree(p); }
+
+  size_type max_size() const {
+    return static_cast<size_type>(-1) / sizeof(T);
+  }
+
+  void construct(pointer p, const value_type&& x) {
+    new(p) value_type(std::forward<const value_type>(x));
+  }
+  void destroy(pointer p) { p->~value_type(); }
+
+private:
+  void operator=(const palloc_allocator&);
+};
+
+template<> class palloc_allocator<void> {
+public:
+  typedef void        value_type;
+  typedef void*       pointer;
+  typedef const void* const_pointer;
+
+  template <class U>
+  struct rebind { typedef palloc_allocator<U> other; };
+};
+
+
+template <class T>
+inline bool operator==(const palloc_allocator<T>&, const palloc_allocator<T>&) {
+  return true;
+}
+
+template <class T>
+inline bool operator!=(const palloc_allocator<T>&, const palloc_allocator<T>&) {
+  return false;
+}
diff --git a/src/base64.c b/src/base64.c
new file mode 100644
index 0000000..0694866
--- /dev/null
+++ b/src/base64.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#include <assert.h>
+#include "base64.h"
+
+static const char bin_to_b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static const char b64_to_bin[128] = {
+   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 62,  0,  0,  0, 63,
+  52, 53, 54, 55, 56, 57, 58, 59, 60, 61,  0,  0,  0,  0,  0,  0,
+   0,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  0,  0,  0,  0,  0,
+   0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,  0,  0,  0,  0,  0
+};
+
+// with full padding
+// produces exactly b64_enc_len(srclen) chars
+// no \0 termination
+void b64_encode(const char* src, unsigned srclen, char* dst) {
+  unsigned buf = 0;
+  int pos = 2;
+
+  while (srclen--) {
+    buf |= (unsigned char)*(src++) << ((pos--) << 3);
+
+    if (pos < 0) {
+      *dst++ = bin_to_b64[(buf >> 18) & 0x3f];
+      *dst++ = bin_to_b64[(buf >> 12) & 0x3f];
+      *dst++ = bin_to_b64[(buf >> 6) & 0x3f];
+      *dst++ = bin_to_b64[buf & 0x3f];
+      pos = 2;
+      buf = 0;
+    }
+  }
+  if (pos != 2) {
+    *dst++ = bin_to_b64[(buf >> 18) & 0x3f];
+    *dst++ = bin_to_b64[(buf >> 12) & 0x3f];
+    *dst++ = (pos == 0) ? bin_to_b64[(buf >> 6) & 0x3f] : '=';
+    *dst++ = '=';
+  }
+}
+
+// supports no padding or partial padding (one =)
+// ignores invalid chars (fills zeros instead)
+// produces exactly b64_dec_len(src, srclen) bytes
+void b64_decode(const char* src, unsigned srclen, char* dst) {
+  unsigned buf = 0;
+  char c;
+  int bits = 0;
+  int pos = 0;
+  int pad = 0;
+
+  while (srclen--) {
+    c = *src++;
+    if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
+    bits = 0;
+    if (c != '=') {
+      if (c > 0 && c < 127) bits = b64_to_bin[(int)c];
+    } else {
+      pad++;
+    }
+    buf = (buf << 6) | bits;
+    pos++;
+    if (pos == 4) {
+      *dst++ = (buf >> 16) & 0xff;
+      if (pad < 2) *dst++ = (buf >> 8) & 0xff;
+      if (pad == 0) *dst++ = buf & 0xff;
+      buf = 0;
+      pos = 0;
+    }
+  }
+  // no padding or partial padding. pos must be 2 or 3
+  if (pos == 2) {
+    *dst++ = (buf >> 4) & 0xff;
+  } else if (pos == 3) {
+    *dst++ = (buf >> 10) & 0xff;
+    if (pad == 0) *dst++ = (buf >> 2) & 0xff;
+  }
+}
+
+// with padding
+unsigned b64_enc_len(unsigned srclen) {
+  return ((srclen + 2) / 3) * 4;
+}
+
+unsigned b64_dec_len(const char* src, unsigned srclen) {
+  unsigned pad = 0;
+  if (srclen > 0 && src[srclen - 1] == '=') pad++;
+  if (srclen > 1 && src[srclen - 2] == '=') pad++;
+  return ((srclen * 3) >> 2) - pad;
+}
diff --git a/src/base64.h b/src/base64.h
new file mode 100644
index 0000000..080c7c3
--- /dev/null
+++ b/src/base64.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#ifndef _BASE64_H_
+#define _BASE64_H_
+
+void b64_encode(const char *src, unsigned srclen, char *dst);
+void b64_decode(const char *src, unsigned srclen, char *dst);
+unsigned b64_enc_len(unsigned srclen);
+unsigned b64_dec_len(const char* src, unsigned srclen);
+
+#endif // _BASE64_H_
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 0000000..d81fe4c
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#include <postgres.h>
+#include <utils/builtins.h>
+
+#include "base64.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(pg_sketch_in);
+PG_FUNCTION_INFO_V1(pg_sketch_out);
+
+Datum pg_sketch_in(PG_FUNCTION_ARGS);
+Datum pg_sketch_out(PG_FUNCTION_ARGS);
+
+// cstring to type
+Datum pg_sketch_in(PG_FUNCTION_ARGS) {
+  // not invoked for nulls
+  bytea* decoded;
+  char* encoded = PG_GETARG_CSTRING(0);
+  const unsigned encoded_length = strlen(encoded);
+  const unsigned decoded_length = b64_dec_len(encoded, encoded_length);
+  decoded = palloc(VARHDRSZ + decoded_length);
+  b64_decode(encoded, encoded_length, VARDATA(decoded));
+  SET_VARSIZE(decoded, VARHDRSZ + decoded_length);
+  PG_RETURN_BYTEA_P(decoded);
+}
+
+// type to cstring
+Datum pg_sketch_out(PG_FUNCTION_ARGS) {
+  // not invoked for nulls
+  char* encoded;
+  bytea* bytes = PG_GETARG_BYTEA_P(0);
+  const unsigned encoded_length = b64_enc_len(VARSIZE(bytes) - VARHDRSZ);
+  encoded = palloc(encoded_length + 1);
+  b64_encode(VARDATA(bytes), VARSIZE(bytes) - VARHDRSZ, encoded);
+  encoded[encoded_length] = '\0';
+  PG_RETURN_CSTRING(encoded); // who should free it?
+}
diff --git a/src/cpc_sketch_c_adapter.cpp b/src/cpc_sketch_c_adapter.cpp
new file mode 100644
index 0000000..f7c3674
--- /dev/null
+++ b/src/cpc_sketch_c_adapter.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#include "cpc_sketch_c_adapter.h"
+
+extern "C" {
+#include <postgres.h>
+}
+
+#include <sstream>
+
+#include <cpc_sketch.hpp>
+#include <cpc_union.hpp>
+
+void* cpc_sketch_new(unsigned lg_k) {
+  return new (palloc(sizeof(datasketches::cpc_sketch))) datasketches::cpc_sketch(lg_k, datasketches::DEFAULT_SEED, &palloc, &pfree);
+}
+
+void cpc_sketch_delete(void* sketchptr) {
+  static_cast<datasketches::cpc_sketch*>(sketchptr)->~cpc_sketch();
+  pfree(sketchptr);
+}
+
+void cpc_sketch_update(void* sketchptr, const void* data, unsigned length) {
+  static_cast<datasketches::cpc_sketch*>(sketchptr)->update(data, length);
+}
+
+double cpc_sketch_get_estimate(const void* sketchptr) {
+  return static_cast<const datasketches::cpc_sketch*>(sketchptr)->get_estimate();
+}
+
+void cpc_sketch_to_string(const void* sketchptr, char* buffer, unsigned length) {
+  std::stringstream s;
+  s << *(static_cast<const datasketches::cpc_sketch*>(sketchptr));
+  snprintf(buffer, length, s.str().c_str());
+}
+
+void* cpc_sketch_serialize(const void* sketchptr) {
+  // intermediate copy in unavoidable with the current cpc_sketch API
+  // potential improvement
+  std::stringstream s;
+  static_cast<const datasketches::cpc_sketch*>(sketchptr)->serialize(s);
+  unsigned length = s.tellp();
+  bytea* buffer = (bytea*) palloc(length + VARHDRSZ);
+  SET_VARSIZE(buffer, length + VARHDRSZ);
+  s.read(VARDATA(buffer), length);
+  return buffer;
+}
+
+void* cpc_sketch_deserialize(const char* buffer, unsigned length) {
+  // intermediate copy in unavoidable with the current cpc_sketch API
+  // potential improvement
+  std::stringstream s;
+  s.write(buffer, length);
+  auto ptr = datasketches::cpc_sketch::deserialize(s, datasketches::DEFAULT_SEED, &palloc, &pfree);
+  return ptr.release();
+}
+
+void* cpc_union_new(unsigned lg_k) {
+  return new (palloc(sizeof(datasketches::cpc_union))) datasketches::cpc_union(lg_k, datasketches::DEFAULT_SEED, &palloc, &pfree);
+}
+
+void cpc_union_update(void* unionptr, const void* sketchptr) {
+  static_cast<datasketches::cpc_union*>(unionptr)->update(*static_cast<const datasketches::cpc_sketch*>(sketchptr));
+}
+
+void* cpc_union_get_result(void* unionptr) {
+  auto ptr = static_cast<datasketches::cpc_union*>(unionptr)->get_result();
+  return ptr.release();
+}
diff --git a/src/cpc_sketch_c_adapter.h b/src/cpc_sketch_c_adapter.h
new file mode 100644
index 0000000..ecd82e2
--- /dev/null
+++ b/src/cpc_sketch_c_adapter.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#ifndef CPC_SKETCH_C_ADAPTER_H
+#define CPC_SKETCH_C_ADAPTER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void* cpc_sketch_new(unsigned lg_k);
+void cpc_sketch_delete(void* sketchptr);
+
+void cpc_sketch_update(void* sketchptr, const void* data, unsigned length);
+void cpc_sketch_merge(void* sketchptr1, const void* sketchptr2);
+double cpc_sketch_get_estimate(const void* sketchptr);
+void cpc_sketch_to_string(const void* sketchptr, char* buffer, unsigned length);
+
+void* cpc_sketch_serialize(const void* sketchptr);
+void* cpc_sketch_deserialize(const char* buffer, unsigned length);
+
+void* cpc_union_new(unsigned lg_k);
+void cpc_union_update(void* unionptr, const void* sketchptr);
+void* cpc_union_get_result(void* unionptr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cpc_sketch_pg_functions.c b/src/cpc_sketch_pg_functions.c
new file mode 100644
index 0000000..3cb5cc8
--- /dev/null
+++ b/src/cpc_sketch_pg_functions.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#include <postgres.h>
+#include <fmgr.h>
+#include <utils/lsyscache.h>
+#include <utils/builtins.h>
+
+#include "cpc_sketch_c_adapter.h"
+#include "base64.h"
+
+const unsigned DEFAULT_LG_K = 13;
+
+/* PG_FUNCTION_INFO_V1 macro to pass functions to postgres */
+PG_FUNCTION_INFO_V1(pg_cpc_sketch_recv);
+PG_FUNCTION_INFO_V1(pg_cpc_sketch_send);
+PG_FUNCTION_INFO_V1(pg_cpc_sketch_add_item_default);
+PG_FUNCTION_INFO_V1(pg_cpc_sketch_get_estimate);
+PG_FUNCTION_INFO_V1(pg_cpc_sketch_to_string);
+PG_FUNCTION_INFO_V1(pg_cpc_sketch_merge_default);
+PG_FUNCTION_INFO_V1(pg_cpc_sketch_from_internal);
+PG_FUNCTION_INFO_V1(pg_cpc_sketch_get_estimate_from_internal);
+PG_FUNCTION_INFO_V1(pg_cpc_union_get_result);
+
+/* function declarations */
+Datum pg_cpc_sketch_recv(PG_FUNCTION_ARGS);
+Datum pg_cpc_sketch_send(PG_FUNCTION_ARGS);
+Datum pg_cpc_sketch_add_item_default(PG_FUNCTION_ARGS);
+Datum pg_cpc_sketch_get_estimate(PG_FUNCTION_ARGS);
+Datum pg_cpc_sketch_to_string(PG_FUNCTION_ARGS);
+Datum pg_cpc_sketch_merge_default(PG_FUNCTION_ARGS);
+Datum pg_cpc_sketch_from_internal(PG_FUNCTION_ARGS);
+Datum pg_cpc_sketch_get_estimate_from_internal(PG_FUNCTION_ARGS);
+Datum pg_cpc_union_get_result(PG_FUNCTION_ARGS);
+
+// external binary to type
+Datum pg_cpc_sketch_recv(PG_FUNCTION_ARGS) {
+  // not invoked for nulls
+  elog(FATAL, "pg_cpc_sketch_recv is not implemented yet");
+  PG_RETURN_NULL();
+}
+
+// type to external binary
+Datum pg_cpc_sketch_send(PG_FUNCTION_ARGS) {
+  // not invoked for nulls
+  elog(FATAL, "pg_cpc_sketch_send is not implemented yet");
+  PG_RETURN_BYTEA_P(0);
+}
+
+Datum pg_cpc_sketch_add_item_default(PG_FUNCTION_ARGS) {
+  void* sketchptr;
+
+  // anyelement
+  Oid   element_type;
+  Datum element;
+  int16 typlen;
+  bool  typbyval;
+  char  typalign;
+
+  MemoryContext oldcontext;
+  MemoryContext aggcontext;
+
+  if (PG_ARGISNULL(0) && PG_ARGISNULL(1)) {
+    PG_RETURN_NULL();
+  } else if (PG_ARGISNULL(1)) {
+    PG_RETURN_POINTER(PG_GETARG_POINTER(0)); // no update value. return unmodified state
+  }
+
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "cpc_sketch_add_item_default called in non-aggregate context");
+  }
+  oldcontext = MemoryContextSwitchTo(aggcontext);
+
+  if (PG_ARGISNULL(0)) {
+    sketchptr = cpc_sketch_new(DEFAULT_LG_K);
+  } else {
+    sketchptr = PG_GETARG_POINTER(0);
+  }
+
+  element_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
+  element = PG_GETARG_DATUM(1);
+  get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);
+  if (typlen == -1) {
+    // varlena
+    cpc_sketch_update(sketchptr, VARDATA_ANY(element), VARSIZE_ANY_EXHDR(element));
+  } else if (typbyval) {
+    // fixed-length passed by value
+    cpc_sketch_update(sketchptr, &element, typlen);
+  } else {
+    // fixed-length passed by reference
+    cpc_sketch_update(sketchptr, (void*)element, typlen);
+  }
+
+  MemoryContextSwitchTo(oldcontext);
+
+  PG_RETURN_POINTER(sketchptr);
+}
+
+Datum pg_cpc_sketch_get_estimate(PG_FUNCTION_ARGS) {
+  const bytea* bytes_in;
+  void* sketchptr;
+  double estimate;
+  bytes_in = PG_GETARG_BYTEA_P(0);
+  sketchptr = cpc_sketch_deserialize(VARDATA(bytes_in), VARSIZE(bytes_in) - VARHDRSZ);
+  estimate = cpc_sketch_get_estimate(sketchptr);
+  cpc_sketch_delete(sketchptr);
+  PG_RETURN_FLOAT8(estimate);
+}
+
+Datum pg_cpc_sketch_to_string(PG_FUNCTION_ARGS) {
+  const bytea* bytes_in;
+  void* sketchptr;
+  char str[1024];
+  bytes_in = PG_GETARG_BYTEA_P(0);
+  sketchptr = cpc_sketch_deserialize(VARDATA(bytes_in), VARSIZE(bytes_in) - VARHDRSZ);
+  cpc_sketch_to_string(sketchptr, str, 1024);
+  cpc_sketch_delete(sketchptr);
+  PG_RETURN_TEXT_P(cstring_to_text(str));
+}
+
+Datum pg_cpc_sketch_merge_default(PG_FUNCTION_ARGS) {
+  void* unionptr;
+  bytea* sketch_bytes;
+  void* sketchptr;
+
+  MemoryContext oldcontext;
+  MemoryContext aggcontext;
+
+  if (PG_ARGISNULL(0) && PG_ARGISNULL(1)) {
+    PG_RETURN_NULL();
+  } else if (PG_ARGISNULL(1)) {
+    PG_RETURN_POINTER(PG_GETARG_POINTER(0)); // no update value. return unmodified state
+  }
+
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "cpc_sketch_merge_default called in non-aggregate context");
+  }
+  oldcontext = MemoryContextSwitchTo(aggcontext);
+
+  if (PG_ARGISNULL(0)) {
+    //elog(LOG, "pg_cpc_sketch_merge_defalut: initializing union state");
+    unionptr = cpc_union_new(DEFAULT_LG_K);
+  } else {
+    //elog(LOG, "pg_cpc_sketch_merge_defalut: loading existing union state");
+    unionptr = PG_GETARG_POINTER(0);
+  }
+
+  sketch_bytes = PG_GETARG_BYTEA_P(1);
+  sketchptr = cpc_sketch_deserialize(VARDATA(sketch_bytes), VARSIZE(sketch_bytes) - VARHDRSZ);
+  cpc_union_update(unionptr, sketchptr);
+  cpc_sketch_delete(sketchptr);
+
+  MemoryContextSwitchTo(oldcontext);
+
+  PG_RETURN_POINTER(unionptr);
+}
+
+Datum pg_cpc_sketch_from_internal(PG_FUNCTION_ARGS) {
+  void* sketchptr;
+  bytea* bytes_out;
+
+  MemoryContext oldcontext;
+  MemoryContext aggcontext;
+
+  if (PG_ARGISNULL(0)) PG_RETURN_NULL();
+
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "cpc_sketch_from_internal called in non-aggregate context");
+  }
+  oldcontext = MemoryContextSwitchTo(aggcontext);
+
+  sketchptr = PG_GETARG_POINTER(0);
+  bytes_out = cpc_sketch_serialize(sketchptr);
+  cpc_sketch_delete(sketchptr);
+
+  MemoryContextSwitchTo(oldcontext);
+
+  PG_RETURN_BYTEA_P(bytes_out);
+}
+
+Datum pg_cpc_sketch_get_estimate_from_internal(PG_FUNCTION_ARGS) {
+  void* sketchptr;
+  double estimate;
+
+  MemoryContext oldcontext;
+  MemoryContext aggcontext;
+
+  if (PG_ARGISNULL(0)) PG_RETURN_NULL();
+
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "cpc_sketch_from_internal called in non-aggregate context");
+  }
+  oldcontext = MemoryContextSwitchTo(aggcontext);
+
+  sketchptr = PG_GETARG_POINTER(0);
+  estimate = cpc_sketch_get_estimate(sketchptr);
+  cpc_sketch_delete(sketchptr);
+
+  MemoryContextSwitchTo(oldcontext);
+
+  PG_RETURN_FLOAT8(estimate);
+}
+
+Datum pg_cpc_union_get_result(PG_FUNCTION_ARGS) {
+  void* unionptr;
+  void* sketchptr;
+  bytea* bytes_out;
+
+  MemoryContext oldcontext;
+  MemoryContext aggcontext;
+
+  if (PG_ARGISNULL(0)) PG_RETURN_NULL();
+
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "cpc_union_get_result called in non-aggregate context");
+  }
+  oldcontext = MemoryContextSwitchTo(aggcontext);
+
+  unionptr = PG_GETARG_POINTER(0);
+  sketchptr = cpc_union_get_result(unionptr);
+  bytes_out = cpc_sketch_serialize(sketchptr);
+  cpc_sketch_delete(sketchptr);
+  //cpc_union_delete(unionptr);
+
+  MemoryContextSwitchTo(oldcontext);
+
+  PG_RETURN_BYTEA_P(bytes_out);
+}
diff --git a/src/kll_float_sketch_c_adapter.cpp b/src/kll_float_sketch_c_adapter.cpp
new file mode 100644
index 0000000..5c77584
--- /dev/null
+++ b/src/kll_float_sketch_c_adapter.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#include "kll_float_sketch_c_adapter.h"
+#include "allocator.h"
+
+#include <sstream>
+
+#include <kll_sketch.hpp>
+
+typedef datasketches::kll_sketch<float, palloc_allocator<void>> kll_float_sketch;
+
+void* kll_float_sketch_new(unsigned k) {
+  return new (palloc(sizeof(kll_float_sketch))) kll_float_sketch(k);
+}
+
+void kll_float_sketch_delete(void* sketchptr) {
+  static_cast<kll_float_sketch*>(sketchptr)->~kll_float_sketch();
+  pfree(sketchptr);
+}
+
+void kll_float_sketch_update(void* sketchptr, float value) {
+  static_cast<kll_float_sketch*>(sketchptr)->update(value);
+}
+
+void kll_float_sketch_merge(void* sketchptr1, const void* sketchptr2) {
+  static_cast<kll_float_sketch*>(sketchptr1)->merge(*static_cast<const kll_float_sketch*>(sketchptr2));
+}
+
+double kll_float_sketch_get_rank(const void* sketchptr, float value) {
+  return static_cast<const kll_float_sketch*>(sketchptr)->get_rank(value);
+}
+
+float kll_float_sketch_get_quantile(const void* sketchptr, double rank) {
+  return static_cast<const kll_float_sketch*>(sketchptr)->get_quantile(rank);
+}
+
+void kll_float_sketch_to_string(const void* sketchptr, char* buffer, unsigned length) {
+  std::stringstream s;
+  s << *(static_cast<const kll_float_sketch*>(sketchptr));
+  snprintf(buffer, length, s.str().c_str());
+}
+
+void kll_float_sketch_serialize(const void* sketchptr, char* buffer) {
+  // intermediate copy in unavoidable with the current kll_sketch API
+  // potential improvement
+  std::stringstream s;
+  static_cast<const kll_float_sketch*>(sketchptr)->serialize(s);
+  s.read(buffer, s.tellp());
+}
+
+void* kll_float_sketch_deserialize(const char* buffer, unsigned length) {
+  // intermediate copy in unavoidable with the current kll_sketch API
+  // potential improvement
+  std::stringstream s;
+  s.write(buffer, length);
+  auto ptr = kll_float_sketch::deserialize(s);
+  return ptr.release();
+}
+
+unsigned kll_float_sketch_get_serialized_size_bytes(const void* sketchptr) {
+  return static_cast<const kll_float_sketch*>(sketchptr)->get_serialized_size_bytes();
+}
diff --git a/src/kll_float_sketch_c_adapter.h b/src/kll_float_sketch_c_adapter.h
new file mode 100644
index 0000000..e1fe94b
--- /dev/null
+++ b/src/kll_float_sketch_c_adapter.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#ifndef KLL_FLOAT_SKETCH_C_ADAPTER_H
+#define KLL_FLOAT_SKETCH_C_ADAPTER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void* kll_float_sketch_new(unsigned k);
+void kll_float_sketch_delete(void* sketchptr);
+
+void kll_float_sketch_update(void* sketchptr, float value);
+void kll_float_sketch_merge(void* sketchptr1, const void* sketchptr2);
+double kll_float_sketch_get_rank(const void* sketchptr, float value);
+float kll_float_sketch_get_quantile(const void* sketchptr, double rank);
+void kll_float_sketch_to_string(const void* sketchptr, char* buffer, unsigned length);
+
+void kll_float_sketch_serialize(const void* sketchptr, char* buffer);
+void* kll_float_sketch_deserialize(const char* buffer, unsigned length);
+unsigned kll_float_sketch_get_serialized_size_bytes(const void* sketchptr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/kll_float_sketch_pg_functions.c b/src/kll_float_sketch_pg_functions.c
new file mode 100644
index 0000000..d172422
--- /dev/null
+++ b/src/kll_float_sketch_pg_functions.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2018, Oath Inc. Licensed under the terms of the
+ * Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#include <postgres.h>
+#include <fmgr.h>
+#include <utils/lsyscache.h>
+#include <utils/builtins.h>
+
+#include "kll_float_sketch_c_adapter.h"
+#include "base64.h"
+
+/* PG_FUNCTION_INFO_V1 macro to pass functions to postgres */
+PG_FUNCTION_INFO_V1(pg_kll_float_sketch_recv);
+PG_FUNCTION_INFO_V1(pg_kll_float_sketch_send);
+PG_FUNCTION_INFO_V1(pg_kll_float_sketch_add_item_default);
+PG_FUNCTION_INFO_V1(pg_kll_float_sketch_get_rank);
+PG_FUNCTION_INFO_V1(pg_kll_float_sketch_get_quantile);
+PG_FUNCTION_INFO_V1(pg_kll_float_sketch_to_string);
+PG_FUNCTION_INFO_V1(pg_kll_float_sketch_merge_default);
+PG_FUNCTION_INFO_V1(pg_kll_float_sketch_from_internal);
+
+/* function declarations */
+Datum pg_kll_float_sketch_recv(PG_FUNCTION_ARGS);
+Datum pg_kll_float_sketch_send(PG_FUNCTION_ARGS);
+Datum pg_kll_float_sketch_add_item_default(PG_FUNCTION_ARGS);
+Datum pg_kll_float_sketch_get_rank(PG_FUNCTION_ARGS);
+Datum pg_kll_float_sketch_get_quantile(PG_FUNCTION_ARGS);
+Datum pg_kll_float_sketch_to_string(PG_FUNCTION_ARGS);
+Datum pg_kll_float_sketch_merge_default(PG_FUNCTION_ARGS);
+Datum pg_kll_float_sketch_from_internal(PG_FUNCTION_ARGS);
+
+static const unsigned DEFAULT_K = 200;
+
+// external binary to type
+Datum pg_kll_float_sketch_recv(PG_FUNCTION_ARGS) {
+  // not invoked for nulls
+  elog(FATAL, "pg_kll_float_sketch_recv is not implemented yet");
+  PG_RETURN_NULL();
+}
+
+// type to external binary
+Datum pg_kll_float_sketch_send(PG_FUNCTION_ARGS) {
+  // not invoked for nulls
+  elog(FATAL, "pg_kll_float_sketch_send is not implemented yet");
+  PG_RETURN_BYTEA_P(0);
+}
+
+Datum pg_kll_float_sketch_add_item_default(PG_FUNCTION_ARGS) {
+  void* sketchptr;
+  float value;
+
+  MemoryContext oldcontext;
+  MemoryContext aggcontext;
+
+  if (PG_ARGISNULL(0) && PG_ARGISNULL(1)) {
+    PG_RETURN_NULL();
+  } else if (PG_ARGISNULL(1)) {
+    PG_RETURN_POINTER(PG_GETARG_POINTER(0)); // no update value. return unmodified state
+  }
+
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "kll_float_sketch_add_item_default called in non-aggregate context");
+  }
+  oldcontext = MemoryContextSwitchTo(aggcontext);
+
+  if (PG_ARGISNULL(0)) {
+    sketchptr = kll_float_sketch_new(DEFAULT_K);
+  } else {
+    sketchptr = PG_GETARG_POINTER(0);
+  }
+
+  value = PG_GETARG_FLOAT4(1);
+  kll_float_sketch_update(sketchptr, value);
+
+  MemoryContextSwitchTo(oldcontext);
+
+  PG_RETURN_POINTER(sketchptr);
+}
+
+Datum pg_kll_float_sketch_get_rank(PG_FUNCTION_ARGS) {
+  const bytea* bytes_in;
+  void* sketchptr;
+  float value;
+  double rank;
+  bytes_in = PG_GETARG_BYTEA_P(0);
+  sketchptr = kll_float_sketch_deserialize(VARDATA(bytes_in), VARSIZE(bytes_in) - VARHDRSZ);
+  value = PG_GETARG_FLOAT4(1);
+  rank = kll_float_sketch_get_rank(sketchptr, value);
+  kll_float_sketch_delete(sketchptr);
+  PG_RETURN_FLOAT8(rank);
+}
+
+Datum pg_kll_float_sketch_get_quantile(PG_FUNCTION_ARGS) {
+  const bytea* bytes_in;
+  void* sketchptr;
+  float value;
+  double rank;
+  bytes_in = PG_GETARG_BYTEA_P(0);
+  sketchptr = kll_float_sketch_deserialize(VARDATA(bytes_in), VARSIZE(bytes_in) - VARHDRSZ);
+  rank = PG_GETARG_FLOAT8(1);
+  value = kll_float_sketch_get_quantile(sketchptr, rank);
+  kll_float_sketch_delete(sketchptr);
+  PG_RETURN_FLOAT4(value);
+}
+
+Datum pg_kll_float_sketch_to_string(PG_FUNCTION_ARGS) {
+  const bytea* bytes_in;
+  void* sketchptr;
+  char str[1024];
+  bytes_in = PG_GETARG_BYTEA_P(0);
+  sketchptr = kll_float_sketch_deserialize(VARDATA(bytes_in), VARSIZE(bytes_in) - VARHDRSZ);
+  kll_float_sketch_to_string(sketchptr, str, 1024);
+  kll_float_sketch_delete(sketchptr);
+  PG_RETURN_TEXT_P(cstring_to_text(str));
+}
+
+Datum pg_kll_float_sketch_merge_default(PG_FUNCTION_ARGS) {
+  void* unionptr;
+  bytea* sketch_bytes;
+  void* sketchptr;
+
+  MemoryContext oldcontext;
+  MemoryContext aggcontext;
+
+  if (PG_ARGISNULL(0) && PG_ARGISNULL(1)) {
+    PG_RETURN_NULL();
+  } else if (PG_ARGISNULL(1)) {
+    PG_RETURN_POINTER(PG_GETARG_POINTER(0)); // no update value. return unmodified state
+  }
+
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "kll_float_sketch_merge called in non-aggregate context");
+  }
+  oldcontext = MemoryContextSwitchTo(aggcontext);
+
+  if (PG_ARGISNULL(0)) {
+    unionptr = kll_float_sketch_new(DEFAULT_K);
+  } else {
+    unionptr = PG_GETARG_POINTER(0);
+  }
+
+  sketch_bytes = PG_GETARG_BYTEA_P(1);
+  sketchptr = kll_float_sketch_deserialize(VARDATA(sketch_bytes), VARSIZE(sketch_bytes) - VARHDRSZ);
+  kll_float_sketch_merge(unionptr, sketchptr);
+  kll_float_sketch_delete(sketchptr);
+
+  MemoryContextSwitchTo(oldcontext);
+
+  PG_RETURN_POINTER(unionptr);
+}
+
+Datum pg_kll_float_sketch_from_internal(PG_FUNCTION_ARGS) {
+  void* sketchptr;
+  unsigned length;
+  bytea* bytes_out;
+  MemoryContext aggcontext;
+  if (PG_ARGISNULL(0)) PG_RETURN_NULL();
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "kll_float_sketch_from_internal called in non-aggregate context");
+  }
+  sketchptr = PG_GETARG_POINTER(0);
+  length = VARHDRSZ + kll_float_sketch_get_serialized_size_bytes(sketchptr);
+  bytes_out = palloc(length);
+  SET_VARSIZE(bytes_out, length);
+  kll_float_sketch_serialize(sketchptr, VARDATA(bytes_out));
+  kll_float_sketch_delete(sketchptr);
+  PG_RETURN_BYTEA_P(bytes_out);
+}