nested hstore
[hstore.git] / hstore_io.c
diff --git a/hstore_io.c b/hstore_io.c
new file mode 100644 (file)
index 0000000..63ffcda
--- /dev/null
@@ -0,0 +1,1906 @@
+/*
+ * contrib/hstore/hstore_io.c
+ */
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_cast.h"
+#include "funcapi.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
+#include "parser/parse_coerce.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+#include "hstore.h"
+
+PG_MODULE_MAGIC;
+
+/* old names for C functions */
+HSTORE_POLLUTE(hstore_from_text, tconvert);
+
+/* GUC variables */
+static bool    pretty_print_var = false;
+#define SET_PRETTY_PRINT_VAR(x)                ((pretty_print_var) ? \
+                                                                        ((x) | PrettyPrint) : (x))
+
+static void recvHStore(StringInfo buf, HStoreValue *v, uint32 level,
+                                          uint32 header);
+static Oid searchCast(Oid src, Oid dst, CoercionMethod *method);
+
+static size_t
+hstoreCheckKeyLen(size_t len)
+{
+       if (len > HSTORE_MAX_KEY_LEN)
+               ereport(ERROR,
+                               (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
+                                errmsg("string too long for hstore key")));
+       return len;
+}
+
+static size_t
+hstoreCheckValLen(size_t len)
+{
+       if (len > HSTORE_MAX_VALUE_LEN)
+               ereport(ERROR,
+                               (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
+                                errmsg("string too long for hstore value")));
+       return len;
+}
+
+
+static HStore*
+hstoreDump(HStoreValue *p)
+{
+       uint32                  buflen;
+       HStore             *out;
+
+       if (p == NULL || (p->type == hsvArray && p->array.nelems == 0) ||
+               (p->type == hsvHash && p->hash.npairs == 0))
+       {
+               buflen = 0;
+               out = palloc(VARHDRSZ);
+       }
+       else
+       {
+               buflen = VARHDRSZ + p->size;
+               out = palloc(buflen);
+               SET_VARSIZE(out, buflen);
+
+               buflen = compressHStore(p, VARDATA(out));
+       }
+       SET_VARSIZE(out, buflen + VARHDRSZ);
+
+       return out;
+}
+
+PG_FUNCTION_INFO_V1(hstore_in);
+Datum          hstore_in(PG_FUNCTION_ARGS);
+Datum
+hstore_in(PG_FUNCTION_ARGS)
+{
+       PG_RETURN_POINTER(hstoreDump(parseHStore(PG_GETARG_CSTRING(0), -1, false)));
+}
+
+static void
+recvHStoreValue(StringInfo buf, HStoreValue *v, uint32 level, int c)
+{
+       uint32  hentry = c & HENTRY_TYPEMASK;
+
+       check_stack_depth();
+
+       if (c == -1 /* compatibility */ || hentry == HENTRY_ISNULL)
+       {
+               v->type = hsvNull;
+               v->size = sizeof(HEntry);
+       }
+       else if (hentry == HENTRY_ISHASH || hentry == HENTRY_ISARRAY ||
+                        hentry == HENTRY_ISSCALAR)
+       {
+               recvHStore(buf, v, level + 1, (uint32)c);
+       }
+       else if (hentry == HENTRY_ISFALSE || hentry == HENTRY_ISTRUE)
+       {
+               v->type = hsvBool;
+               v->size = sizeof(HEntry);
+               v->boolean = (hentry == HENTRY_ISFALSE) ? false : true;
+       }
+       else if (hentry == HENTRY_ISNUMERIC)
+       {
+               v->type = hsvNumeric;
+               v->numeric = DatumGetNumeric(DirectFunctionCall3(numeric_recv,
+                                                                                                                PointerGetDatum(buf),
+                                                                                                                Int32GetDatum(0),
+                                                                                                                Int32GetDatum(-1)));
+               v->size = sizeof(HEntry) * 2 + VARSIZE_ANY(v->numeric);
+       }
+       else if (hentry == HENTRY_ISSTRING)
+       {
+               v->type = hsvString;
+               v->string.val = pq_getmsgtext(buf, c & ~HENTRY_TYPEMASK, &c);
+               v->string.len = hstoreCheckKeyLen(c);
+               v->size = sizeof(HEntry) + v->string.len;
+       }
+       else
+       {
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+                                errmsg("unknown hstore value")));
+       }
+}
+
+static void
+recvHStore(StringInfo buf, HStoreValue *v, uint32 level, uint32 header)
+{
+       uint32  hentry;
+       uint32  i;
+
+       hentry = header & HENTRY_TYPEMASK;
+
+       if (level == 0 && hentry == 0)
+               hentry = HENTRY_ISHASH; /* old version */
+
+       check_stack_depth();
+
+       v->size = 3 * sizeof(HEntry);
+       if (hentry == HENTRY_ISHASH)
+       {
+               v->type = hsvHash;
+               v->hash.npairs = header & HS_COUNT_MASK;
+
+               if (v->hash.npairs > (buf->len  - buf->cursor) / (2 * sizeof(uint32)) ||
+                       v->hash.npairs > MaxAllocSize / sizeof(HStorePair))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                        errmsg("too much elements in hstore hash")));
+
+               if (v->hash.npairs > 0)
+               {
+                       v->hash.pairs = palloc(sizeof(*v->hash.pairs) * v->hash.npairs);
+
+                       for(i=0; i<v->hash.npairs; i++)
+                       {
+                               recvHStoreValue(buf, &v->hash.pairs[i].key, level,
+                                                               pq_getmsgint(buf, 4));
+                               if (v->hash.pairs[i].key.type != hsvString)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("hstore's key could be only a string")));
+
+                               recvHStoreValue(buf, &v->hash.pairs[i].value, level,
+                                                               pq_getmsgint(buf, 4));
+
+                               v->size += v->hash.pairs[i].key.size +
+                                                       v->hash.pairs[i].value.size;
+                       }
+
+                       uniqueHStoreValue(v);
+               }
+       }
+       else if (hentry == HENTRY_ISARRAY || hentry == HENTRY_ISSCALAR)
+       {
+               v->type = hsvArray;
+               v->array.nelems = header & HS_COUNT_MASK;
+               v->array.scalar = (hentry == HENTRY_ISSCALAR) ? true : false;
+
+               if (v->array.nelems > (buf->len  - buf->cursor) / sizeof(uint32) ||
+                       v->array.nelems > MaxAllocSize / sizeof(HStoreValue))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                        errmsg("too much elements in hstore array")));
+
+               if (v->array.scalar && v->array.nelems != 1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+                                        errmsg("wrong scalar representation")));
+
+               if (v->array.nelems > 0)
+               {
+                       v->array.elems = palloc(sizeof(*v->array.elems) * v->array.nelems);
+
+                       for(i=0; i<v->array.nelems; i++)
+                       {
+                               recvHStoreValue(buf, v->array.elems + i, level,
+                                                               pq_getmsgint(buf, 4));
+                               v->size += v->array.elems[i].size;
+                       }
+               }
+       }
+       else
+       {
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+                                errmsg("unknown hstore element")));
+       }
+}
+
+PG_FUNCTION_INFO_V1(hstore_recv);
+Datum          hstore_recv(PG_FUNCTION_ARGS);
+Datum
+hstore_recv(PG_FUNCTION_ARGS)
+{
+       StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
+       HStoreValue     v;
+
+       recvHStore(buf, &v, 0, pq_getmsgint(buf, 4));
+
+       PG_RETURN_POINTER(hstoreDump(&v));
+}
+
+PG_FUNCTION_INFO_V1(hstore_from_text);
+Datum          hstore_from_text(PG_FUNCTION_ARGS);
+Datum
+hstore_from_text(PG_FUNCTION_ARGS)
+{
+       text            *key;
+       HStoreValue     v;
+       HStorePair      pair;
+
+       if (PG_ARGISNULL(0))
+               PG_RETURN_NULL();
+
+       key = PG_GETARG_TEXT_PP(0);
+       pair.key.type = hsvString;
+       pair.key.string.val = VARDATA_ANY(key);
+       pair.key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key));
+       pair.key.size = pair.key.string.len + sizeof(HEntry);
+
+       if (PG_ARGISNULL(1))
+       {
+               pair.value.type = hsvNull;
+               pair.value.size = sizeof(HEntry);
+       }
+       else
+       {
+               text            *val = NULL;
+
+               val = PG_GETARG_TEXT_PP(1);
+               pair.value.type = hsvString;
+               pair.value.string.val = VARDATA_ANY(val);
+               pair.value.string.len = hstoreCheckValLen(VARSIZE_ANY_EXHDR(val));
+               pair.value.size = pair.value.string.len + sizeof(HEntry);
+       }
+
+       v.type = hsvHash;
+       v.size = sizeof(HEntry) + pair.key.size + pair.value.size;
+       v.hash.npairs = 1;
+       v.hash.pairs = &pair;
+
+       PG_RETURN_POINTER(hstoreDump(&v));
+}
+
+PG_FUNCTION_INFO_V1(hstore_from_bool);
+Datum          hstore_from_bool(PG_FUNCTION_ARGS);
+Datum
+hstore_from_bool(PG_FUNCTION_ARGS)
+{
+       text            *key;
+       HStoreValue     v;
+       HStorePair      pair;
+
+       if (PG_ARGISNULL(0))
+               PG_RETURN_NULL();
+
+       key = PG_GETARG_TEXT_PP(0);
+       pair.key.type = hsvString;
+       pair.key.string.val = VARDATA_ANY(key);
+       pair.key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key));
+       pair.key.size = pair.key.string.len + sizeof(HEntry);
+
+       if (PG_ARGISNULL(1))
+       {
+               pair.value.type = hsvNull;
+               pair.value.size = sizeof(HEntry);
+       }
+       else
+       {
+               pair.value.type = hsvBool;
+               pair.value.boolean = PG_GETARG_BOOL(1);
+               pair.value.size = sizeof(HEntry);
+       }
+
+       v.type = hsvHash;
+       v.size = sizeof(HEntry) + pair.key.size + pair.value.size;
+       v.hash.npairs = 1;
+       v.hash.pairs = &pair;
+
+       PG_RETURN_POINTER(hstoreDump(&v));
+}
+
+PG_FUNCTION_INFO_V1(hstore_from_numeric);
+Datum          hstore_from_numeric(PG_FUNCTION_ARGS);
+Datum
+hstore_from_numeric(PG_FUNCTION_ARGS)
+{
+       text            *key;
+       HStoreValue     v;
+       HStorePair      pair;
+
+       if (PG_ARGISNULL(0))
+               PG_RETURN_NULL();
+
+       key = PG_GETARG_TEXT_PP(0);
+       pair.key.type = hsvString;
+       pair.key.string.val = VARDATA_ANY(key);
+       pair.key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key));
+       pair.key.size = pair.key.string.len + sizeof(HEntry);
+
+       if (PG_ARGISNULL(1))
+       {
+               pair.value.type = hsvNull;
+               pair.value.size = sizeof(HEntry);
+       }
+       else
+       {
+               pair.value.type = hsvNumeric;
+               pair.value.numeric = PG_GETARG_NUMERIC(1);
+               pair.value.size = sizeof(HEntry) + sizeof(HEntry) +
+                                                       VARSIZE_ANY(pair.value.numeric);
+       }
+
+       v.type = hsvHash;
+       v.size = sizeof(HEntry) + pair.key.size + pair.value.size;
+       v.hash.npairs = 1;
+       v.hash.pairs = &pair;
+
+       PG_RETURN_POINTER(hstoreDump(&v));
+}
+
+PG_FUNCTION_INFO_V1(hstore_from_th);
+Datum          hstore_from_th(PG_FUNCTION_ARGS);
+Datum
+hstore_from_th(PG_FUNCTION_ARGS)
+{
+       text            *key;
+       HStoreValue     v;
+       HStorePair      pair;
+
+       if (PG_ARGISNULL(0))
+               PG_RETURN_NULL();
+
+       key = PG_GETARG_TEXT_PP(0);
+       pair.key.type = hsvString;
+       pair.key.string.val = VARDATA_ANY(key);
+       pair.key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key));
+       pair.key.size = pair.key.string.len + sizeof(HEntry);
+
+       if (PG_ARGISNULL(1))
+       {
+               pair.value.type = hsvNull;
+               pair.value.size = sizeof(HEntry);
+       }
+       else
+       {
+               HStore          *val = NULL;
+
+               val = PG_GETARG_HS(1);
+               pair.value.type = hsvBinary;
+               pair.value.binary.data = VARDATA_ANY(val);
+               pair.value.binary.len = VARSIZE_ANY_EXHDR(val);
+               pair.value.size = pair.value.binary.len + sizeof(HEntry) * 2;
+       }
+
+       v.type = hsvHash;
+       v.size = sizeof(HEntry) + pair.key.size + pair.value.size;
+       v.hash.npairs = 1;
+       v.hash.pairs = &pair;
+
+       PG_RETURN_POINTER(hstoreDump(&v));
+}
+
+PG_FUNCTION_INFO_V1(hstore_from_arrays);
+PG_FUNCTION_INFO_V1(hstore_scalar_from_text);
+Datum          hstore_scalar_from_text(PG_FUNCTION_ARGS);
+Datum
+hstore_scalar_from_text(PG_FUNCTION_ARGS)
+{
+       HStoreValue     a, v;
+
+       if (PG_ARGISNULL(0))
+       {
+               v.type = hsvNull;
+               v.size = sizeof(HEntry);
+       }
+       else
+       {
+               text    *scalar;
+
+               scalar = PG_GETARG_TEXT_PP(0);
+               v.type = hsvString;
+               v.string.val = VARDATA_ANY(scalar);
+               v.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(scalar));
+               v.size = v.string.len + sizeof(HEntry);
+       }
+
+       a.type = hsvArray;
+       a.size = sizeof(HEntry) + v.size;
+       a.array.nelems = 1;
+       a.array.elems = &v;
+       a.array.scalar = true;
+
+       PG_RETURN_POINTER(hstoreDump(&a));
+}
+
+PG_FUNCTION_INFO_V1(hstore_scalar_from_bool);
+Datum          hstore_scalar_from_bool(PG_FUNCTION_ARGS);
+Datum
+hstore_scalar_from_bool(PG_FUNCTION_ARGS)
+{
+       HStoreValue     a, v;
+
+       if (PG_ARGISNULL(0))
+       {
+               v.type = hsvNull;
+               v.size = sizeof(HEntry);
+       }
+       else
+       {
+               v.type = hsvBool;
+               v.boolean = PG_GETARG_BOOL(0);
+               v.size = sizeof(HEntry);
+       }
+
+       a.type = hsvArray;
+       a.size = sizeof(HEntry) + v.size;
+       a.array.nelems = 1;
+       a.array.elems = &v;
+       a.array.scalar = true;
+
+       PG_RETURN_POINTER(hstoreDump(&a));
+}
+
+PG_FUNCTION_INFO_V1(hstore_scalar_from_numeric);
+Datum          hstore_scalar_from_numeric(PG_FUNCTION_ARGS);
+Datum
+hstore_scalar_from_numeric(PG_FUNCTION_ARGS)
+{
+       HStoreValue     a, v;
+
+       if (PG_ARGISNULL(0))
+       {
+               v.type = hsvNull;
+               v.size = sizeof(HEntry);
+       }
+       else
+       {
+               v.type = hsvNumeric;
+               v.numeric = PG_GETARG_NUMERIC(0);
+               v.size = VARSIZE_ANY(v.numeric) + 2*sizeof(HEntry);
+       }
+
+       a.type = hsvArray;
+       a.size = sizeof(HEntry) + v.size;
+       a.array.nelems = 1;
+       a.array.elems = &v;
+       a.array.scalar = true;
+
+       PG_RETURN_POINTER(hstoreDump(&a));
+}
+
+Datum          hstore_from_arrays(PG_FUNCTION_ARGS);
+Datum
+hstore_from_arrays(PG_FUNCTION_ARGS)
+{
+       HStoreValue v;
+       Datum      *key_datums;
+       bool       *key_nulls;
+       int                     key_count;
+       Datum      *value_datums;
+       bool       *value_nulls;
+       int                     value_count;
+       ArrayType  *key_array;
+       ArrayType  *value_array;
+       int                     i;
+
+       if (PG_ARGISNULL(0))
+               PG_RETURN_NULL();
+
+       key_array = PG_GETARG_ARRAYTYPE_P(0);
+
+       Assert(ARR_ELEMTYPE(key_array) == TEXTOID);
+
+       /*
+        * must check >1 rather than != 1 because empty arrays have 0 dimensions,
+        * not 1
+        */
+
+       if (ARR_NDIM(key_array) > 1)
+               ereport(ERROR,
+                               (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                errmsg("wrong number of array subscripts")));
+
+       deconstruct_array(key_array,
+                                         TEXTOID, -1, false, 'i',
+                                         &key_datums, &key_nulls, &key_count);
+
+       /* see discussion in arrayToHStoreSortedArray() */
+       if (key_count > MaxAllocSize / sizeof(HStorePair))
+               ereport(ERROR,
+                               (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                         errmsg("number of pairs (%d) exceeds the maximum allowed (%d)",
+                                        key_count, (int) (MaxAllocSize / sizeof(HStorePair)))));
+
+       /* value_array might be NULL */
+
+       if (PG_ARGISNULL(1))
+       {
+               value_array = NULL;
+               value_count = key_count;
+               value_datums = NULL;
+               value_nulls = NULL;
+       }
+       else
+       {
+               value_array = PG_GETARG_ARRAYTYPE_P(1);
+
+               Assert(ARR_ELEMTYPE(value_array) == TEXTOID);
+
+               if (ARR_NDIM(value_array) > 1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                        errmsg("wrong number of array subscripts")));
+
+               if ((ARR_NDIM(key_array) > 0 || ARR_NDIM(value_array) > 0) &&
+                       (ARR_NDIM(key_array) != ARR_NDIM(value_array) ||
+                        ARR_DIMS(key_array)[0] != ARR_DIMS(value_array)[0] ||
+                        ARR_LBOUND(key_array)[0] != ARR_LBOUND(value_array)[0]))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                        errmsg("arrays must have same bounds")));
+
+               deconstruct_array(value_array,
+                                                 TEXTOID, -1, false, 'i',
+                                                 &value_datums, &value_nulls, &value_count);
+
+               Assert(key_count == value_count);
+       }
+
+       v.type = hsvHash;
+       v.size = 2 * sizeof(HEntry);
+       v.hash.pairs = palloc(key_count * sizeof(*v.hash.pairs));
+       v.hash.npairs = key_count;
+
+       for (i = 0; i < key_count; ++i)
+       {
+               if (key_nulls[i])
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("null value not allowed for hstore key")));
+
+               v.hash.pairs[i].key.type = hsvString;
+               v.hash.pairs[i].key.string.val = VARDATA_ANY(key_datums[i]);
+               v.hash.pairs[i].key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key_datums[i]));
+               v.hash.pairs[i].key.size = sizeof(HEntry) +
+                                                                       v.hash.pairs[i].key.string.len;
+
+               if (!value_nulls || value_nulls[i])
+               {
+                       v.hash.pairs[i].value.type = hsvNull;
+                       v.hash.pairs[i].value.size = sizeof(HEntry);
+               }
+               else
+               {
+                       v.hash.pairs[i].value.type = hsvString;
+                       v.hash.pairs[i].value.size = sizeof(HEntry);
+                       v.hash.pairs[i].value.string.val = VARDATA_ANY(value_datums[i]);
+                       v.hash.pairs[i].value.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(value_datums[i]));
+                       v.hash.pairs[i].value.size = sizeof(HEntry) +
+                                                                                       v.hash.pairs[i].value.string.len;
+               }
+
+               v.size += v.hash.pairs[i].key.size + v.hash.pairs[i].value.size;
+       }
+
+       uniqueHStoreValue(&v);
+
+
+       PG_RETURN_POINTER(hstoreDump(&v));
+}
+
+
+PG_FUNCTION_INFO_V1(hstore_from_array);
+Datum          hstore_from_array(PG_FUNCTION_ARGS);
+Datum
+hstore_from_array(PG_FUNCTION_ARGS)
+{
+       ArrayType  *in_array = PG_GETARG_ARRAYTYPE_P(0);
+       int                     ndims = ARR_NDIM(in_array);
+       int                     count;
+       HStoreValue     v;
+       Datum      *in_datums;
+       bool       *in_nulls;
+       int                     in_count;
+       int                     i;
+
+       Assert(ARR_ELEMTYPE(in_array) == TEXTOID);
+
+       switch (ndims)
+       {
+               case 0:
+                       PG_RETURN_POINTER(hstoreDump(NULL));
+
+               case 1:
+                       if ((ARR_DIMS(in_array)[0]) % 2)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                                errmsg("array must have even number of elements")));
+                       break;
+
+               case 2:
+                       if ((ARR_DIMS(in_array)[1]) != 2)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                                errmsg("array must have two columns")));
+                       break;
+
+               default:
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                        errmsg("wrong number of array subscripts")));
+       }
+
+       deconstruct_array(in_array,
+                                         TEXTOID, -1, false, 'i',
+                                         &in_datums, &in_nulls, &in_count);
+
+       count = in_count / 2;
+
+       /* see discussion in arrayToHStoreSortedArray() */
+       if (count > MaxAllocSize / sizeof(HStorePair))
+               ereport(ERROR,
+                               (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                         errmsg("number of pairs (%d) exceeds the maximum allowed (%d)",
+                                        count, (int) (MaxAllocSize / sizeof(HStorePair)))));
+
+       v.type = hsvHash;
+       v.size = 2*sizeof(HEntry);
+       v.hash.npairs = count;
+       v.hash.pairs = palloc(count * sizeof(HStorePair));
+
+       for (i = 0; i < count; ++i)
+       {
+               if (in_nulls[i * 2])
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("null value not allowed for hstore key")));
+
+               v.hash.pairs[i].key.type = hsvString;
+               v.hash.pairs[i].key.string.val = VARDATA_ANY(in_datums[i * 2]);
+               v.hash.pairs[i].key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(in_datums[i * 2]));
+               v.hash.pairs[i].key.size = sizeof(HEntry) +
+                                                                       v.hash.pairs[i].key.string.len;
+
+               if (in_nulls[i * 2 + 1])
+               {
+                       v.hash.pairs[i].value.type = hsvNull;
+                       v.hash.pairs[i].value.size = sizeof(HEntry);
+               }
+               else
+               {
+                       v.hash.pairs[i].value.type = hsvString;
+                       v.hash.pairs[i].value.size = sizeof(HEntry);
+                       v.hash.pairs[i].value.string.val = VARDATA_ANY(in_datums[i * 2 + 1]);
+                       v.hash.pairs[i].value.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(in_datums[i * 2 + 1]));
+                       v.hash.pairs[i].value.size = sizeof(HEntry) +
+                                                                                       v.hash.pairs[i].value.string.len;
+               }
+
+               v.size += v.hash.pairs[i].key.size + v.hash.pairs[i].value.size;
+       }
+
+       uniqueHStoreValue(&v);
+
+       PG_RETURN_POINTER(hstoreDump(&v));
+}
+
+/* most of hstore_from_record is shamelessly swiped from record_out */
+
+/*
+ * structure to cache metadata needed for record I/O
+ */
+typedef struct ColumnIOData
+{
+       Oid                     column_type;
+       Oid                     typiofunc;
+       Oid                     typioparam;
+       FmgrInfo        proc;
+} ColumnIOData;
+
+typedef struct RecordIOData
+{
+       Oid                     record_type;
+       int32           record_typmod;
+       int                     ncolumns;
+       ColumnIOData columns[1];        /* VARIABLE LENGTH ARRAY */
+} RecordIOData;
+
+PG_FUNCTION_INFO_V1(hstore_from_record);
+Datum          hstore_from_record(PG_FUNCTION_ARGS);
+Datum
+hstore_from_record(PG_FUNCTION_ARGS)
+{
+       HeapTupleHeader rec;
+       HStore             *out;
+       HStoreValue        v;
+       Oid                             tupType;
+       int32                   tupTypmod;
+       TupleDesc               tupdesc;
+       HeapTupleData   tuple;
+       RecordIOData   *my_extra;
+       int                             ncolumns;
+       int                             i;
+       Datum              *values;
+       bool               *nulls;
+
+       if (PG_ARGISNULL(0))
+       {
+               Oid                     argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+
+               /*
+                * have no tuple to look at, so the only source of type info is the
+                * argtype. The lookup_rowtype_tupdesc call below will error out if we
+                * don't have a known composite type oid here.
+                */
+               tupType = argtype;
+               tupTypmod = -1;
+
+               rec = NULL;
+       }
+       else
+       {
+               rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+               /* Extract type info from the tuple itself */
+               tupType = HeapTupleHeaderGetTypeId(rec);
+               tupTypmod = HeapTupleHeaderGetTypMod(rec);
+       }
+
+       tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+       ncolumns = tupdesc->natts;
+
+       /*
+        * We arrange to look up the needed I/O info just once per series of
+        * calls, assuming the record type doesn't change underneath us.
+        */
+       my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+       if (my_extra == NULL ||
+               my_extra->ncolumns != ncolumns)
+       {
+               fcinfo->flinfo->fn_extra =
+                       MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+                                                          sizeof(RecordIOData) - sizeof(ColumnIOData)
+                                                          + ncolumns * sizeof(ColumnIOData));
+               my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+               my_extra->record_type = InvalidOid;
+               my_extra->record_typmod = 0;
+       }
+
+       if (my_extra->record_type != tupType ||
+               my_extra->record_typmod != tupTypmod)
+       {
+               MemSet(my_extra, 0,
+                          sizeof(RecordIOData) - sizeof(ColumnIOData)
+                          + ncolumns * sizeof(ColumnIOData));
+               my_extra->record_type = tupType;
+               my_extra->record_typmod = tupTypmod;
+               my_extra->ncolumns = ncolumns;
+       }
+
+       v.type = hsvHash;
+       v.size = 2*sizeof(HEntry);
+       v.hash.npairs = ncolumns;
+       Assert(ncolumns <= MaxTupleAttributeNumber);            /* thus, no overflow */
+       v.hash.pairs = palloc(ncolumns * sizeof(HStorePair));
+
+       if (rec)
+       {
+               /* Build a temporary HeapTuple control structure */
+               tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+               ItemPointerSetInvalid(&(tuple.t_self));
+               tuple.t_tableOid = InvalidOid;
+               tuple.t_data = rec;
+
+               values = (Datum *) palloc(ncolumns * sizeof(Datum));
+               nulls = (bool *) palloc(ncolumns * sizeof(bool));
+
+               /* Break down the tuple into fields */
+               heap_deform_tuple(&tuple, tupdesc, values, nulls);
+       }
+       else
+       {
+               values = NULL;
+               nulls = NULL;
+       }
+
+       for (i = 0; i < ncolumns; ++i)
+       {
+               ColumnIOData *column_info = &my_extra->columns[i];
+               Oid                     column_type = tupdesc->attrs[i]->atttypid;
+               char       *value;
+
+               /* Ignore dropped columns in datatype */
+               if (tupdesc->attrs[i]->attisdropped)
+                       continue;
+
+               v.hash.pairs[i].key.type = hsvString;
+               v.hash.pairs[i].key.string.val = NameStr(tupdesc->attrs[i]->attname);
+               v.hash.pairs[i].key.string.len = hstoreCheckKeyLen(strlen(v.hash.pairs[i].key.string.val));
+               v.hash.pairs[i].key.size = sizeof(HEntry) +
+                                                                       v.hash.pairs[i].key.string.len;
+
+               if (!nulls || nulls[i])
+               {
+                       v.hash.pairs[i].value.type = hsvNull;
+                       v.hash.pairs[i].value.size = sizeof(HEntry);
+               }
+               else
+               {
+                       /*
+                        * Convert the column value to hstore's values
+                        */
+                       if (column_type == BOOLOID)
+                       {
+                               v.hash.pairs[i].value.type = hsvBool;
+                               v.hash.pairs[i].value.boolean = DatumGetBool(values[i]);
+                               v.hash.pairs[i].value.size = sizeof(HEntry);
+                       }
+                       else if (TypeCategory(column_type) == TYPCATEGORY_NUMERIC)
+                       {
+                               Oid                             castOid = InvalidOid;
+                               CoercionMethod  method;
+
+                               v.hash.pairs[i].value.type = hsvNumeric;
+
+                               castOid = searchCast(column_type, NUMERICOID, &method);
+                               if (castOid == InvalidOid)
+                               {
+                                       if (method != COERCION_METHOD_BINARY)
+                                               elog(ERROR, "Could not cast numeric category type to numeric '%c'", (char)method);
+
+                                       v.hash.pairs[i].value.numeric = DatumGetNumeric(values[i]);
+                               }
+                               else
+                               {
+                                       v.hash.pairs[i].value.numeric = 
+                                               DatumGetNumeric(OidFunctionCall1(castOid, values[i]));
+
+                               }
+                               v.hash.pairs[i].value.size = 2*sizeof(HEntry) +
+                                                               VARSIZE_ANY(v.hash.pairs[i].value.numeric);
+                       }
+                       else
+                       {
+                               if (column_info->column_type != column_type)
+                               {
+                                       bool            typIsVarlena;
+
+                                       getTypeOutputInfo(column_type,
+                                                                         &column_info->typiofunc,
+                                                                         &typIsVarlena);
+                                       fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+                                                                 fcinfo->flinfo->fn_mcxt);
+                                       column_info->column_type = column_type;
+                               }
+
+                               value = OutputFunctionCall(&column_info->proc, values[i]);
+
+                               v.hash.pairs[i].value.type = hsvString;
+                               v.hash.pairs[i].value.string.val = value;
+                               v.hash.pairs[i].value.string.len = hstoreCheckValLen(strlen(value));
+                               v.hash.pairs[i].value.size = sizeof(HEntry) +
+                                                                               v.hash.pairs[i].value.string.len;
+                       }
+               }
+
+               v.size += v.hash.pairs[i].key.size + v.hash.pairs[i].value.size;
+       }
+
+       uniqueHStoreValue(&v);
+
+       out = hstoreDump(&v);
+
+       ReleaseTupleDesc(tupdesc);
+
+       PG_RETURN_POINTER(out);
+}
+
+
+PG_FUNCTION_INFO_V1(hstore_populate_record);
+Datum          hstore_populate_record(PG_FUNCTION_ARGS);
+Datum
+hstore_populate_record(PG_FUNCTION_ARGS)
+{
+       Oid                     argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+       HStore     *hs;
+       HeapTupleHeader rec;
+       Oid                     tupType;
+       int32           tupTypmod;
+       TupleDesc       tupdesc;
+       HeapTupleData tuple;
+       HeapTuple       rettuple;
+       RecordIOData *my_extra;
+       int                     ncolumns;
+       int                     i;
+       Datum      *values;
+       bool       *nulls;
+
+       if (!type_is_rowtype(argtype))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("first argument must be a rowtype")));
+
+       if (PG_ARGISNULL(0))
+       {
+               if (PG_ARGISNULL(1))
+                       PG_RETURN_NULL();
+
+               rec = NULL;
+
+               /*
+                * have no tuple to look at, so the only source of type info is the
+                * argtype. The lookup_rowtype_tupdesc call below will error out if we
+                * don't have a known composite type oid here.
+                */
+               tupType = argtype;
+               tupTypmod = -1;
+       }
+       else
+       {
+               rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+               if (PG_ARGISNULL(1))
+                       PG_RETURN_POINTER(rec);
+
+               /* Extract type info from the tuple itself */
+               tupType = HeapTupleHeaderGetTypeId(rec);
+               tupTypmod = HeapTupleHeaderGetTypMod(rec);
+       }
+
+       hs = PG_GETARG_HS(1);
+
+       /*
+        * if the input hstore is empty, we can only skip the rest if we were
+        * passed in a non-null record, since otherwise there may be issues with
+        * domain nulls.
+        */
+
+       if (HS_ISEMPTY(hs) && rec)
+               PG_RETURN_POINTER(rec);
+
+       tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+       ncolumns = tupdesc->natts;
+
+       if (rec)
+       {
+               /* Build a temporary HeapTuple control structure */
+               tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+               ItemPointerSetInvalid(&(tuple.t_self));
+               tuple.t_tableOid = InvalidOid;
+               tuple.t_data = rec;
+       }
+
+       /*
+        * We arrange to look up the needed I/O info just once per series of
+        * calls, assuming the record type doesn't change underneath us.
+        */
+       my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+       if (my_extra == NULL ||
+               my_extra->ncolumns != ncolumns)
+       {
+               fcinfo->flinfo->fn_extra =
+                       MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+                                                          sizeof(RecordIOData) - sizeof(ColumnIOData)
+                                                          + ncolumns * sizeof(ColumnIOData));
+               my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+               my_extra->record_type = InvalidOid;
+               my_extra->record_typmod = 0;
+       }
+
+       if (my_extra->record_type != tupType ||
+               my_extra->record_typmod != tupTypmod)
+       {
+               MemSet(my_extra, 0,
+                          sizeof(RecordIOData) - sizeof(ColumnIOData)
+                          + ncolumns * sizeof(ColumnIOData));
+               my_extra->record_type = tupType;
+               my_extra->record_typmod = tupTypmod;
+               my_extra->ncolumns = ncolumns;
+       }
+
+       values = (Datum *) palloc(ncolumns * sizeof(Datum));
+       nulls = (bool *) palloc(ncolumns * sizeof(bool));
+
+       if (rec)
+       {
+               /* Break down the tuple into fields */
+               heap_deform_tuple(&tuple, tupdesc, values, nulls);
+       }
+       else
+       {
+               for (i = 0; i < ncolumns; ++i)
+               {
+                       values[i] = (Datum) 0;
+                       nulls[i] = true;
+               }
+       }
+
+       for (i = 0; i < ncolumns; ++i)
+       {
+               ColumnIOData *column_info = &my_extra->columns[i];
+               Oid                     column_type = tupdesc->attrs[i]->atttypid;
+               HStoreValue     *v = NULL;
+
+               /* Ignore dropped columns in datatype */
+               if (tupdesc->attrs[i]->attisdropped)
+               {
+                       nulls[i] = true;
+                       continue;
+               }
+
+               if (!HS_ISEMPTY(hs))
+               {
+                       char *key = NameStr(tupdesc->attrs[i]->attname);
+
+                       v = findUncompressedHStoreValue(VARDATA(hs), HS_FLAG_HSTORE, NULL, key, strlen(key));
+               }
+
+               /*
+                * we can't just skip here if the key wasn't found since we might have
+                * a domain to deal with. If we were passed in a non-null record
+                * datum, we assume that the existing values are valid (if they're
+                * not, then it's not our fault), but if we were passed in a null,
+                * then every field which we don't populate needs to be run through
+                * the input function just in case it's a domain type.
+                */
+               if (v == NULL && rec)
+                       continue;
+
+               /*
+                * Prepare to convert the column value from text
+                */
+               if (column_info->column_type != column_type)
+               {
+                       getTypeInputInfo(column_type,
+                                                        &column_info->typiofunc,
+                                                        &column_info->typioparam);
+                       fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+                                                 fcinfo->flinfo->fn_mcxt);
+                       column_info->column_type = column_type;
+               }
+
+               if (v == NULL || v->type == hsvNull)
+               {
+                       /*
+                        * need InputFunctionCall to happen even for nulls, so that domain
+                        * checks are done
+                        */
+                       values[i] = InputFunctionCall(&column_info->proc, NULL,
+                                                                                 column_info->typioparam,
+                                                                                 tupdesc->attrs[i]->atttypmod);
+                       nulls[i] = true;
+               }
+               else
+               {
+                       char *s = NULL;
+
+                       if (v->type == hsvString)
+                               s = pnstrdup(v->string.val, v->string.len);
+                       else if (v->type == hsvBool)
+                               s = pnstrdup((v->boolean) ? "t" : "f", 1);
+                       else if (v->type == hsvNumeric)
+                               s = DatumGetCString(DirectFunctionCall1(numeric_out,
+                                                                                                               PointerGetDatum(v->numeric)));
+                       else if (v->type == hsvBinary && column_type == JSONOID)
+                               s = hstoreToCString(NULL, v->binary.data, v->binary.len,
+                                                                       SET_PRETTY_PRINT_VAR(JsonOutput | RootHashDecorated));
+                       else if (v->type == hsvBinary && type_is_array(column_type))
+                               s = hstoreToCString(NULL, v->binary.data, v->binary.len,
+                                                                       SET_PRETTY_PRINT_VAR(ArrayCurlyBraces));
+                       else if (v->type == hsvBinary)
+                               s = hstoreToCString(NULL, v->binary.data, v->binary.len,
+                                                                       SET_PRETTY_PRINT_VAR(0));
+                       else
+                               elog(PANIC, "Wrong hstore");
+
+                       values[i] = InputFunctionCall(&column_info->proc, s,
+                                                                                 column_info->typioparam,
+                                                                                 tupdesc->attrs[i]->atttypmod);
+                       nulls[i] = false;
+               }
+       }
+
+       rettuple = heap_form_tuple(tupdesc, values, nulls);
+
+       ReleaseTupleDesc(tupdesc);
+
+       PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
+}
+
+bool
+stringIsNumber(char *string, int len, bool jsonNumber) {
+       enum {
+               SIN_FIRSTINT,
+               SIN_ZEROINT,
+               SIN_INT,
+               SIN_SCALE,
+               SIN_MSIGN,
+               SIN_MANTISSA
+       } sinState;
+       char    *c;
+       bool    r;
+
+       if (*string == '-' || *string == '+')
+       {
+               string++;
+               len--;
+       }
+
+       c = string;
+       r = true;
+       sinState = SIN_FIRSTINT;
+
+       while(r && c - string < len)
+       {
+               switch(sinState)
+               {
+                       case SIN_FIRSTINT:
+                               if (*c == '0' && jsonNumber)
+                                       sinState = SIN_ZEROINT;
+                               else if (*c == '.')
+                                       sinState = SIN_SCALE;
+                               else if (isdigit(*c))
+                                       sinState = SIN_INT;
+                               else
+                                       r = false;
+                               break;
+                       case SIN_ZEROINT:
+                               if (*c == '.')
+                                       sinState = SIN_SCALE;
+                               else
+                                       r = false;
+                               break;
+                       case SIN_INT:
+                               if (*c == '.')
+                                       sinState = SIN_SCALE;
+                               else if (*c == 'e' || *c == 'E')
+                                       sinState = SIN_MSIGN;
+                               else if (!isdigit(*c))
+                                       r = false;
+                               break;
+                       case SIN_SCALE:
+                               if (*c == 'e' || *c == 'E')
+                                       sinState = SIN_MSIGN;
+                               else if (!isdigit(*c))
+                                       r = false;
+                               break;
+                       case SIN_MSIGN:
+                               if (*c == '-' || *c == '+' || isdigit(*c))
+                                       sinState = SIN_MANTISSA;
+                               else
+                                       r = false;
+                               break;
+                       case SIN_MANTISSA:
+                               if (!isdigit(*c))
+                                       r = false;
+                               break;
+                       default:
+                               abort();
+               }
+
+               c++;
+       }
+
+       if (sinState == SIN_MSIGN)
+               r = false;
+
+       return r;
+}
+
+static void
+printIndent(StringInfo out, bool isRootHash, HStoreOutputKind kind, int level)
+{
+       if (kind & PrettyPrint)
+       {
+               int i;
+
+               if (isRootHash && (kind & RootHashDecorated) == 0)
+                       level--;
+               for(i=0; i<4*level; i++)
+                       appendStringInfoCharMacro(out, ' ');
+       }
+}
+
+static void
+printCR(StringInfo out, HStoreOutputKind kind)
+{
+       if (kind & PrettyPrint)
+               appendStringInfoCharMacro(out, '\n');
+}
+
+static void
+escape_hstore(StringInfo out, char *string, uint32 len)
+{
+       char       *ptr = string;
+
+       appendStringInfoCharMacro(out, '"');
+       while (ptr - string < len)
+       {
+               if (*ptr == '"' || *ptr == '\\')
+                       appendStringInfoCharMacro(out, '\\');
+               appendStringInfoCharMacro(out, *ptr);
+               ptr++;
+       }
+       appendStringInfoCharMacro(out, '"');
+}
+
+static void
+putEscapedString(StringInfo out, HStoreOutputKind kind,
+                                char *string, uint32 len)
+{
+       if (kind & LooseOutput)
+       {
+               if (len == 1 && *string == 't')
+                       appendStringInfoString(out, (kind & JsonOutput) ? "true" : "t" );
+               else if (len == 1 && *string == 'f')
+                       appendStringInfoString(out, (kind & JsonOutput) ? "false" : "f");
+               else if (len > 0 && stringIsNumber(string, len, true))
+                       appendBinaryStringInfo(out, string, len);
+               else if (kind & JsonOutput)
+                       escape_json(out, pnstrdup(string, len));
+               else
+                       escape_hstore(out, string, len);
+       }
+       else
+       {
+               if (kind & JsonOutput)
+                       escape_json(out, pnstrdup(string, len));
+               else
+                       escape_hstore(out, string, len);
+       }
+}
+
+static void
+putEscapedValue(StringInfo out, HStoreOutputKind kind, HStoreValue *v)
+{
+       switch(v->type)
+       {
+               case hsvNull:
+                       appendBinaryStringInfo(out,
+                                                                  (kind & JsonOutput) ? "null" : "NULL", 4);
+                       break;
+               case hsvString:
+                       putEscapedString(out, kind, v->string.val, v->string.len);
+                       break;
+               case hsvBool:
+                       if ((kind & JsonOutput) == 0)
+                               appendBinaryStringInfo(out, (v->boolean) ? "t" : "f", 1);
+                       else if (v->boolean)
+                               appendBinaryStringInfo(out, "true", 4);
+                       else
+                               appendBinaryStringInfo(out, "false", 5);
+                       break;
+               case hsvNumeric:
+                       appendStringInfoString(out, DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(v->numeric))));
+                       break;
+               default:
+                       elog(PANIC, "Unknown type");
+       }
+}
+
+static bool
+needBrackets(int level, bool isArray, HStoreOutputKind kind, bool isScalar)
+{
+       bool res;
+
+       if (isArray && isScalar)
+               res = false;
+       else if (level == 0)
+               res = (isArray || (kind & RootHashDecorated)) ? true : false;
+       else
+               res = true;
+
+       return res;
+}
+
+static bool
+isArrayBrackets(HStoreOutputKind kind)
+{
+       return ((kind & ArrayCurlyBraces) == 0) ? true : false;
+}
+
+
+char*
+hstoreToCString(StringInfo out, char *in, int len /* just estimation */,
+                 HStoreOutputKind kind)
+{
+       bool                    first = true;
+       HStoreIterator  *it;
+       int                             type;
+       HStoreValue             v;
+       int                             level = 0;
+       bool                    isRootHash = false;
+
+       if (out == NULL)
+               out = makeStringInfo();
+
+       if (in == NULL)
+       {
+               appendStringInfoString(out, "");
+               return out->data;
+       }
+
+       enlargeStringInfo(out, (len >= 0) ? len : 64);
+
+       it = HStoreIteratorInit(in);
+
+       while((type = HStoreIteratorGet(&it, &v, false)) != 0)
+       {
+reout:
+               switch(type)
+               {
+                       case WHS_BEGIN_ARRAY:
+                               if (first == false)
+                               {
+                                       appendBinaryStringInfo(out, ", ", 2);
+                                       printCR(out, kind);
+                               }
+                               first = true;
+
+                               if (needBrackets(level, true, kind, v.array.scalar))
+                               {
+                                       printIndent(out, isRootHash, kind, level);
+                                       appendStringInfoChar(out, isArrayBrackets(kind) ? '[' : '{');
+                                       printCR(out, kind);
+                               }
+                               level++;
+                               break;
+                       case WHS_BEGIN_HASH:
+                               if (first == false)
+                               {
+                                       appendBinaryStringInfo(out, ", ", 2);
+                                       printCR(out, kind);
+                               }
+                               first = true;
+
+                               if (level == 0)
+                                       isRootHash = true;
+
+                               if (needBrackets(level, false, kind, false))
+                               {
+                                       printIndent(out, isRootHash, kind, level);
+                                       appendStringInfoCharMacro(out, '{');
+                                       printCR(out, kind);
+                               }
+
+                               level++;
+                               break;
+                       case WHS_KEY:
+                               if (first == false)
+                               {
+                                       appendBinaryStringInfo(out, ", ", 2);
+                                       printCR(out, kind);
+                               }
+                               first = true;
+
+                               printIndent(out, isRootHash, kind, level);
+                               /* key should not be loose */
+                               putEscapedValue(out, kind & ~LooseOutput, &v);
+                               appendBinaryStringInfo(out,
+                                                                          (kind & JsonOutput) ? ": " : "=>", 2);
+
+                               type = HStoreIteratorGet(&it, &v, false);
+                               if (type == WHS_VALUE)
+                               {
+                                       first = false;
+                                       putEscapedValue(out, kind, &v);
+                               }
+                               else
+                               {
+                                       Assert(type == WHS_BEGIN_HASH || type == WHS_BEGIN_ARRAY);
+                                       printCR(out, kind);
+                                       /*
+                                        * We need to rerun current switch() due to put
+                                        * in current place object which we just got
+                                        * from iterator.
+                                        */
+                                       goto reout;
+                               }
+                               break;
+                       case WHS_ELEM:
+                               if (first == false)
+                               {
+                                       appendBinaryStringInfo(out, ", ", 2);
+                                       printCR(out, kind);
+                               }
+                               else
+                               {
+                                       first = false;
+                               }
+
+                               printIndent(out, isRootHash, kind, level);
+                               putEscapedValue(out, kind, &v);
+                               break;
+                       case WHS_END_ARRAY:
+                               level--;
+                               if (needBrackets(level, true, kind, v.array.scalar))
+                               {
+                                       printCR(out, kind);
+                                       printIndent(out, isRootHash, kind, level);
+                                       appendStringInfoChar(out, isArrayBrackets(kind) ? ']' : '}');
+                               }
+                               first = false;
+                               break;
+                       case WHS_END_HASH:
+                               level--;
+                               if (needBrackets(level, false, kind, false))
+                               {
+                                       printCR(out, kind);
+                                       printIndent(out, isRootHash, kind, level);
+                                       appendStringInfoCharMacro(out, '}');
+                               }
+                               first = false;
+                               break;
+                       default:
+                               elog(PANIC, "Wrong flags");
+               }
+       }
+
+       Assert(level == 0);
+
+       return out->data;
+}
+
+text*
+HStoreValueToText(HStoreValue *v)
+{
+       text            *out;
+
+       if (v == NULL || v->type == hsvNull)
+       {
+               out = NULL;
+       }
+       else if (v->type == hsvString)
+       {
+               out = cstring_to_text_with_len(v->string.val, v->string.len);
+       }
+       else if (v->type == hsvBool)
+       {
+               out = cstring_to_text_with_len((v->boolean) ? "t" : "f", 1);
+       }
+       else if (v->type == hsvNumeric)
+       {
+               out = cstring_to_text(DatumGetCString(
+                               DirectFunctionCall1(numeric_out, PointerGetDatum(v->numeric))
+               ));
+       }
+       else
+       {
+               StringInfo      str;
+
+               str = makeStringInfo();
+               appendBinaryStringInfo(str, "    ", 4); /* VARHDRSZ */
+
+               hstoreToCString(str, v->binary.data, v->binary.len,
+                                               SET_PRETTY_PRINT_VAR(0));
+
+               out = (text*)str->data;
+               SET_VARSIZE(out, str->len);
+       }
+
+       return out;
+}
+
+PG_FUNCTION_INFO_V1(hstore_out);
+Datum          hstore_out(PG_FUNCTION_ARGS);
+Datum
+hstore_out(PG_FUNCTION_ARGS)
+{
+       HStore  *hs = PG_GETARG_HS(0);
+       char    *out;
+
+       out = hstoreToCString(NULL, (HS_ISEMPTY(hs)) ? NULL : VARDATA(hs),
+                                                 VARSIZE(hs), SET_PRETTY_PRINT_VAR(0));
+
+       PG_RETURN_CSTRING(out);
+}
+
+PG_FUNCTION_INFO_V1(hstore_send);
+Datum          hstore_send(PG_FUNCTION_ARGS);
+Datum
+hstore_send(PG_FUNCTION_ARGS)
+{
+       HStore                  *in = PG_GETARG_HS(0);
+       StringInfoData  buf;
+
+       pq_begintypsend(&buf);
+
+       if (HS_ISEMPTY(in))
+       {
+               pq_sendint(&buf, 0, 4);
+       }
+       else
+       {
+               HStoreIterator  *it;
+               int                             type;
+               HStoreValue             v;
+               uint32                  flag;
+               bytea                   *nbuf;
+
+               enlargeStringInfo(&buf, VARSIZE_ANY(in) /* just estimation */);
+
+               it = HStoreIteratorInit(VARDATA_ANY(in));
+
+               while((type = HStoreIteratorGet(&it, &v, false)) != 0)
+               {
+                       switch(type)
+                       {
+                               case WHS_BEGIN_ARRAY:
+                                       flag = (v.array.scalar) ? HENTRY_ISSCALAR : HENTRY_ISARRAY;
+                                       pq_sendint(&buf, v.array.nelems | flag, 4);
+                                       break;
+                               case WHS_BEGIN_HASH:
+                                       pq_sendint(&buf, v.hash.npairs | HENTRY_ISHASH, 4);
+                                       break;
+                               case WHS_KEY:
+                                       pq_sendint(&buf, v.string.len | HENTRY_ISSTRING, 4);
+                                       pq_sendtext(&buf, v.string.val, v.string.len);
+                                       break;
+                               case WHS_ELEM:
+                               case WHS_VALUE:
+                                       switch(v.type)
+                                       {
+                                               case hsvNull:
+                                                       pq_sendint(&buf, HENTRY_ISNULL, 4);
+                                                       break;
+                                               case hsvString:
+                                                       pq_sendint(&buf, v.string.len | HENTRY_ISSTRING, 4);
+                                                       pq_sendtext(&buf, v.string.val, v.string.len);
+                                                       break;
+                                               case hsvBool:
+                                                       pq_sendint(&buf, (v.boolean) ? HENTRY_ISTRUE : HENTRY_ISFALSE, 4);
+                                                       break;
+                                               case hsvNumeric:
+                                                       nbuf = DatumGetByteaP(DirectFunctionCall1(numeric_send, NumericGetDatum(v.numeric)));
+                                                       pq_sendint(&buf, ((int)VARSIZE_ANY_EXHDR(nbuf)) | HENTRY_ISNUMERIC, 4);
+                                                       pq_sendbytes(&buf, VARDATA(nbuf), (int)VARSIZE_ANY_EXHDR(nbuf));
+                                                       break;
+                                               default:
+                                                       elog(PANIC, "Wrong type: %u", v.type);
+                                       }
+                                       break;
+                               case WHS_END_ARRAY:
+                               case WHS_END_HASH:
+                                       break;
+                               default:
+                                       elog(PANIC, "Wrong flags");
+                       }
+               }
+       }
+
+       PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+}
+
+
+/*
+ * hstore_to_json_loose
+ *
+ * This is a heuristic conversion to json which treats
+ * 't' and 'f' as booleans and strings that look like numbers as numbers,
+ * as long as they don't start with a leading zero followed by another digit
+ * (think zip codes or phone numbers starting with 0).
+ */
+PG_FUNCTION_INFO_V1(hstore_to_json_loose);
+Datum          hstore_to_json_loose(PG_FUNCTION_ARGS);
+Datum
+hstore_to_json_loose(PG_FUNCTION_ARGS)
+{
+       HStore     *in = PG_GETARG_HS(0);
+       text       *out;
+
+       if (HS_ISEMPTY(in))
+       {
+               out = cstring_to_text_with_len("{}",2);
+       }
+       else
+       {
+               StringInfo      str;
+
+               str = makeStringInfo();
+               appendBinaryStringInfo(str, "    ", 4); /* VARHDRSZ */
+
+               hstoreToCString(str, VARDATA_ANY(in), VARSIZE_ANY(in),
+                                               SET_PRETTY_PRINT_VAR(JsonOutput | RootHashDecorated | LooseOutput));
+
+               out = (text*)str->data;
+
+               SET_VARSIZE(out, str->len);
+       }
+
+       PG_RETURN_TEXT_P(out);
+}
+
+PG_FUNCTION_INFO_V1(hstore_to_json);
+Datum          hstore_to_json(PG_FUNCTION_ARGS);
+Datum
+hstore_to_json(PG_FUNCTION_ARGS)
+{
+       HStore     *in = PG_GETARG_HS(0);
+       text       *out;
+
+       if (HS_ISEMPTY(in))
+       {
+               out = cstring_to_text_with_len("{}",2);
+       }
+       else
+       {
+               StringInfo      str;
+
+               str = makeStringInfo();
+               appendBinaryStringInfo(str, "    ", 4); /* VARHDRSZ */
+
+               hstoreToCString(str,
+                                               HS_ISEMPTY(in) ? NULL : VARDATA_ANY(in),
+                                               VARSIZE_ANY(in),
+                                               SET_PRETTY_PRINT_VAR(JsonOutput | RootHashDecorated));
+
+               out = (text*)str->data;
+
+               SET_VARSIZE(out, str->len);
+       }
+
+       PG_RETURN_TEXT_P(out);
+}
+
+PG_FUNCTION_INFO_V1(json_to_hstore);
+Datum          json_to_hstore(PG_FUNCTION_ARGS);
+Datum
+json_to_hstore(PG_FUNCTION_ARGS)
+{
+       text    *json = PG_GETARG_TEXT_PP(0);
+
+       PG_RETURN_POINTER(hstoreDump(parseHStore(VARDATA_ANY(json),
+                                                                                        VARSIZE_ANY_EXHDR(json), true)));
+}
+
+static Oid
+searchCast(Oid src, Oid dst, CoercionMethod *method)
+{
+       Oid                             funcOid = InvalidOid,
+                                       baseSrc;
+       HeapTuple       tuple;
+
+       if (src == dst)
+       {
+               *method = COERCION_METHOD_BINARY;
+               return InvalidOid;
+       }
+
+       tuple = SearchSysCache2(CASTSOURCETARGET,
+                                                       ObjectIdGetDatum(src),
+                                                       ObjectIdGetDatum(dst));
+
+       *method = 0;
+
+       if (HeapTupleIsValid(tuple))
+       {
+               Form_pg_cast    castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+               if (castForm->castmethod == COERCION_METHOD_FUNCTION)
+                       funcOid = castForm->castfunc;
+
+               *method = castForm->castmethod;
+
+               ReleaseSysCache(tuple);
+       }
+       else if ((baseSrc = getBaseType(src)) != src && OidIsValid(baseSrc))
+       {       
+               /* domain type */
+               funcOid = searchCast(baseSrc, dst, method);
+       }
+
+       return funcOid;
+}
+
+PG_FUNCTION_INFO_V1(array_to_hstore);
+Datum          array_to_hstore(PG_FUNCTION_ARGS);
+Datum
+array_to_hstore(PG_FUNCTION_ARGS)
+{
+       ArrayType               *array = PG_GETARG_ARRAYTYPE_P(0);
+       ArrayIterator   iterator;
+       int                             i = 0;
+       Datum                   datum;
+       bool                    isnull;
+       int                             ncounters = ARR_NDIM(array),
+                                       *counters = palloc0(sizeof(*counters) * ncounters),
+                                       *dims = ARR_DIMS(array);
+       ToHStoreState   *state = NULL;
+       HStoreValue             value, *result;
+       Oid                             castOid = InvalidOid;
+       int                             valueType = hsvString;
+       FmgrInfo                castInfo;
+       CoercionMethod  method;
+
+       if (ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)) == 0)
+               PG_RETURN_POINTER(hstoreDump(NULL));
+
+       switch(ARR_ELEMTYPE(array))
+       {
+               case BOOLOID:
+                       valueType = hsvBool;
+                       break;
+               case NUMERICOID:
+                       valueType = hsvNumeric;
+                       break;
+               case TEXTOID:
+                       valueType = hsvString;
+                       break;
+               default:
+                       if (TypeCategory(ARR_ELEMTYPE(array)) == TYPCATEGORY_NUMERIC)
+                       {
+                               castOid = searchCast(ARR_ELEMTYPE(array), NUMERICOID, &method);
+
+                               if (castOid == InvalidOid && method != COERCION_METHOD_BINARY)
+                                       elog(ERROR, "Could not cast array's element type to numeric");
+
+                               valueType = hsvNumeric;
+                               break;
+                       }
+                       else
+                       {
+                               castOid = searchCast(ARR_ELEMTYPE(array), TEXTOID, &method);
+
+                               if (castOid == InvalidOid && method != COERCION_METHOD_BINARY)
+                                       elog(ERROR, "Could not cast array's element type to text");
+
+                               valueType = hsvString;
+                               break;
+                       }
+       }
+
+       if (castOid != InvalidOid)
+               fmgr_info(castOid, &castInfo);
+
+       iterator = array_create_iterator(array, 0);
+
+       value.type = hsvArray;
+       value.array.scalar = false;
+       for(i=0; i<ncounters; i++)
+       {
+               value.array.nelems = dims[i];
+               result = pushHStoreValue(&state, WHS_BEGIN_ARRAY, &value);
+       }
+
+       while(array_iterate(iterator, &datum, &isnull))
+       {
+               i = ncounters - 1;
+
+               if (counters[i] >= dims[i])
+               {
+                       while(i>=0 && counters[i] >= dims[i])
+                       {
+                               counters[i] = 0;
+                               result = pushHStoreValue(&state, WHS_END_ARRAY, NULL);
+                               i--;
+                       }
+
+                       Assert(i>=0);
+
+                       counters[i]++;
+
+                       value.type = hsvArray;
+                       value.array.scalar = false;
+                       for(i = i + 1; i<ncounters; i++)
+                       {
+                               counters[i] = 1;
+                               value.array.nelems = dims[i];
+                               result = pushHStoreValue(&state, WHS_BEGIN_ARRAY, &value);
+                       }
+               }
+               else
+               {
+                       counters[i]++;
+               }
+
+               if (isnull)
+               {
+                       value.type = hsvNull;
+                       value.size = sizeof(HEntry);
+               }
+               else
+               {
+                       value.type = valueType;
+                       switch(valueType)
+                       {
+                               case hsvBool:
+                                       value.boolean = DatumGetBool(datum);
+                                       value.size = sizeof(HEntry);
+                                       break;
+                               case hsvString:
+                                       if (castOid != InvalidOid)
+                                               datum = FunctionCall1(&castInfo, datum);
+                                       value.string.val = VARDATA_ANY(datum);
+                                       value.string.len = VARSIZE_ANY_EXHDR(datum);
+                                       value.size = sizeof(HEntry) + value.string.len;
+                                       break;
+                               case hsvNumeric:
+                                       if (castOid != InvalidOid)
+                                               datum = FunctionCall1(&castInfo, datum);
+                                       value.numeric = DatumGetNumeric(datum);
+                                       value.size = sizeof(HEntry)*2 + VARSIZE_ANY(value.numeric);
+                                       break;
+                               default:
+                                       elog(ERROR, "Impossible state: %d", valueType);
+                       }
+               }
+
+               result = pushHStoreValue(&state, WHS_ELEM, &value);
+       }
+
+       for(i=0; i<ncounters; i++)
+               result = pushHStoreValue(&state, WHS_END_ARRAY, NULL);
+
+       PG_RETURN_POINTER(hstoreDump(result));
+}
+
+PG_FUNCTION_INFO_V1(hstore_print);
+Datum          hstore_print(PG_FUNCTION_ARGS);
+Datum
+hstore_print(PG_FUNCTION_ARGS)
+{
+       HStore          *hs = PG_GETARG_HS(0);
+       int             flags = 0;
+       text            *out;
+       StringInfo      str;
+
+       if (PG_GETARG_BOOL(1))
+               flags |= PrettyPrint;
+       if (PG_GETARG_BOOL(2))
+               flags |= ArrayCurlyBraces;
+       if (PG_GETARG_BOOL(3))
+               flags |= RootHashDecorated;
+       if (PG_GETARG_BOOL(4))
+               flags |= JsonOutput;
+       if (PG_GETARG_BOOL(5))
+               flags |= LooseOutput;
+
+       str = makeStringInfo();
+       appendBinaryStringInfo(str, "    ", 4); /* VARHDRSZ */
+
+       hstoreToCString(str, (HS_ISEMPTY(hs)) ? NULL : VARDATA(hs),
+                                       VARSIZE(hs), flags);
+
+       out = (text*)str->data;
+       SET_VARSIZE(out, str->len);
+
+       PG_RETURN_TEXT_P(out);
+}
+
+void _PG_init(void);
+void
+_PG_init(void)
+{
+       DefineCustomBoolVariable(
+               "hstore.pretty_print",
+               "Enable pretty print",
+               "Enable pretty print of hstore type",
+               &pretty_print_var,
+               pretty_print_var,
+               PGC_USERSET,
+               GUC_NOT_IN_SAMPLE,
+               NULL,
+               NULL,
+               NULL
+       );
+
+       EmitWarningsOnPlaceholders("hstore");
+}