merged from master
diff --git a/Makefile b/Makefile
index bcac339..6cc3a54 100644
--- a/Makefile
+++ b/Makefile
@@ -4,17 +4,18 @@
 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 \
-  src/theta_sketch_pg_functions.o src/theta_sketch_c_adapter.o
+  src/theta_sketch_pg_functions.o src/theta_sketch_c_adapter.o \
+  src/frequent_strings_sketch_pg_functions.o src/frequent_strings_sketch_c_adapter.o
 
 # assume a copy or link datasketches-cpp in the current dir
 CORE = datasketches-cpp
 CPC = $(CORE)/cpc/src
 OBJS += $(CPC)/cpc_sketch.o $(CPC)/fm85.o $(CPC)/fm85Compression.o $(CPC)/fm85Confidence.o $(CPC)/fm85Merging.o $(CPC)/fm85Util.o $(CPC)/iconEstimator.o $(CPC)/u32Table.o
 
-DATA = sql/datasketches_cpc_sketch.sql sql/datasketches_kll_float_sketch.sql sql/datasketches_theta_sketch.sql
+DATA = sql/datasketches_cpc_sketch.sql sql/datasketches_kll_float_sketch.sql sql/datasketches_theta_sketch.sql sql/datasketches_frequent_strings_sketch.sql
 
 CXX = g++-8
-PG_CPPFLAGS = -I/usr/local/include -I$(CORE)/kll/include -I$(CORE)/common/include -I$(CORE)/cpc/include -I$(CORE)/theta/include
+PG_CPPFLAGS = -I/usr/local/include -I$(CORE)/kll/include -I$(CORE)/common/include -I$(CORE)/cpc/include -I$(CORE)/theta/include -I$(CORE)/fi/include
 SHLIB_LINK = -lstdc++ -L/usr/local/lib
 
 PG_CONFIG = pg_config
