forked from huawei/openGauss-server
1035 lines
32 KiB
C++
1035 lines
32 KiB
C++
/*
|
|
* contrib/hstore/hstore_io.c
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "catalog/pg_type.h"
|
|
#include "funcapi.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/typcache.h"
|
|
|
|
#include "hstore.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) { \
|
|
int4 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 = (char*)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 '%c' at position %d", *(state->ptr), (int4)(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++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#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 '%c' at position %d", *(state->ptr), (int4)(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 '%c' at position %d", *(state->ptr), (int4)(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 '%c' at position %d", *(state->ptr), (int4)(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 = (const Pairs*)a;
|
|
const Pairs* pb = (const Pairs*)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, int4 l, int4* buflen)
|
|
{
|
|
Pairs *ptr = NULL, *res = NULL;
|
|
|
|
*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);
|
|
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_ext(ptr->key);
|
|
pfree_ext(ptr->val);
|
|
}
|
|
} else {
|
|
*buflen += res->keylen + ((res->isnull) ? 0 : res->vallen);
|
|
res++;
|
|
errno_t rc = memcpy_s(res, sizeof(Pairs), ptr, sizeof(Pairs));
|
|
securec_check(rc, "", "");
|
|
}
|
|
|
|
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, int4 pcount, int4 buflen)
|
|
{
|
|
HStore* out = NULL;
|
|
HEntry* entry = NULL;
|
|
char* ptr = NULL;
|
|
char* buf = NULL;
|
|
int4 len;
|
|
int4 i;
|
|
|
|
len = CALCDATASIZE(pcount, buflen);
|
|
out = (HStore*)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], len);
|
|
|
|
HS_FINALIZE(out, len, (uint32)pcount, buf, ptr);
|
|
|
|
return out;
|
|
}
|
|
|
|
PG_FUNCTION_INFO_V1(hstore_in);
|
|
extern "C" Datum hstore_in(PG_FUNCTION_ARGS);
|
|
Datum hstore_in(PG_FUNCTION_ARGS)
|
|
{
|
|
HSParser state;
|
|
int4 buflen;
|
|
HStore* out = NULL;
|
|
|
|
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);
|
|
extern "C" Datum hstore_recv(PG_FUNCTION_ARGS);
|
|
Datum hstore_recv(PG_FUNCTION_ARGS)
|
|
{
|
|
int4 buflen;
|
|
HStore* out = NULL;
|
|
Pairs* pairs = NULL;
|
|
int4 i;
|
|
int4 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 > (int)(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 = (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);
|
|
extern "C" Datum hstore_from_text(PG_FUNCTION_ARGS);
|
|
Datum hstore_from_text(PG_FUNCTION_ARGS)
|
|
{
|
|
text* key = NULL;
|
|
text* val = NULL;
|
|
Pairs p;
|
|
HStore* out = NULL;
|
|
|
|
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);
|
|
extern "C" Datum hstore_from_arrays(PG_FUNCTION_ARGS);
|
|
Datum hstore_from_arrays(PG_FUNCTION_ARGS)
|
|
{
|
|
int4 buflen;
|
|
HStore* out = NULL;
|
|
Pairs* pairs = NULL;
|
|
Datum* key_datums = NULL;
|
|
bool* key_nulls = NULL;
|
|
int key_count;
|
|
Datum* value_datums = NULL;
|
|
bool* value_nulls = NULL;
|
|
int value_count;
|
|
ArrayType* key_array = NULL;
|
|
ArrayType* value_array = NULL;
|
|
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 hstoreArrayToPairs() */
|
|
if (key_count > (int)(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, 'i', &value_datums, &value_nulls, &value_count);
|
|
|
|
Assert(key_count == value_count);
|
|
}
|
|
|
|
pairs = (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 == NULL || value_nulls[i]) {
|
|
pairs[i].key = VARDATA_ANY(key_datums[i]);
|
|
pairs[i].val = NULL;
|
|
pairs[i].keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key_datums[i]));
|
|
pairs[i].vallen = 4;
|
|
pairs[i].isnull = true;
|
|
pairs[i].needfree = false;
|
|
} else {
|
|
pairs[i].key = VARDATA_ANY(key_datums[i]);
|
|
pairs[i].val = VARDATA_ANY(value_datums[i]);
|
|
pairs[i].keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key_datums[i]));
|
|
pairs[i].vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(value_datums[i]));
|
|
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);
|
|
extern "C" 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;
|
|
int4 buflen;
|
|
HStore* out = NULL;
|
|
Pairs* pairs = NULL;
|
|
Datum* in_datums = NULL;
|
|
bool* in_nulls = NULL;
|
|
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, 'i', &in_datums, &in_nulls, &in_count);
|
|
|
|
count = in_count / 2;
|
|
|
|
/* see discussion in hstoreArrayToPairs() */
|
|
if (count > (int)(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 = (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_ANY(in_datums[i * 2]);
|
|
pairs[i].val = NULL;
|
|
pairs[i].keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(in_datums[i * 2]));
|
|
pairs[i].vallen = 4;
|
|
pairs[i].isnull = true;
|
|
pairs[i].needfree = false;
|
|
} else {
|
|
pairs[i].key = VARDATA_ANY(in_datums[i * 2]);
|
|
pairs[i].val = VARDATA_ANY(in_datums[i * 2 + 1]);
|
|
pairs[i].keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(in_datums[i * 2]));
|
|
pairs[i].vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(in_datums[i * 2 + 1]));
|
|
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;
|
|
int ncolumns;
|
|
ColumnIOData columns[1]; /* VARIABLE LENGTH ARRAY */
|
|
} RecordIOData;
|
|
|
|
PG_FUNCTION_INFO_V1(hstore_from_record);
|
|
extern "C" Datum hstore_from_record(PG_FUNCTION_ARGS);
|
|
Datum hstore_from_record(PG_FUNCTION_ARGS)
|
|
{
|
|
HeapTupleHeader rec;
|
|
int4 buflen;
|
|
HStore* out = NULL;
|
|
Pairs* pairs = NULL;
|
|
Oid tupType;
|
|
int32 tupTypmod;
|
|
TupleDesc tupdesc;
|
|
HeapTupleData tuple;
|
|
RecordIOData* my_extra = NULL;
|
|
int ncolumns;
|
|
int i, j;
|
|
Datum* values = NULL;
|
|
bool* nulls = NULL;
|
|
|
|
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) {
|
|
int rc = memset_s(my_extra, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData),
|
|
0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData));
|
|
securec_check(rc, "", "");
|
|
my_extra->record_type = tupType;
|
|
my_extra->record_typmod = tupTypmod;
|
|
my_extra->ncolumns = ncolumns;
|
|
}
|
|
|
|
Assert(ncolumns <= MaxTupleAttributeNumber); /* thus, no overflow */
|
|
pairs = (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_bucketId = InvalidBktId;
|
|
HeapTupleSetZeroBase(&tuple);
|
|
#ifdef PGXC
|
|
tuple.t_xc_node_id = 0;
|
|
#endif
|
|
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];
|
|
Oid column_type = tupdesc->attrs[i]->atttypid;
|
|
char* value = NULL;
|
|
|
|
/* Ignore dropped columns in datatype */
|
|
if (tupdesc->attrs[i]->attisdropped)
|
|
continue;
|
|
|
|
pairs[j].key = NameStr(tupdesc->attrs[i]->attname);
|
|
pairs[j].keylen = hstoreCheckKeyLen(strlen(NameStr(tupdesc->attrs[i]->attname)));
|
|
|
|
if (nulls == NULL || 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 = false;
|
|
|
|
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);
|
|
extern "C" 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 = NULL;
|
|
HEntry* entries = NULL;
|
|
char* ptr = NULL;
|
|
HeapTupleHeader rec;
|
|
Oid tupType;
|
|
int32 tupTypmod;
|
|
TupleDesc tupdesc;
|
|
HeapTupleData tuple;
|
|
HeapTuple rettuple;
|
|
RecordIOData* my_extra = NULL;
|
|
int ncolumns;
|
|
int i;
|
|
Datum* values = NULL;
|
|
bool* nulls = NULL;
|
|
|
|
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);
|
|
NOT_NULL_HS(hs);
|
|
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);
|
|
|
|
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_bucketId = InvalidBktId;
|
|
HeapTupleSetZeroBase(&tuple);
|
|
#ifdef PGXC
|
|
tuple.t_xc_node_id = 0;
|
|
#endif
|
|
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) {
|
|
int rc = memset_s(my_extra, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData),
|
|
0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData));
|
|
securec_check(rc, "", "");
|
|
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;
|
|
char* value = NULL;
|
|
int idx;
|
|
int vallen;
|
|
|
|
/* Ignore dropped columns in datatype */
|
|
if (tupdesc->attrs[i]->attisdropped) {
|
|
nulls[i] = true;
|
|
continue;
|
|
}
|
|
|
|
idx = hstoreFindKey(hs, 0, NameStr(tupdesc->attrs[i]->attname), strlen(NameStr(tupdesc->attrs[i]->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 || HS_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, tupdesc->attrs[i]->atttypmod);
|
|
nulls[i] = true;
|
|
} else {
|
|
vallen = HS_VALLEN(entries, idx);
|
|
value = (char*)palloc(1 + vallen);
|
|
errno_t rc = memcpy_s(value, 1 + vallen, HS_VAL(entries, ptr, idx), vallen);
|
|
securec_check(rc, "", "");
|
|
value[vallen] = 0;
|
|
|
|
values[i] =
|
|
InputFunctionCall(&column_info->proc, value, column_info->typioparam, tupdesc->attrs[i]->atttypmod);
|
|
nulls[i] = false;
|
|
}
|
|
}
|
|
|
|
rettuple = heap_form_tuple(tupdesc, values, nulls);
|
|
|
|
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);
|
|
extern "C" Datum hstore_out(PG_FUNCTION_ARGS);
|
|
Datum hstore_out(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore* in = PG_GETARG_HS(0);
|
|
NOT_NULL_HS(in);
|
|
int buflen, i;
|
|
int count = HS_COUNT(in);
|
|
char *out = NULL, *ptr = NULL;
|
|
char* base = STRPTR(in);
|
|
HEntry* entries = ARRPTR(in);
|
|
|
|
if (count == 0) {
|
|
out = (char*)palloc(1);
|
|
*out = '\0';
|
|
PG_RETURN_CSTRING(out);
|
|
}
|
|
|
|
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 * HS_KEYLEN(entries, i);
|
|
/* include "" only if nonnull */
|
|
buflen += 2 + (HS_VALISNULL(entries, i) ? 2 : (2 * HS_VALLEN(entries, i)));
|
|
}
|
|
|
|
out = ptr = (char*)palloc(buflen);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
*ptr++ = '"';
|
|
ptr = cpw(ptr, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
|
|
*ptr++ = '"';
|
|
*ptr++ = '=';
|
|
*ptr++ = '>';
|
|
if (HS_VALISNULL(entries, i)) {
|
|
*ptr++ = 'N';
|
|
*ptr++ = 'U';
|
|
*ptr++ = 'L';
|
|
*ptr++ = 'L';
|
|
} else {
|
|
*ptr++ = '"';
|
|
ptr = cpw(ptr, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
|
|
*ptr++ = '"';
|
|
}
|
|
|
|
if (i + 1 != count) {
|
|
*ptr++ = ',';
|
|
*ptr++ = ' ';
|
|
}
|
|
}
|
|
*ptr = '\0';
|
|
|
|
PG_RETURN_CSTRING(out);
|
|
}
|
|
|
|
PG_FUNCTION_INFO_V1(hstore_send);
|
|
extern "C" Datum hstore_send(PG_FUNCTION_ARGS);
|
|
Datum hstore_send(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore* in = PG_GETARG_HS(0);
|
|
NOT_NULL_HS(in);
|
|
int i;
|
|
int count = HS_COUNT(in);
|
|
char* base = STRPTR(in);
|
|
HEntry* entries = ARRPTR(in);
|
|
StringInfoData buf;
|
|
|
|
pq_begintypsend(&buf);
|
|
|
|
pq_sendint(&buf, count, 4);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
int32 keylen = HS_KEYLEN(entries, i);
|
|
|
|
pq_sendint(&buf, keylen, 4);
|
|
pq_sendtext(&buf, HS_KEY(entries, base, i), keylen);
|
|
if (HS_VALISNULL(entries, i)) {
|
|
pq_sendint(&buf, -1, 4);
|
|
} else {
|
|
int32 vallen = HS_VALLEN(entries, i);
|
|
|
|
pq_sendint(&buf, vallen, 4);
|
|
pq_sendtext(&buf, HS_VAL(entries, base, i), vallen);
|
|
}
|
|
}
|
|
|
|
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
|
}
|