postgresql/contrib/hstore/hstore_io.c

1500 lines
33 KiB
C

/*
* 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));
}