diff options
Diffstat (limited to 'contrib/hstore/hstore_io.c')
-rw-r--r-- | contrib/hstore/hstore_io.c | 1499 |
1 files changed, 1499 insertions, 0 deletions
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c new file mode 100644 index 0000000..b3304ff --- /dev/null +++ b/contrib/hstore/hstore_io.c @@ -0,0 +1,1499 @@ +/* + * contrib/hstore/hstore_io.c + */ +#include "postgres.h" + +#include <ctype.h> + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "common/jsonapi.h" +#include "funcapi.h" +#include "hstore.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "utils/builtins.h" +#include "utils/json.h" +#include "utils/jsonb.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/typcache.h" + +PG_MODULE_MAGIC; + +/* old names for C functions */ +HSTORE_POLLUTE(hstore_from_text, tconvert); + + +typedef struct +{ + char *begin; + char *ptr; + char *cur; + char *word; + int wordlen; + + Pairs *pairs; + int pcur; + int plen; +} HSParser; + +#define RESIZEPRSBUF \ +do { \ + if ( state->cur - state->word + 1 >= state->wordlen ) \ + { \ + int32 clen = state->cur - state->word; \ + state->wordlen *= 2; \ + state->word = (char*)repalloc( (void*)state->word, state->wordlen ); \ + state->cur = state->word + clen; \ + } \ +} while (0) + + +#define GV_WAITVAL 0 +#define GV_INVAL 1 +#define GV_INESCVAL 2 +#define GV_WAITESCIN 3 +#define GV_WAITESCESCIN 4 + +static bool +get_val(HSParser *state, bool ignoreeq, bool *escaped) +{ + int st = GV_WAITVAL; + + state->wordlen = 32; + state->cur = state->word = palloc(state->wordlen); + *escaped = false; + + while (1) + { + if (st == GV_WAITVAL) + { + if (*(state->ptr) == '"') + { + *escaped = true; + st = GV_INESCVAL; + } + else if (*(state->ptr) == '\0') + { + return false; + } + else if (*(state->ptr) == '=' && !ignoreeq) + { + elog(ERROR, "Syntax error near \"%.*s\" at position %d", + pg_mblen(state->ptr), state->ptr, + (int32) (state->ptr - state->begin)); + } + else if (*(state->ptr) == '\\') + { + st = GV_WAITESCIN; + } + else if (!isspace((unsigned char) *(state->ptr))) + { + *(state->cur) = *(state->ptr); + state->cur++; + st = GV_INVAL; + } + } + else if (st == GV_INVAL) + { + if (*(state->ptr) == '\\') + { + st = GV_WAITESCIN; + } + else if (*(state->ptr) == '=' && !ignoreeq) + { + state->ptr--; + return true; + } + else if (*(state->ptr) == ',' && ignoreeq) + { + state->ptr--; + return true; + } + else if (isspace((unsigned char) *(state->ptr))) + { + return true; + } + else if (*(state->ptr) == '\0') + { + state->ptr--; + return true; + } + else + { + RESIZEPRSBUF; + *(state->cur) = *(state->ptr); + state->cur++; + } + } + else if (st == GV_INESCVAL) + { + if (*(state->ptr) == '\\') + { + st = GV_WAITESCESCIN; + } + else if (*(state->ptr) == '"') + { + return true; + } + else if (*(state->ptr) == '\0') + { + elog(ERROR, "Unexpected end of string"); + } + else + { + RESIZEPRSBUF; + *(state->cur) = *(state->ptr); + state->cur++; + } + } + else if (st == GV_WAITESCIN) + { + if (*(state->ptr) == '\0') + elog(ERROR, "Unexpected end of string"); + RESIZEPRSBUF; + *(state->cur) = *(state->ptr); + state->cur++; + st = GV_INVAL; + } + else if (st == GV_WAITESCESCIN) + { + if (*(state->ptr) == '\0') + elog(ERROR, "Unexpected end of string"); + RESIZEPRSBUF; + *(state->cur) = *(state->ptr); + state->cur++; + st = GV_INESCVAL; + } + else + elog(ERROR, "Unknown state %d at position line %d in file '%s'", st, __LINE__, __FILE__); + + state->ptr++; + } +} + +#define WKEY 0 +#define WVAL 1 +#define WEQ 2 +#define WGT 3 +#define WDEL 4 + + +static void +parse_hstore(HSParser *state) +{ + int st = WKEY; + bool escaped = false; + + state->plen = 16; + state->pairs = (Pairs *) palloc(sizeof(Pairs) * state->plen); + state->pcur = 0; + state->ptr = state->begin; + state->word = NULL; + + while (1) + { + if (st == WKEY) + { + if (!get_val(state, false, &escaped)) + return; + if (state->pcur >= state->plen) + { + state->plen *= 2; + state->pairs = (Pairs *) repalloc(state->pairs, sizeof(Pairs) * state->plen); + } + state->pairs[state->pcur].key = state->word; + state->pairs[state->pcur].keylen = hstoreCheckKeyLen(state->cur - state->word); + state->pairs[state->pcur].val = NULL; + state->word = NULL; + st = WEQ; + } + else if (st == WEQ) + { + if (*(state->ptr) == '=') + { + st = WGT; + } + else if (*(state->ptr) == '\0') + { + elog(ERROR, "Unexpected end of string"); + } + else if (!isspace((unsigned char) *(state->ptr))) + { + elog(ERROR, "Syntax error near \"%.*s\" at position %d", + pg_mblen(state->ptr), state->ptr, + (int32) (state->ptr - state->begin)); + } + } + else if (st == WGT) + { + if (*(state->ptr) == '>') + { + st = WVAL; + } + else if (*(state->ptr) == '\0') + { + elog(ERROR, "Unexpected end of string"); + } + else + { + elog(ERROR, "Syntax error near \"%.*s\" at position %d", + pg_mblen(state->ptr), state->ptr, + (int32) (state->ptr - state->begin)); + } + } + else if (st == WVAL) + { + if (!get_val(state, true, &escaped)) + elog(ERROR, "Unexpected end of string"); + state->pairs[state->pcur].val = state->word; + state->pairs[state->pcur].vallen = hstoreCheckValLen(state->cur - state->word); + state->pairs[state->pcur].isnull = false; + state->pairs[state->pcur].needfree = true; + if (state->cur - state->word == 4 && !escaped) + { + state->word[4] = '\0'; + if (0 == pg_strcasecmp(state->word, "null")) + state->pairs[state->pcur].isnull = true; + } + state->word = NULL; + state->pcur++; + st = WDEL; + } + else if (st == WDEL) + { + if (*(state->ptr) == ',') + { + st = WKEY; + } + else if (*(state->ptr) == '\0') + { + return; + } + else if (!isspace((unsigned char) *(state->ptr))) + { + elog(ERROR, "Syntax error near \"%.*s\" at position %d", + pg_mblen(state->ptr), state->ptr, + (int32) (state->ptr - state->begin)); + } + } + else + elog(ERROR, "Unknown state %d at line %d in file '%s'", st, __LINE__, __FILE__); + + state->ptr++; + } +} + +static int +comparePairs(const void *a, const void *b) +{ + const Pairs *pa = a; + const Pairs *pb = b; + + if (pa->keylen == pb->keylen) + { + int res = memcmp(pa->key, pb->key, pa->keylen); + + if (res) + return res; + + /* guarantee that needfree will be later */ + if (pb->needfree == pa->needfree) + return 0; + else if (pa->needfree) + return 1; + else + return -1; + } + return (pa->keylen > pb->keylen) ? 1 : -1; +} + +/* + * this code still respects pairs.needfree, even though in general + * it should never be called in a context where anything needs freeing. + * we keep it because (a) those calls are in a rare code path anyway, + * and (b) who knows whether they might be needed by some caller. + */ +int +hstoreUniquePairs(Pairs *a, int32 l, int32 *buflen) +{ + Pairs *ptr, + *res; + + *buflen = 0; + if (l < 2) + { + if (l == 1) + *buflen = a->keylen + ((a->isnull) ? 0 : a->vallen); + return l; + } + + qsort((void *) a, l, sizeof(Pairs), comparePairs); + + /* + * We can't use qunique here because we have some clean-up code to run on + * removed elements. + */ + ptr = a + 1; + res = a; + while (ptr - a < l) + { + if (ptr->keylen == res->keylen && + memcmp(ptr->key, res->key, res->keylen) == 0) + { + if (ptr->needfree) + { + pfree(ptr->key); + pfree(ptr->val); + } + } + else + { + *buflen += res->keylen + ((res->isnull) ? 0 : res->vallen); + res++; + if (res != ptr) + memcpy(res, ptr, sizeof(Pairs)); + } + + ptr++; + } + + *buflen += res->keylen + ((res->isnull) ? 0 : res->vallen); + return res + 1 - a; +} + +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; +} + +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; +} + + +HStore * +hstorePairs(Pairs *pairs, int32 pcount, int32 buflen) +{ + HStore *out; + HEntry *entry; + char *ptr; + char *buf; + int32 len; + int32 i; + + len = CALCDATASIZE(pcount, buflen); + out = palloc(len); + SET_VARSIZE(out, len); + HS_SETCOUNT(out, pcount); + + if (pcount == 0) + return out; + + entry = ARRPTR(out); + buf = ptr = STRPTR(out); + + for (i = 0; i < pcount; i++) + HS_ADDITEM(entry, buf, ptr, pairs[i]); + + HS_FINALIZE(out, pcount, buf, ptr); + + return out; +} + + +PG_FUNCTION_INFO_V1(hstore_in); +Datum +hstore_in(PG_FUNCTION_ARGS) +{ + HSParser state; + int32 buflen; + HStore *out; + + state.begin = PG_GETARG_CSTRING(0); + + parse_hstore(&state); + + state.pcur = hstoreUniquePairs(state.pairs, state.pcur, &buflen); + + out = hstorePairs(state.pairs, state.pcur, buflen); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_recv); +Datum +hstore_recv(PG_FUNCTION_ARGS) +{ + int32 buflen; + HStore *out; + Pairs *pairs; + int32 i; + int32 pcount; + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + pcount = pq_getmsgint(buf, 4); + + if (pcount == 0) + { + out = hstorePairs(NULL, 0, 0); + PG_RETURN_POINTER(out); + } + + if (pcount < 0 || pcount > MaxAllocSize / sizeof(Pairs)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of pairs (%d) exceeds the maximum allowed (%d)", + pcount, (int) (MaxAllocSize / sizeof(Pairs))))); + pairs = palloc(pcount * sizeof(Pairs)); + + for (i = 0; i < pcount; ++i) + { + int rawlen = pq_getmsgint(buf, 4); + int len; + + if (rawlen < 0) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for hstore key"))); + + pairs[i].key = pq_getmsgtext(buf, rawlen, &len); + pairs[i].keylen = hstoreCheckKeyLen(len); + pairs[i].needfree = true; + + rawlen = pq_getmsgint(buf, 4); + if (rawlen < 0) + { + pairs[i].val = NULL; + pairs[i].vallen = 0; + pairs[i].isnull = true; + } + else + { + pairs[i].val = pq_getmsgtext(buf, rawlen, &len); + pairs[i].vallen = hstoreCheckValLen(len); + pairs[i].isnull = false; + } + } + + pcount = hstoreUniquePairs(pairs, pcount, &buflen); + + out = hstorePairs(pairs, pcount, buflen); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_from_text); +Datum +hstore_from_text(PG_FUNCTION_ARGS) +{ + text *key; + text *val = NULL; + Pairs p; + HStore *out; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + p.needfree = false; + key = PG_GETARG_TEXT_PP(0); + p.key = VARDATA_ANY(key); + p.keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key)); + + if (PG_ARGISNULL(1)) + { + p.vallen = 0; + p.isnull = true; + } + else + { + val = PG_GETARG_TEXT_PP(1); + p.val = VARDATA_ANY(val); + p.vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(val)); + p.isnull = false; + } + + out = hstorePairs(&p, 1, p.keylen + p.vallen); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_from_arrays); +Datum +hstore_from_arrays(PG_FUNCTION_ARGS) +{ + int32 buflen; + HStore *out; + Pairs *pairs; + 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, TYPALIGN_INT, + &key_datums, &key_nulls, &key_count); + + /* see discussion in hstoreArrayToPairs() */ + if (key_count > MaxAllocSize / sizeof(Pairs)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of pairs (%d) exceeds the maximum allowed (%d)", + key_count, (int) (MaxAllocSize / sizeof(Pairs))))); + + /* 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, TYPALIGN_INT, + &value_datums, &value_nulls, &value_count); + + Assert(key_count == value_count); + } + + pairs = palloc(key_count * sizeof(Pairs)); + + 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"))); + + if (!value_nulls || value_nulls[i]) + { + pairs[i].key = VARDATA(key_datums[i]); + pairs[i].val = NULL; + pairs[i].keylen = + hstoreCheckKeyLen(VARSIZE(key_datums[i]) - VARHDRSZ); + pairs[i].vallen = 4; + pairs[i].isnull = true; + pairs[i].needfree = false; + } + else + { + pairs[i].key = VARDATA(key_datums[i]); + pairs[i].val = VARDATA(value_datums[i]); + pairs[i].keylen = + hstoreCheckKeyLen(VARSIZE(key_datums[i]) - VARHDRSZ); + pairs[i].vallen = + hstoreCheckValLen(VARSIZE(value_datums[i]) - VARHDRSZ); + pairs[i].isnull = false; + pairs[i].needfree = false; + } + } + + key_count = hstoreUniquePairs(pairs, key_count, &buflen); + + out = hstorePairs(pairs, key_count, buflen); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_from_array); +Datum +hstore_from_array(PG_FUNCTION_ARGS) +{ + ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); + int ndims = ARR_NDIM(in_array); + int count; + int32 buflen; + HStore *out; + Pairs *pairs; + Datum *in_datums; + bool *in_nulls; + int in_count; + int i; + + Assert(ARR_ELEMTYPE(in_array) == TEXTOID); + + switch (ndims) + { + case 0: + out = hstorePairs(NULL, 0, 0); + PG_RETURN_POINTER(out); + + 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, TYPALIGN_INT, + &in_datums, &in_nulls, &in_count); + + count = in_count / 2; + + /* see discussion in hstoreArrayToPairs() */ + if (count > MaxAllocSize / sizeof(Pairs)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of pairs (%d) exceeds the maximum allowed (%d)", + count, (int) (MaxAllocSize / sizeof(Pairs))))); + + pairs = palloc(count * sizeof(Pairs)); + + 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"))); + + if (in_nulls[i * 2 + 1]) + { + pairs[i].key = VARDATA(in_datums[i * 2]); + pairs[i].val = NULL; + pairs[i].keylen = + hstoreCheckKeyLen(VARSIZE(in_datums[i * 2]) - VARHDRSZ); + pairs[i].vallen = 4; + pairs[i].isnull = true; + pairs[i].needfree = false; + } + else + { + pairs[i].key = VARDATA(in_datums[i * 2]); + pairs[i].val = VARDATA(in_datums[i * 2 + 1]); + pairs[i].keylen = + hstoreCheckKeyLen(VARSIZE(in_datums[i * 2]) - VARHDRSZ); + pairs[i].vallen = + hstoreCheckValLen(VARSIZE(in_datums[i * 2 + 1]) - VARHDRSZ); + pairs[i].isnull = false; + pairs[i].needfree = false; + } + } + + count = hstoreUniquePairs(pairs, count, &buflen); + + out = hstorePairs(pairs, count, buflen); + + PG_RETURN_POINTER(out); +} + +/* 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; + /* this field is used only if target type is domain over composite: */ + void *domain_info; /* opaque cache for domain checks */ + int ncolumns; + ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER]; +} RecordIOData; + +PG_FUNCTION_INFO_V1(hstore_from_record); +Datum +hstore_from_record(PG_FUNCTION_ARGS) +{ + HeapTupleHeader rec; + int32 buflen; + HStore *out; + Pairs *pairs; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + RecordIOData *my_extra; + int ncolumns; + int i, + j; + Datum *values; + bool *nulls; + + if (PG_ARGISNULL(0)) + { + Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + + /* + * We have no tuple to look at, so the only source of type info is the + * argtype --- which might be domain over composite, but we don't care + * here, since we have no need to be concerned about domain + * constraints. The lookup_rowtype_tupdesc_domain 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 -- this will work even for + * anonymous record types. + */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); + } + + tupdesc = lookup_rowtype_tupdesc_domain(tupType, tupTypmod, false); + 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, + offsetof(RecordIOData, columns) + + 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, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra->record_type = tupType; + my_extra->record_typmod = tupTypmod; + my_extra->ncolumns = ncolumns; + } + + Assert(ncolumns <= MaxTupleAttributeNumber); /* thus, no overflow */ + pairs = palloc(ncolumns * sizeof(Pairs)); + + 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, j = 0; i < ncolumns; ++i) + { + ColumnIOData *column_info = &my_extra->columns[i]; + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + Oid column_type = att->atttypid; + char *value; + + /* Ignore dropped columns in datatype */ + if (att->attisdropped) + continue; + + pairs[j].key = NameStr(att->attname); + pairs[j].keylen = hstoreCheckKeyLen(strlen(NameStr(att->attname))); + + if (!nulls || nulls[i]) + { + pairs[j].val = NULL; + pairs[j].vallen = 4; + pairs[j].isnull = true; + pairs[j].needfree = false; + ++j; + continue; + } + + /* + * Convert the column value to text + */ + 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]); + + pairs[j].val = value; + pairs[j].vallen = hstoreCheckValLen(strlen(value)); + pairs[j].isnull = false; + pairs[j].needfree = false; + ++j; + } + + ncolumns = hstoreUniquePairs(pairs, j, &buflen); + + out = hstorePairs(pairs, ncolumns, buflen); + + ReleaseTupleDesc(tupdesc); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_populate_record); +Datum +hstore_populate_record(PG_FUNCTION_ARGS) +{ + Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + HStore *hs; + HEntry *entries; + char *ptr; + 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; + + /* + * We have no tuple to look at, so the only source of type info is the + * argtype. The lookup_rowtype_tupdesc_domain 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 -- this will work even for + * anonymous record types. + */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); + } + + hs = PG_GETARG_HSTORE_P(1); + entries = ARRPTR(hs); + ptr = STRPTR(hs); + + /* + * 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_COUNT(hs) == 0 && rec) + PG_RETURN_POINTER(rec); + + /* + * Lookup the input record's tupdesc. For the moment, we don't worry + * about whether it is a domain over composite. + */ + tupdesc = lookup_rowtype_tupdesc_domain(tupType, tupTypmod, false); + 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, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + my_extra->record_type = InvalidOid; + my_extra->record_typmod = 0; + my_extra->domain_info = NULL; + } + + if (my_extra->record_type != tupType || + my_extra->record_typmod != tupTypmod) + { + MemSet(my_extra, 0, + offsetof(RecordIOData, columns) + + 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]; + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + Oid column_type = att->atttypid; + char *value; + int idx; + int vallen; + + /* Ignore dropped columns in datatype */ + if (att->attisdropped) + { + nulls[i] = true; + continue; + } + + idx = hstoreFindKey(hs, 0, + NameStr(att->attname), + strlen(NameStr(att->attname))); + + /* + * 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 (idx < 0 && 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 (idx < 0 || HSTORE_VALISNULL(entries, idx)) + { + /* + * need InputFunctionCall to happen even for nulls, so that domain + * checks are done + */ + values[i] = InputFunctionCall(&column_info->proc, NULL, + column_info->typioparam, + att->atttypmod); + nulls[i] = true; + } + else + { + vallen = HSTORE_VALLEN(entries, idx); + value = palloc(1 + vallen); + memcpy(value, HSTORE_VAL(entries, ptr, idx), vallen); + value[vallen] = 0; + + values[i] = InputFunctionCall(&column_info->proc, value, + column_info->typioparam, + att->atttypmod); + nulls[i] = false; + } + } + + rettuple = heap_form_tuple(tupdesc, values, nulls); + + /* + * If the target type is domain over composite, all we know at this point + * is that we've made a valid value of the base composite type. Must + * check domain constraints before deciding we're done. + */ + if (argtype != tupdesc->tdtypeid) + domain_check(HeapTupleGetDatum(rettuple), false, + argtype, + &my_extra->domain_info, + fcinfo->flinfo->fn_mcxt); + + ReleaseTupleDesc(tupdesc); + + PG_RETURN_DATUM(HeapTupleGetDatum(rettuple)); +} + + +static char * +cpw(char *dst, char *src, int len) +{ + char *ptr = src; + + while (ptr - src < len) + { + if (*ptr == '"' || *ptr == '\\') + *dst++ = '\\'; + *dst++ = *ptr++; + } + return dst; +} + +PG_FUNCTION_INFO_V1(hstore_out); +Datum +hstore_out(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int buflen, + i; + int count = HS_COUNT(in); + char *out, + *ptr; + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + + if (count == 0) + PG_RETURN_CSTRING(pstrdup("")); + + buflen = 0; + + /* + * this loop overestimates due to pessimistic assumptions about escaping, + * so very large hstore values can't be output. this could be fixed, but + * many other data types probably have the same issue. This replaced code + * that used the original varlena size for calculations, which was wrong + * in some subtle ways. + */ + + for (i = 0; i < count; i++) + { + /* include "" and => and comma-space */ + buflen += 6 + 2 * HSTORE_KEYLEN(entries, i); + /* include "" only if nonnull */ + buflen += 2 + (HSTORE_VALISNULL(entries, i) + ? 2 + : 2 * HSTORE_VALLEN(entries, i)); + } + + out = ptr = palloc(buflen); + + for (i = 0; i < count; i++) + { + *ptr++ = '"'; + ptr = cpw(ptr, HSTORE_KEY(entries, base, i), HSTORE_KEYLEN(entries, i)); + *ptr++ = '"'; + *ptr++ = '='; + *ptr++ = '>'; + if (HSTORE_VALISNULL(entries, i)) + { + *ptr++ = 'N'; + *ptr++ = 'U'; + *ptr++ = 'L'; + *ptr++ = 'L'; + } + else + { + *ptr++ = '"'; + ptr = cpw(ptr, HSTORE_VAL(entries, base, i), HSTORE_VALLEN(entries, i)); + *ptr++ = '"'; + } + + if (i + 1 != count) + { + *ptr++ = ','; + *ptr++ = ' '; + } + } + *ptr = '\0'; + + PG_RETURN_CSTRING(out); +} + + +PG_FUNCTION_INFO_V1(hstore_send); +Datum +hstore_send(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + StringInfoData buf; + + pq_begintypsend(&buf); + + pq_sendint32(&buf, count); + + for (i = 0; i < count; i++) + { + int32 keylen = HSTORE_KEYLEN(entries, i); + + pq_sendint32(&buf, keylen); + pq_sendtext(&buf, HSTORE_KEY(entries, base, i), keylen); + if (HSTORE_VALISNULL(entries, i)) + { + pq_sendint32(&buf, -1); + } + else + { + int32 vallen = HSTORE_VALLEN(entries, i); + + pq_sendint32(&buf, vallen); + pq_sendtext(&buf, HSTORE_VAL(entries, base, i), vallen); + } + } + + 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) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + StringInfoData tmp, + dst; + + if (count == 0) + PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); + + initStringInfo(&tmp); + initStringInfo(&dst); + + appendStringInfoChar(&dst, '{'); + + for (i = 0; i < count; i++) + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + escape_json(&dst, tmp.data); + appendStringInfoString(&dst, ": "); + if (HSTORE_VALISNULL(entries, i)) + appendStringInfoString(&dst, "null"); + /* guess that values of 't' or 'f' are booleans */ + else if (HSTORE_VALLEN(entries, i) == 1 && + *(HSTORE_VAL(entries, base, i)) == 't') + appendStringInfoString(&dst, "true"); + else if (HSTORE_VALLEN(entries, i) == 1 && + *(HSTORE_VAL(entries, base, i)) == 'f') + appendStringInfoString(&dst, "false"); + else + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + if (IsValidJsonNumber(tmp.data, tmp.len)) + appendBinaryStringInfo(&dst, tmp.data, tmp.len); + else + escape_json(&dst, tmp.data); + } + + if (i + 1 != count) + appendStringInfoString(&dst, ", "); + } + appendStringInfoChar(&dst, '}'); + + PG_RETURN_TEXT_P(cstring_to_text(dst.data)); +} + +PG_FUNCTION_INFO_V1(hstore_to_json); +Datum +hstore_to_json(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + StringInfoData tmp, + dst; + + if (count == 0) + PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); + + initStringInfo(&tmp); + initStringInfo(&dst); + + appendStringInfoChar(&dst, '{'); + + for (i = 0; i < count; i++) + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + escape_json(&dst, tmp.data); + appendStringInfoString(&dst, ": "); + if (HSTORE_VALISNULL(entries, i)) + appendStringInfoString(&dst, "null"); + else + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + escape_json(&dst, tmp.data); + } + + if (i + 1 != count) + appendStringInfoString(&dst, ", "); + } + appendStringInfoChar(&dst, '}'); + + PG_RETURN_TEXT_P(cstring_to_text(dst.data)); +} + +PG_FUNCTION_INFO_V1(hstore_to_jsonb); +Datum +hstore_to_jsonb(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + JsonbParseState *state = NULL; + JsonbValue *res; + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < count; i++) + { + JsonbValue key, + val; + + key.type = jbvString; + key.val.string.len = HSTORE_KEYLEN(entries, i); + key.val.string.val = HSTORE_KEY(entries, base, i); + + (void) pushJsonbValue(&state, WJB_KEY, &key); + + if (HSTORE_VALISNULL(entries, i)) + { + val.type = jbvNull; + } + else + { + val.type = jbvString; + val.val.string.len = HSTORE_VALLEN(entries, i); + val.val.string.val = HSTORE_VAL(entries, base, i); + } + (void) pushJsonbValue(&state, WJB_VALUE, &val); + } + + res = pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(res)); +} + +PG_FUNCTION_INFO_V1(hstore_to_jsonb_loose); +Datum +hstore_to_jsonb_loose(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + JsonbParseState *state = NULL; + JsonbValue *res; + StringInfoData tmp; + + initStringInfo(&tmp); + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < count; i++) + { + JsonbValue key, + val; + + key.type = jbvString; + key.val.string.len = HSTORE_KEYLEN(entries, i); + key.val.string.val = HSTORE_KEY(entries, base, i); + + (void) pushJsonbValue(&state, WJB_KEY, &key); + + if (HSTORE_VALISNULL(entries, i)) + { + val.type = jbvNull; + } + /* guess that values of 't' or 'f' are booleans */ + else if (HSTORE_VALLEN(entries, i) == 1 && + *(HSTORE_VAL(entries, base, i)) == 't') + { + val.type = jbvBool; + val.val.boolean = true; + } + else if (HSTORE_VALLEN(entries, i) == 1 && + *(HSTORE_VAL(entries, base, i)) == 'f') + { + val.type = jbvBool; + val.val.boolean = false; + } + else + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + if (IsValidJsonNumber(tmp.data, tmp.len)) + { + Datum numd; + + val.type = jbvNumeric; + numd = DirectFunctionCall3(numeric_in, + CStringGetDatum(tmp.data), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + val.val.numeric = DatumGetNumeric(numd); + } + else + { + val.type = jbvString; + val.val.string.len = HSTORE_VALLEN(entries, i); + val.val.string.val = HSTORE_VAL(entries, base, i); + } + } + (void) pushJsonbValue(&state, WJB_VALUE, &val); + } + + res = pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(res)); +} |