diff --git a/sql/datasketches_frequent_strings_sketch.sql b/sql/datasketches_frequent_strings_sketch.sql
new file mode 100644
index 0000000..2c44681
--- /dev/null
+++ b/sql/datasketches_frequent_strings_sketch.sql
@@ -0,0 +1,86 @@
+-- Copyright 2019, Verizon Media.
+-- Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+
+CREATE TYPE frequent_strings_sketch;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_in(cstring) RETURNS frequent_strings_sketch
+     AS '$libdir/datasketches', 'pg_sketch_in'
+     LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_out(frequent_strings_sketch) RETURNS cstring
+     AS '$libdir/datasketches', 'pg_sketch_out'
+     LANGUAGE C STRICT IMMUTABLE;
+
+CREATE TYPE frequent_strings_sketch (
+    INPUT = frequent_strings_sketch_in,
+    OUTPUT = frequent_strings_sketch_out,
+    STORAGE = EXTERNAL
+);
+
+CREATE CAST (bytea as frequent_strings_sketch) WITHOUT FUNCTION AS ASSIGNMENT;
+CREATE CAST (frequent_strings_sketch as bytea) WITHOUT FUNCTION AS ASSIGNMENT;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_add_item(internal, int, varchar) RETURNS internal
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_add_item'
+    LANGUAGE C IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_add_item(internal, int, varchar, bigint) RETURNS internal
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_add_item'
+    LANGUAGE C IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_merge(internal, int, frequent_strings_sketch) RETURNS internal
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_merge'
+    LANGUAGE C IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_from_internal(internal) RETURNS frequent_strings_sketch
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_from_internal'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_to_string(frequent_strings_sketch) RETURNS TEXT
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_to_string'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_to_string(frequent_strings_sketch, boolean) RETURNS TEXT
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_to_string'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE AGGREGATE frequent_strings_sketch_build(int, varchar) (
+    sfunc = frequent_strings_sketch_add_item,
+    stype = internal,
+    finalfunc = frequent_strings_sketch_from_internal
+);
+
+CREATE AGGREGATE frequent_strings_sketch_build(int, varchar, bigint) (
+    sfunc = frequent_strings_sketch_add_item,
+    stype = internal,
+    finalfunc = frequent_strings_sketch_from_internal
+);
+
+CREATE AGGREGATE frequent_strings_sketch_merge(int, frequent_strings_sketch) (
+    sfunc = frequent_strings_sketch_merge,
+    stype = internal,
+    finalfunc = frequent_strings_sketch_from_internal
+);
+
+CREATE TYPE frequent_strings_sketch_row AS (str varchar, estimate bigint, lower_bound bigint, upper_bound bigint);
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_result_no_false_positives(frequent_strings_sketch)
+    RETURNS setof frequent_strings_sketch_row
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_result_no_false_positives'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_result_no_false_positives(frequent_strings_sketch, bigint)
+    RETURNS setof frequent_strings_sketch_row
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_result_no_false_positives'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_result_no_false_negatives(frequent_strings_sketch)
+    RETURNS setof frequent_strings_sketch_row
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_result_no_false_negatives'
+    LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION frequent_strings_sketch_result_no_false_negatives(frequent_strings_sketch, bigint)
+    RETURNS setof frequent_strings_sketch_row
+    AS '$libdir/datasketches', 'pg_frequent_strings_sketch_result_no_false_negatives'
+    LANGUAGE C STRICT IMMUTABLE;
+
diff --git a/src/frequent_strings_sketch_c_adapter.cpp b/src/frequent_strings_sketch_c_adapter.cpp
new file mode 100644
index 0000000..ca12362
--- /dev/null
+++ b/src/frequent_strings_sketch_c_adapter.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2019, Verizon Media.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#include "frequent_strings_sketch_c_adapter.h"
+#include "allocator.h"
+
+#include <string>
+#include <sstream>
+
+#include <frequent_items_sketch.hpp>
+
+typedef std::basic_string<char, std::char_traits<char>, palloc_allocator<char>> string;
+
+// delegate to std::hash<std::string>
+struct hash_string {
+  std::size_t operator()(const string& s) const {
+    return std::hash<std::string>()(s.c_str());
+  }
+};
+
+struct serde_string {
+  void serialize(std::ostream& os, const string* items, unsigned num) {
+    for (unsigned i = 0; i < num; i++) {
+      uint32_t length = items[i].size();
+      os.write((char*)&length, sizeof(length));
+      os.write(items[i].c_str(), length);
+    }
+  }
+  void deserialize(std::istream& is, string* items, unsigned num) {
+    for (unsigned i = 0; i < num; i++) {
+      uint32_t length;
+      is.read((char*)&length, sizeof(length));
+      new (&items[i]) string;
+      items[i].reserve(length);
+      auto it = std::istreambuf_iterator<char>(is);
+      for (uint32_t j = 0; j < length; j++) {
+        items[i].push_back(*it);
+        ++it;
+      }
+    }
+  }
+  size_t size_of_item(const string& item) {
+    return sizeof(uint32_t) + item.size();
+  }
+  size_t serialize(char* ptr, const string* items, unsigned num) {
+    size_t size = sizeof(uint32_t) * num;
+    for (unsigned i = 0; i < num; i++) {
+      uint32_t length = items[i].size();
+      memcpy(ptr, &length, sizeof(length));
+      ptr += sizeof(uint32_t);
+      memcpy(ptr, items[i].c_str(), length);
+      ptr += length;
+      size += length;
+    }
+    return size;
+  }
+  size_t deserialize(const char* ptr, string* items, unsigned num) {
+    size_t size = sizeof(uint32_t) * num;
+    for (unsigned i = 0; i < num; i++) {
+      uint32_t length;
+      memcpy(&length, ptr, sizeof(length));
+      ptr += sizeof(uint32_t);
+      new (&items[i]) string(ptr, length);
+      ptr += length;
+      size += length;
+    }
+    return size;
+  }
+};
+
+typedef datasketches::frequent_items_sketch<string, hash_string, std::equal_to<string>, serde_string, palloc_allocator<string>> frequent_strings_sketch;
+
+void* frequent_strings_sketch_new(unsigned lg_k) {
+  try {
+    return new (palloc(sizeof(frequent_strings_sketch))) frequent_strings_sketch(lg_k);
+  } catch (std::exception& e) {
+    elog(ERROR, e.what());
+  }
+}
+
+void frequent_strings_sketch_delete(void* sketchptr) {
+  try {
+    static_cast<frequent_strings_sketch*>(sketchptr)->~frequent_strings_sketch();
+    pfree(sketchptr);
+  } catch (std::exception& e) {
+    elog(ERROR, e.what());
+  }
+}
+
+void frequent_strings_sketch_update(void* sketchptr, const char* str, unsigned length, unsigned long long weight) {
+  try {
+    static_cast<frequent_strings_sketch*>(sketchptr)->update(string(str, length), weight);
+  } catch (std::exception& e) {
+    elog(ERROR, e.what());
+  }
+}
+
+void frequent_strings_sketch_merge(void* sketchptr1, const void* sketchptr2) {
+  try {
+    static_cast<frequent_strings_sketch*>(sketchptr1)->merge(*static_cast<const frequent_strings_sketch*>(sketchptr2));
+  } catch (std::exception& e) {
+    elog(ERROR, e.what());
+  }
+}
+
+char* frequent_strings_sketch_to_string(const void* sketchptr, bool print_items) {
+  try {
+    std::stringstream s;
+    static_cast<const frequent_strings_sketch*>(sketchptr)->to_stream(s, print_items);
+    const unsigned len = (unsigned) s.tellp() + 1;
+    char* buffer = (char*) palloc(len);
+    strncpy(buffer, s.str().c_str(), len);
+    return buffer;
+  } catch (std::exception& e) {
+    elog(ERROR, e.what());
+  }
+}
+
+void* frequent_strings_sketch_serialize(const void* sketchptr) {
+  try {
+    auto data = static_cast<const frequent_strings_sketch*>(sketchptr)->serialize(VARHDRSZ);
+    bytea* buffer = (bytea*) data.first.release();
+    const size_t length = data.second;
+    SET_VARSIZE(buffer, length);
+    return buffer;
+  } catch (std::exception& e) {
+    elog(ERROR, e.what());
+  }
+}
+
+void* frequent_strings_sketch_deserialize(const char* buffer, unsigned length) {
+  try {
+    frequent_strings_sketch* sketchptr = new (palloc(sizeof(frequent_strings_sketch)))
+      frequent_strings_sketch(frequent_strings_sketch::deserialize(buffer, length));
+    return sketchptr;
+  } catch (std::exception& e) {
+    elog(ERROR, e.what());
+  }
+}
+
+unsigned frequent_strings_sketch_get_serialized_size_bytes(const void* sketchptr) {
+  try {
+    return static_cast<const frequent_strings_sketch*>(sketchptr)->get_serialized_size_bytes();
+  } catch (std::exception& e) {
+    elog(ERROR, e.what());
+  }
+}
+
+frequent_strings_sketch_result* frequent_strings_sketch_get_frequent_items(void* sketchptr, bool no_false_positives, unsigned long long threshold) {
+  try {
+    auto data = static_cast<const frequent_strings_sketch*>(sketchptr)->get_frequent_items(
+      no_false_positives ? datasketches::frequent_items_error_type::NO_FALSE_POSITIVES : datasketches::frequent_items_error_type::NO_FALSE_NEGATIVES,
+      threshold
+    );
+    auto rows = (frequent_strings_sketch_result_row*) palloc(sizeof(frequent_strings_sketch_result_row) * data.size());
+    unsigned i = 0;
+    for (auto& it: data) {
+      const string& str = it.get_item();
+      rows[i].str = (char*) palloc(str.length() + 1);
+      strncpy(rows[i].str, str.c_str(), str.length() + 1);
+      rows[i].estimate = it.get_estimate();
+      rows[i].lower_bound = it.get_lower_bound();
+      rows[i].upper_bound = it.get_upper_bound();
+      ++i;
+    }
+    auto result = (frequent_strings_sketch_result*) palloc(sizeof(frequent_strings_sketch_result));;
+    result->rows = rows;
+    result->num = data.size();
+    return result;
+  } catch (std::exception& e) {
+    elog(ERROR, e.what());
+  }
+}
diff --git a/src/frequent_strings_sketch_c_adapter.h b/src/frequent_strings_sketch_c_adapter.h
new file mode 100644
index 0000000..65514ca
--- /dev/null
+++ b/src/frequent_strings_sketch_c_adapter.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019, Verizon Media.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+#ifndef FREQUENT_STRINGS_SKETCH_C_ADAPTER_H
+#define FREQUENT_STRINGS_SKETCH_C_ADAPTER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <postgres.h>
+
+void* frequent_strings_sketch_new(unsigned lg_k);
+void frequent_strings_sketch_delete(void* sketchptr);
+
+void frequent_strings_sketch_update(void* sketchptr, const char* str, unsigned length, unsigned long long weight);
+void frequent_strings_sketch_merge(void* sketchptr1, const void* sketchptr2);
+char* frequent_strings_sketch_to_string(const void* sketchptr, bool print_items);
+
+void* frequent_strings_sketch_serialize(const void* sketchptr);
+void* frequent_strings_sketch_deserialize(const char* buffer, unsigned length);
+unsigned frequent_strings_sketch_get_serialized_size_bytes(const void* sketchptr);
+
+struct frequent_strings_sketch_result_row {
+  char* str;
+  unsigned long long estimate;
+  unsigned long long lower_bound;
+  unsigned long long upper_bound;
+};
+
+struct frequent_strings_sketch_result {
+  struct frequent_strings_sketch_result_row* rows;
+  unsigned num;
+};
+
+struct frequent_strings_sketch_result* frequent_strings_sketch_get_frequent_items(void* sketchptr, bool no_false_positives, unsigned long long threshold);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/frequent_strings_sketch_pg_functions.c b/src/frequent_strings_sketch_pg_functions.c
new file mode 100644
index 0000000..85dd556
--- /dev/null
+++ b/src/frequent_strings_sketch_pg_functions.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2019, Verizon Media.
+ * 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 <utils/array.h>
+#include <catalog/pg_type.h>
+#include <funcapi.h>
+
+#include "frequent_strings_sketch_c_adapter.h"
+#include "base64.h"
+
+/* PG_FUNCTION_INFO_V1 macro to pass functions to postgres */
+PG_FUNCTION_INFO_V1(pg_frequent_strings_sketch_add_item);
+PG_FUNCTION_INFO_V1(pg_frequent_strings_sketch_merge);
+PG_FUNCTION_INFO_V1(pg_frequent_strings_sketch_from_internal);
+PG_FUNCTION_INFO_V1(pg_frequent_strings_sketch_to_string);
+PG_FUNCTION_INFO_V1(pg_frequent_strings_sketch_result_no_false_positives);
+PG_FUNCTION_INFO_V1(pg_frequent_strings_sketch_result_no_false_negatives);
+
+/* function declarations */
+Datum pg_frequent_strings_sketch_recv(PG_FUNCTION_ARGS);
+Datum pg_frequent_strings_sketch_send(PG_FUNCTION_ARGS);
+Datum pg_frequent_strings_sketch_add_item(PG_FUNCTION_ARGS);
+Datum pg_frequent_strings_sketch_merge(PG_FUNCTION_ARGS);
+Datum pg_frequent_strings_sketch_from_internal(PG_FUNCTION_ARGS);
+Datum pg_frequent_strings_sketch_to_string(PG_FUNCTION_ARGS);
+Datum pg_frequent_strings_sketch_result_no_false_positives(PG_FUNCTION_ARGS);
+Datum pg_frequent_strings_sketch_result_no_false_negatives(PG_FUNCTION_ARGS);
+
+Datum frequent_strings_sketch_get_result(PG_FUNCTION_ARGS, bool);
+
+
+Datum pg_frequent_strings_sketch_add_item(PG_FUNCTION_ARGS) {
+  void* sketchptr;
+  unsigned lg_k;
+  const VarChar* str;
+  unsigned long long weight;
+
+  MemoryContext oldcontext;
+  MemoryContext aggcontext;
+
+  if (PG_ARGISNULL(0) && PG_ARGISNULL(2)) {
+    PG_RETURN_NULL();
+  } else if (PG_ARGISNULL(2)) {
+    PG_RETURN_POINTER(PG_GETARG_POINTER(0)); // no update value. return unmodified state
+  }
+
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "frequent_strings_sketch_add_item called in non-aggregate context");
+  }
+  oldcontext = MemoryContextSwitchTo(aggcontext);
+
+  if (PG_ARGISNULL(0)) {
+    lg_k = PG_GETARG_INT32(1);
+    sketchptr = frequent_strings_sketch_new(lg_k);
+  } else {
+    sketchptr = PG_GETARG_POINTER(0);
+  }
+
+  str = PG_GETARG_VARCHAR_P(2);
+
+  // optional weight
+  if (PG_NARGS() == 3) {
+    weight = 1;
+  } else {
+    weight = PG_GETARG_INT64(3);
+  }
+
+  frequent_strings_sketch_update(sketchptr, VARDATA(str), VARSIZE(str) - VARHDRSZ, weight);
+
+  MemoryContextSwitchTo(oldcontext);
+
+  PG_RETURN_POINTER(sketchptr);
+}
+
+Datum pg_frequent_strings_sketch_merge(PG_FUNCTION_ARGS) {
+  void* unionptr;
+  bytea* sketch_bytes;
+  void* sketchptr;
+  unsigned lg_k;
+
+  MemoryContext oldcontext;
+  MemoryContext aggcontext;
+
+  if (PG_ARGISNULL(0) && PG_ARGISNULL(2)) {
+    PG_RETURN_NULL();
+  } else if (PG_ARGISNULL(2)) {
+    PG_RETURN_POINTER(PG_GETARG_POINTER(0)); // no update value. return unmodified state
+  }
+
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "frequent_strings_sketch_merge called in non-aggregate context");
+  }
+  oldcontext = MemoryContextSwitchTo(aggcontext);
+
+  if (PG_ARGISNULL(0)) {
+    lg_k = PG_GETARG_INT32(1);
+    unionptr = frequent_strings_sketch_new(lg_k);
+  } else {
+    unionptr = PG_GETARG_POINTER(0);
+  }
+
+  sketch_bytes = PG_GETARG_BYTEA_P(2);
+  sketchptr = frequent_strings_sketch_deserialize(VARDATA(sketch_bytes), VARSIZE(sketch_bytes) - VARHDRSZ);
+  frequent_strings_sketch_merge(unionptr, sketchptr);
+  frequent_strings_sketch_delete(sketchptr);
+
+  MemoryContextSwitchTo(oldcontext);
+
+  PG_RETURN_POINTER(unionptr);
+}
+
+Datum pg_frequent_strings_sketch_from_internal(PG_FUNCTION_ARGS) {
+  void* sketchptr;
+  bytea* bytes_out;
+  MemoryContext aggcontext;
+  if (PG_ARGISNULL(0)) PG_RETURN_NULL();
+  if (!AggCheckCallContext(fcinfo, &aggcontext)) {
+    elog(ERROR, "frequent_strings_sketch_from_internal called in non-aggregate context");
+  }
+  sketchptr = PG_GETARG_POINTER(0);
+  bytes_out = frequent_strings_sketch_serialize(sketchptr);
+  frequent_strings_sketch_delete(sketchptr);
+  PG_RETURN_BYTEA_P(bytes_out);
+}
+
+Datum pg_frequent_strings_sketch_to_string(PG_FUNCTION_ARGS) {
+  const bytea* bytes_in;
+  void* sketchptr;
+  bool print_items;
+  char* str;
+  bytes_in = PG_GETARG_BYTEA_P(0);
+  if (PG_NARGS() > 1) {
+    print_items = PG_GETARG_BOOL(1);
+  } else {
+    print_items = false;
+  }
+  sketchptr = frequent_strings_sketch_deserialize(VARDATA(bytes_in), VARSIZE(bytes_in) - VARHDRSZ);
+  str = frequent_strings_sketch_to_string(sketchptr, print_items);
+  frequent_strings_sketch_delete(sketchptr);
+  PG_RETURN_TEXT_P(cstring_to_text(str));
+}
+
+Datum frequent_strings_sketch_get_result(PG_FUNCTION_ARGS, bool no_false_positives) {
+  FuncCallContext* funcctx;
+  TupleDesc tupdesc;
+  const bytea* bytes_in;
+  void* sketchptr;
+  unsigned long long threshold;
+
+  if (SRF_IS_FIRSTCALL()) {
+    MemoryContext oldcontext;
+    funcctx = SRF_FIRSTCALL_INIT();
+    oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+    if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
+      ereport(
+        ERROR,
+        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context that cannot accept type record"))
+      );
+    }
+
+    if (PG_ARGISNULL(0)) SRF_RETURN_DONE(funcctx);
+    bytes_in = PG_GETARG_BYTEA_P(0);
+    sketchptr = frequent_strings_sketch_deserialize(VARDATA(bytes_in), VARSIZE(bytes_in) - VARHDRSZ);
+    if (PG_ARGISNULL(1)) {
+      threshold = 0;
+    } else {
+      threshold = PG_GETARG_INT64(1);
+    }
+
+    funcctx->user_fctx = frequent_strings_sketch_get_frequent_items(sketchptr, no_false_positives, threshold);
+    funcctx->max_calls = ((struct frequent_strings_sketch_result*) funcctx->user_fctx)->num;
+    funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+    frequent_strings_sketch_delete(sketchptr);
+
+    MemoryContextSwitchTo(oldcontext);
+  }
+
+  funcctx = SRF_PERCALL_SETUP();
+
+  if (funcctx->call_cntr < funcctx->max_calls) {
+    char       **values;
+    HeapTuple    tuple;
+    Datum        result;
+
+    struct frequent_strings_sketch_result* frequent_strings = funcctx->user_fctx;
+    struct frequent_strings_sketch_result_row* row = &frequent_strings->rows[funcctx->call_cntr];
+
+    values = (char**) palloc(4 * sizeof(char*));
+    values[0] = row->str;
+    values[1] = (char*) palloc(21);
+    values[2] = (char*) palloc(21);
+    values[3] = (char*) palloc(21);
+
+    snprintf(values[1], 21, "%llu", row->estimate);
+    snprintf(values[2], 21, "%llu", row->lower_bound);
+    snprintf(values[3], 21, "%llu", row->upper_bound);
+
+    tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
+
+    result = HeapTupleGetDatum(tuple);
+
+    pfree(values[1]);
+    pfree(values[2]);
+    pfree(values[3]);
+    pfree(values);
+
+    SRF_RETURN_NEXT(funcctx, result);
+  } else {
+    SRF_RETURN_DONE(funcctx);
+  }
+}
+
+Datum pg_frequent_strings_sketch_result_no_false_positives(PG_FUNCTION_ARGS) {
+  return frequent_strings_sketch_get_result(fcinfo, true);
+}
+
+Datum pg_frequent_strings_sketch_result_no_false_negatives(PG_FUNCTION_ARGS) {
+  return frequent_strings_sketch_get_result(fcinfo, false);
+}