X-Git-Url: http://www.sigaev.ru/git/gitweb.cgi?p=hstore.git;a=blobdiff_plain;f=hstore_io.c;fp=hstore_io.c;h=63ffcda167211259d9ff3cc8d21675d9294bbfcd;hp=0000000000000000000000000000000000000000;hb=2d3cb5062568eab105ed554350ac99bae76ee0ec;hpb=77af220c462dd61507d6cca9b9f54ad3e102e1b6 diff --git a/hstore_io.c b/hstore_io.c new file mode 100644 index 0000000..63ffcda --- /dev/null +++ b/hstore_io.c @@ -0,0 +1,1906 @@ +/* + * contrib/hstore/hstore_io.c + */ +#include "postgres.h" + +#include + +#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; ihash.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; iarray.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= 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; idata; + 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"); +}