diff options
Diffstat (limited to 'contrib/hstore/hstore_op.c')
-rw-r--r-- | contrib/hstore/hstore_op.c | 1278 |
1 files changed, 1278 insertions, 0 deletions
diff --git a/contrib/hstore/hstore_op.c b/contrib/hstore/hstore_op.c new file mode 100644 index 0000000..dd79d01 --- /dev/null +++ b/contrib/hstore/hstore_op.c @@ -0,0 +1,1278 @@ +/* + * contrib/hstore/hstore_op.c + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "funcapi.h" +#include "hstore.h" +#include "utils/builtins.h" +#include "utils/memutils.h" + +/* old names for C functions */ +HSTORE_POLLUTE(hstore_fetchval, fetchval); +HSTORE_POLLUTE(hstore_exists, exists); +HSTORE_POLLUTE(hstore_defined, defined); +HSTORE_POLLUTE(hstore_delete, delete); +HSTORE_POLLUTE(hstore_concat, hs_concat); +HSTORE_POLLUTE(hstore_contains, hs_contains); +HSTORE_POLLUTE(hstore_contained, hs_contained); +HSTORE_POLLUTE(hstore_akeys, akeys); +HSTORE_POLLUTE(hstore_avals, avals); +HSTORE_POLLUTE(hstore_skeys, skeys); +HSTORE_POLLUTE(hstore_svals, svals); +HSTORE_POLLUTE(hstore_each, each); + + +/* + * We're often finding a sequence of keys in ascending order. The + * "lowbound" parameter is used to cache lower bounds of searches + * between calls, based on this assumption. Pass NULL for it for + * one-off or unordered searches. + */ +int +hstoreFindKey(HStore *hs, int *lowbound, char *key, int keylen) +{ + HEntry *entries = ARRPTR(hs); + int stopLow = lowbound ? *lowbound : 0; + int stopHigh = HS_COUNT(hs); + int stopMiddle; + char *base = STRPTR(hs); + + while (stopLow < stopHigh) + { + int difference; + + stopMiddle = stopLow + (stopHigh - stopLow) / 2; + + if (HSTORE_KEYLEN(entries, stopMiddle) == keylen) + difference = memcmp(HSTORE_KEY(entries, base, stopMiddle), key, keylen); + else + difference = (HSTORE_KEYLEN(entries, stopMiddle) > keylen) ? 1 : -1; + + if (difference == 0) + { + if (lowbound) + *lowbound = stopMiddle + 1; + return stopMiddle; + } + else if (difference < 0) + stopLow = stopMiddle + 1; + else + stopHigh = stopMiddle; + } + + if (lowbound) + *lowbound = stopLow; + return -1; +} + +Pairs * +hstoreArrayToPairs(ArrayType *a, int *npairs) +{ + Datum *key_datums; + bool *key_nulls; + int key_count; + Pairs *key_pairs; + int bufsiz; + int i, + j; + + deconstruct_array(a, + TEXTOID, -1, false, TYPALIGN_INT, + &key_datums, &key_nulls, &key_count); + + if (key_count == 0) + { + *npairs = 0; + return NULL; + } + + /* + * A text array uses at least eight bytes per element, so any overflow in + * "key_count * sizeof(Pairs)" is small enough for palloc() to catch. + * However, credible improvements to the array format could invalidate + * that assumption. Therefore, use an explicit check rather than relying + * on palloc() to complain. + */ + 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))))); + + key_pairs = palloc(sizeof(Pairs) * key_count); + + for (i = 0, j = 0; i < key_count; i++) + { + if (!key_nulls[i]) + { + key_pairs[j].key = VARDATA(key_datums[i]); + key_pairs[j].keylen = VARSIZE(key_datums[i]) - VARHDRSZ; + key_pairs[j].val = NULL; + key_pairs[j].vallen = 0; + key_pairs[j].needfree = 0; + key_pairs[j].isnull = 1; + j++; + } + } + + *npairs = hstoreUniquePairs(key_pairs, j, &bufsiz); + + return key_pairs; +} + + +PG_FUNCTION_INFO_V1(hstore_fetchval); +Datum +hstore_fetchval(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + text *key = PG_GETARG_TEXT_PP(1); + HEntry *entries = ARRPTR(hs); + text *out; + int idx = hstoreFindKey(hs, NULL, + VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); + + if (idx < 0 || HSTORE_VALISNULL(entries, idx)) + PG_RETURN_NULL(); + + out = cstring_to_text_with_len(HSTORE_VAL(entries, STRPTR(hs), idx), + HSTORE_VALLEN(entries, idx)); + + PG_RETURN_TEXT_P(out); +} + + +PG_FUNCTION_INFO_V1(hstore_exists); +Datum +hstore_exists(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + text *key = PG_GETARG_TEXT_PP(1); + int idx = hstoreFindKey(hs, NULL, + VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); + + PG_RETURN_BOOL(idx >= 0); +} + + +PG_FUNCTION_INFO_V1(hstore_exists_any); +Datum +hstore_exists_any(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); + int nkeys; + Pairs *key_pairs = hstoreArrayToPairs(keys, &nkeys); + int i; + int lowbound = 0; + bool res = false; + + /* + * we exploit the fact that the pairs list is already sorted into strictly + * increasing order to narrow the hstoreFindKey search; each search can + * start one entry past the previous "found" entry, or at the lower bound + * of the last search. + */ + for (i = 0; i < nkeys; i++) + { + int idx = hstoreFindKey(hs, &lowbound, + key_pairs[i].key, key_pairs[i].keylen); + + if (idx >= 0) + { + res = true; + break; + } + } + + PG_RETURN_BOOL(res); +} + + +PG_FUNCTION_INFO_V1(hstore_exists_all); +Datum +hstore_exists_all(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); + int nkeys; + Pairs *key_pairs = hstoreArrayToPairs(keys, &nkeys); + int i; + int lowbound = 0; + bool res = true; + + /* + * we exploit the fact that the pairs list is already sorted into strictly + * increasing order to narrow the hstoreFindKey search; each search can + * start one entry past the previous "found" entry, or at the lower bound + * of the last search. + */ + for (i = 0; i < nkeys; i++) + { + int idx = hstoreFindKey(hs, &lowbound, + key_pairs[i].key, key_pairs[i].keylen); + + if (idx < 0) + { + res = false; + break; + } + } + + PG_RETURN_BOOL(res); +} + + +PG_FUNCTION_INFO_V1(hstore_defined); +Datum +hstore_defined(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + text *key = PG_GETARG_TEXT_PP(1); + HEntry *entries = ARRPTR(hs); + int idx = hstoreFindKey(hs, NULL, + VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); + bool res = (idx >= 0 && !HSTORE_VALISNULL(entries, idx)); + + PG_RETURN_BOOL(res); +} + + +PG_FUNCTION_INFO_V1(hstore_delete); +Datum +hstore_delete(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + text *key = PG_GETARG_TEXT_PP(1); + char *keyptr = VARDATA_ANY(key); + int keylen = VARSIZE_ANY_EXHDR(key); + HStore *out = palloc(VARSIZE(hs)); + char *bufs, + *bufd, + *ptrd; + HEntry *es, + *ed; + int i; + int count = HS_COUNT(hs); + int outcount = 0; + + SET_VARSIZE(out, VARSIZE(hs)); + HS_SETCOUNT(out, count); /* temporary! */ + + bufs = STRPTR(hs); + es = ARRPTR(hs); + bufd = ptrd = STRPTR(out); + ed = ARRPTR(out); + + for (i = 0; i < count; ++i) + { + int len = HSTORE_KEYLEN(es, i); + char *ptrs = HSTORE_KEY(es, bufs, i); + + if (!(len == keylen && memcmp(ptrs, keyptr, keylen) == 0)) + { + int vallen = HSTORE_VALLEN(es, i); + + HS_COPYITEM(ed, bufd, ptrd, ptrs, len, vallen, + HSTORE_VALISNULL(es, i)); + ++outcount; + } + } + + HS_FINALIZE(out, outcount, bufd, ptrd); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_delete_array); +Datum +hstore_delete_array(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + HStore *out = palloc(VARSIZE(hs)); + int hs_count = HS_COUNT(hs); + char *ps, + *bufd, + *pd; + HEntry *es, + *ed; + int i, + j; + int outcount = 0; + ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1); + int nkeys; + Pairs *key_pairs = hstoreArrayToPairs(key_array, &nkeys); + + SET_VARSIZE(out, VARSIZE(hs)); + HS_SETCOUNT(out, hs_count); /* temporary! */ + + ps = STRPTR(hs); + es = ARRPTR(hs); + bufd = pd = STRPTR(out); + ed = ARRPTR(out); + + if (nkeys == 0) + { + /* return a copy of the input, unchanged */ + memcpy(out, hs, VARSIZE(hs)); + HS_FIXSIZE(out, hs_count); + HS_SETCOUNT(out, hs_count); + PG_RETURN_POINTER(out); + } + + /* + * this is in effect a merge between hs and key_pairs, both of which are + * already sorted by (keylen,key); we take keys from hs only + */ + + for (i = j = 0; i < hs_count;) + { + int difference; + + if (j >= nkeys) + difference = -1; + else + { + int skeylen = HSTORE_KEYLEN(es, i); + + if (skeylen == key_pairs[j].keylen) + difference = memcmp(HSTORE_KEY(es, ps, i), + key_pairs[j].key, + key_pairs[j].keylen); + else + difference = (skeylen > key_pairs[j].keylen) ? 1 : -1; + } + + if (difference > 0) + ++j; + else if (difference == 0) + ++i, ++j; + else + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es, ps, i), HSTORE_KEYLEN(es, i), + HSTORE_VALLEN(es, i), HSTORE_VALISNULL(es, i)); + ++outcount; + ++i; + } + } + + HS_FINALIZE(out, outcount, bufd, pd); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_delete_hstore); +Datum +hstore_delete_hstore(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + HStore *hs2 = PG_GETARG_HSTORE_P(1); + HStore *out = palloc(VARSIZE(hs)); + int hs_count = HS_COUNT(hs); + int hs2_count = HS_COUNT(hs2); + char *ps, + *ps2, + *bufd, + *pd; + HEntry *es, + *es2, + *ed; + int i, + j; + int outcount = 0; + + SET_VARSIZE(out, VARSIZE(hs)); + HS_SETCOUNT(out, hs_count); /* temporary! */ + + ps = STRPTR(hs); + es = ARRPTR(hs); + ps2 = STRPTR(hs2); + es2 = ARRPTR(hs2); + bufd = pd = STRPTR(out); + ed = ARRPTR(out); + + if (hs2_count == 0) + { + /* return a copy of the input, unchanged */ + memcpy(out, hs, VARSIZE(hs)); + HS_FIXSIZE(out, hs_count); + HS_SETCOUNT(out, hs_count); + PG_RETURN_POINTER(out); + } + + /* + * this is in effect a merge between hs and hs2, both of which are already + * sorted by (keylen,key); we take keys from hs only; for equal keys, we + * take the value from hs unless the values are equal + */ + + for (i = j = 0; i < hs_count;) + { + int difference; + + if (j >= hs2_count) + difference = -1; + else + { + int skeylen = HSTORE_KEYLEN(es, i); + int s2keylen = HSTORE_KEYLEN(es2, j); + + if (skeylen == s2keylen) + difference = memcmp(HSTORE_KEY(es, ps, i), + HSTORE_KEY(es2, ps2, j), + skeylen); + else + difference = (skeylen > s2keylen) ? 1 : -1; + } + + if (difference > 0) + ++j; + else if (difference == 0) + { + int svallen = HSTORE_VALLEN(es, i); + int snullval = HSTORE_VALISNULL(es, i); + + if (snullval != HSTORE_VALISNULL(es2, j) || + (!snullval && (svallen != HSTORE_VALLEN(es2, j) || + memcmp(HSTORE_VAL(es, ps, i), + HSTORE_VAL(es2, ps2, j), + svallen) != 0))) + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es, ps, i), HSTORE_KEYLEN(es, i), + svallen, snullval); + ++outcount; + } + ++i, ++j; + } + else + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es, ps, i), HSTORE_KEYLEN(es, i), + HSTORE_VALLEN(es, i), HSTORE_VALISNULL(es, i)); + ++outcount; + ++i; + } + } + + HS_FINALIZE(out, outcount, bufd, pd); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_concat); +Datum +hstore_concat(PG_FUNCTION_ARGS) +{ + HStore *s1 = PG_GETARG_HSTORE_P(0); + HStore *s2 = PG_GETARG_HSTORE_P(1); + HStore *out = palloc(VARSIZE(s1) + VARSIZE(s2)); + char *ps1, + *ps2, + *bufd, + *pd; + HEntry *es1, + *es2, + *ed; + int s1idx; + int s2idx; + int s1count = HS_COUNT(s1); + int s2count = HS_COUNT(s2); + int outcount = 0; + + SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2) - HSHRDSIZE); + HS_SETCOUNT(out, s1count + s2count); + + if (s1count == 0) + { + /* return a copy of the input, unchanged */ + memcpy(out, s2, VARSIZE(s2)); + HS_FIXSIZE(out, s2count); + HS_SETCOUNT(out, s2count); + PG_RETURN_POINTER(out); + } + + if (s2count == 0) + { + /* return a copy of the input, unchanged */ + memcpy(out, s1, VARSIZE(s1)); + HS_FIXSIZE(out, s1count); + HS_SETCOUNT(out, s1count); + PG_RETURN_POINTER(out); + } + + ps1 = STRPTR(s1); + ps2 = STRPTR(s2); + bufd = pd = STRPTR(out); + es1 = ARRPTR(s1); + es2 = ARRPTR(s2); + ed = ARRPTR(out); + + /* + * this is in effect a merge between s1 and s2, both of which are already + * sorted by (keylen,key); we take s2 for equal keys + */ + + for (s1idx = s2idx = 0; s1idx < s1count || s2idx < s2count; ++outcount) + { + int difference; + + if (s1idx >= s1count) + difference = 1; + else if (s2idx >= s2count) + difference = -1; + else + { + int s1keylen = HSTORE_KEYLEN(es1, s1idx); + int s2keylen = HSTORE_KEYLEN(es2, s2idx); + + if (s1keylen == s2keylen) + difference = memcmp(HSTORE_KEY(es1, ps1, s1idx), + HSTORE_KEY(es2, ps2, s2idx), + s1keylen); + else + difference = (s1keylen > s2keylen) ? 1 : -1; + } + + if (difference >= 0) + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es2, ps2, s2idx), HSTORE_KEYLEN(es2, s2idx), + HSTORE_VALLEN(es2, s2idx), HSTORE_VALISNULL(es2, s2idx)); + ++s2idx; + if (difference == 0) + ++s1idx; + } + else + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es1, ps1, s1idx), HSTORE_KEYLEN(es1, s1idx), + HSTORE_VALLEN(es1, s1idx), HSTORE_VALISNULL(es1, s1idx)); + ++s1idx; + } + } + + HS_FINALIZE(out, outcount, bufd, pd); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_slice_to_array); +Datum +hstore_slice_to_array(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + HEntry *entries = ARRPTR(hs); + char *ptr = STRPTR(hs); + ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1); + ArrayType *aout; + Datum *key_datums; + bool *key_nulls; + Datum *out_datums; + bool *out_nulls; + int key_count; + int i; + + deconstruct_array(key_array, + TEXTOID, -1, false, TYPALIGN_INT, + &key_datums, &key_nulls, &key_count); + + if (key_count == 0) + { + aout = construct_empty_array(TEXTOID); + PG_RETURN_POINTER(aout); + } + + out_datums = palloc(sizeof(Datum) * key_count); + out_nulls = palloc(sizeof(bool) * key_count); + + for (i = 0; i < key_count; ++i) + { + text *key = (text *) DatumGetPointer(key_datums[i]); + int idx; + + if (key_nulls[i]) + idx = -1; + else + idx = hstoreFindKey(hs, NULL, VARDATA(key), VARSIZE(key) - VARHDRSZ); + + if (idx < 0 || HSTORE_VALISNULL(entries, idx)) + { + out_nulls[i] = true; + out_datums[i] = (Datum) 0; + } + else + { + out_datums[i] = + PointerGetDatum(cstring_to_text_with_len(HSTORE_VAL(entries, ptr, idx), + HSTORE_VALLEN(entries, idx))); + out_nulls[i] = false; + } + } + + aout = construct_md_array(out_datums, out_nulls, + ARR_NDIM(key_array), + ARR_DIMS(key_array), + ARR_LBOUND(key_array), + TEXTOID, -1, false, TYPALIGN_INT); + + PG_RETURN_POINTER(aout); +} + + +PG_FUNCTION_INFO_V1(hstore_slice_to_hstore); +Datum +hstore_slice_to_hstore(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + HEntry *entries = ARRPTR(hs); + char *ptr = STRPTR(hs); + ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1); + HStore *out; + int nkeys; + Pairs *key_pairs = hstoreArrayToPairs(key_array, &nkeys); + Pairs *out_pairs; + int bufsiz; + int lastidx = 0; + int i; + int out_count = 0; + + if (nkeys == 0) + { + out = hstorePairs(NULL, 0, 0); + PG_RETURN_POINTER(out); + } + + /* hstoreArrayToPairs() checked overflow */ + out_pairs = palloc(sizeof(Pairs) * nkeys); + bufsiz = 0; + + /* + * we exploit the fact that the pairs list is already sorted into strictly + * increasing order to narrow the hstoreFindKey search; each search can + * start one entry past the previous "found" entry, or at the lower bound + * of the last search. + */ + + for (i = 0; i < nkeys; ++i) + { + int idx = hstoreFindKey(hs, &lastidx, + key_pairs[i].key, key_pairs[i].keylen); + + if (idx >= 0) + { + out_pairs[out_count].key = key_pairs[i].key; + bufsiz += (out_pairs[out_count].keylen = key_pairs[i].keylen); + out_pairs[out_count].val = HSTORE_VAL(entries, ptr, idx); + bufsiz += (out_pairs[out_count].vallen = HSTORE_VALLEN(entries, idx)); + out_pairs[out_count].isnull = HSTORE_VALISNULL(entries, idx); + out_pairs[out_count].needfree = false; + ++out_count; + } + } + + /* + * we don't use hstoreUniquePairs here because we know that the pairs list + * is already sorted and uniq'ed. + */ + + out = hstorePairs(out_pairs, out_count, bufsiz); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_akeys); +Datum +hstore_akeys(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + Datum *d; + ArrayType *a; + HEntry *entries = ARRPTR(hs); + char *base = STRPTR(hs); + int count = HS_COUNT(hs); + int i; + + if (count == 0) + { + a = construct_empty_array(TEXTOID); + PG_RETURN_POINTER(a); + } + + d = (Datum *) palloc(sizeof(Datum) * count); + + for (i = 0; i < count; ++i) + { + text *t = cstring_to_text_with_len(HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + + d[i] = PointerGetDatum(t); + } + + a = construct_array(d, count, + TEXTOID, -1, false, TYPALIGN_INT); + + PG_RETURN_POINTER(a); +} + + +PG_FUNCTION_INFO_V1(hstore_avals); +Datum +hstore_avals(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + Datum *d; + bool *nulls; + ArrayType *a; + HEntry *entries = ARRPTR(hs); + char *base = STRPTR(hs); + int count = HS_COUNT(hs); + int lb = 1; + int i; + + if (count == 0) + { + a = construct_empty_array(TEXTOID); + PG_RETURN_POINTER(a); + } + + d = (Datum *) palloc(sizeof(Datum) * count); + nulls = (bool *) palloc(sizeof(bool) * count); + + for (i = 0; i < count; ++i) + { + if (HSTORE_VALISNULL(entries, i)) + { + d[i] = (Datum) 0; + nulls[i] = true; + } + else + { + text *item = cstring_to_text_with_len(HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + + d[i] = PointerGetDatum(item); + nulls[i] = false; + } + } + + a = construct_md_array(d, nulls, 1, &count, &lb, + TEXTOID, -1, false, TYPALIGN_INT); + + PG_RETURN_POINTER(a); +} + + +static ArrayType * +hstore_to_array_internal(HStore *hs, int ndims) +{ + HEntry *entries = ARRPTR(hs); + char *base = STRPTR(hs); + int count = HS_COUNT(hs); + int out_size[2] = {0, 2}; + int lb[2] = {1, 1}; + Datum *out_datums; + bool *out_nulls; + int i; + + Assert(ndims < 3); + + if (count == 0 || ndims == 0) + return construct_empty_array(TEXTOID); + + out_size[0] = count * 2 / ndims; + out_datums = palloc(sizeof(Datum) * count * 2); + out_nulls = palloc(sizeof(bool) * count * 2); + + for (i = 0; i < count; ++i) + { + text *key = cstring_to_text_with_len(HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + + out_datums[i * 2] = PointerGetDatum(key); + out_nulls[i * 2] = false; + + if (HSTORE_VALISNULL(entries, i)) + { + out_datums[i * 2 + 1] = (Datum) 0; + out_nulls[i * 2 + 1] = true; + } + else + { + text *item = cstring_to_text_with_len(HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + + out_datums[i * 2 + 1] = PointerGetDatum(item); + out_nulls[i * 2 + 1] = false; + } + } + + return construct_md_array(out_datums, out_nulls, + ndims, out_size, lb, + TEXTOID, -1, false, TYPALIGN_INT); +} + +PG_FUNCTION_INFO_V1(hstore_to_array); +Datum +hstore_to_array(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + ArrayType *out = hstore_to_array_internal(hs, 1); + + PG_RETURN_POINTER(out); +} + +PG_FUNCTION_INFO_V1(hstore_to_matrix); +Datum +hstore_to_matrix(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + ArrayType *out = hstore_to_array_internal(hs, 2); + + PG_RETURN_POINTER(out); +} + +/* + * Common initialization function for the various set-returning + * funcs. fcinfo is only passed if the function is to return a + * composite; it will be used to look up the return tupledesc. + * we stash a copy of the hstore in the multi-call context in + * case it was originally toasted. (At least I assume that's why; + * there was no explanatory comment in the original code. --AG) + */ + +static void +setup_firstcall(FuncCallContext *funcctx, HStore *hs, + FunctionCallInfo fcinfo) +{ + MemoryContext oldcontext; + HStore *st; + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + st = (HStore *) palloc(VARSIZE(hs)); + memcpy(st, hs, VARSIZE(hs)); + + funcctx->user_fctx = (void *) st; + + if (fcinfo) + { + TupleDesc tupdesc; + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + } + + MemoryContextSwitchTo(oldcontext); +} + + +PG_FUNCTION_INFO_V1(hstore_skeys); +Datum +hstore_skeys(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + HStore *hs; + int i; + + if (SRF_IS_FIRSTCALL()) + { + hs = PG_GETARG_HSTORE_P(0); + funcctx = SRF_FIRSTCALL_INIT(); + setup_firstcall(funcctx, hs, NULL); + } + + funcctx = SRF_PERCALL_SETUP(); + hs = (HStore *) funcctx->user_fctx; + i = funcctx->call_cntr; + + if (i < HS_COUNT(hs)) + { + HEntry *entries = ARRPTR(hs); + text *item; + + item = cstring_to_text_with_len(HSTORE_KEY(entries, STRPTR(hs), i), + HSTORE_KEYLEN(entries, i)); + + SRF_RETURN_NEXT(funcctx, PointerGetDatum(item)); + } + + SRF_RETURN_DONE(funcctx); +} + + +PG_FUNCTION_INFO_V1(hstore_svals); +Datum +hstore_svals(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + HStore *hs; + int i; + + if (SRF_IS_FIRSTCALL()) + { + hs = PG_GETARG_HSTORE_P(0); + funcctx = SRF_FIRSTCALL_INIT(); + setup_firstcall(funcctx, hs, NULL); + } + + funcctx = SRF_PERCALL_SETUP(); + hs = (HStore *) funcctx->user_fctx; + i = funcctx->call_cntr; + + if (i < HS_COUNT(hs)) + { + HEntry *entries = ARRPTR(hs); + + if (HSTORE_VALISNULL(entries, i)) + { + ReturnSetInfo *rsi; + + /* ugly ugly ugly. why no macro for this? */ + (funcctx)->call_cntr++; + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + rsi->isDone = ExprMultipleResult; + PG_RETURN_NULL(); + } + else + { + text *item; + + item = cstring_to_text_with_len(HSTORE_VAL(entries, STRPTR(hs), i), + HSTORE_VALLEN(entries, i)); + + SRF_RETURN_NEXT(funcctx, PointerGetDatum(item)); + } + } + + SRF_RETURN_DONE(funcctx); +} + + +PG_FUNCTION_INFO_V1(hstore_contains); +Datum +hstore_contains(PG_FUNCTION_ARGS) +{ + HStore *val = PG_GETARG_HSTORE_P(0); + HStore *tmpl = PG_GETARG_HSTORE_P(1); + bool res = true; + HEntry *te = ARRPTR(tmpl); + char *tstr = STRPTR(tmpl); + HEntry *ve = ARRPTR(val); + char *vstr = STRPTR(val); + int tcount = HS_COUNT(tmpl); + int lastidx = 0; + int i; + + /* + * we exploit the fact that keys in "tmpl" are in strictly increasing + * order to narrow the hstoreFindKey search; each search can start one + * entry past the previous "found" entry, or at the lower bound of the + * search + */ + + for (i = 0; res && i < tcount; ++i) + { + int idx = hstoreFindKey(val, &lastidx, + HSTORE_KEY(te, tstr, i), + HSTORE_KEYLEN(te, i)); + + if (idx >= 0) + { + bool nullval = HSTORE_VALISNULL(te, i); + int vallen = HSTORE_VALLEN(te, i); + + if (nullval != HSTORE_VALISNULL(ve, idx) || + (!nullval && (vallen != HSTORE_VALLEN(ve, idx) || + memcmp(HSTORE_VAL(te, tstr, i), + HSTORE_VAL(ve, vstr, idx), + vallen) != 0))) + res = false; + } + else + res = false; + } + + PG_RETURN_BOOL(res); +} + + +PG_FUNCTION_INFO_V1(hstore_contained); +Datum +hstore_contained(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(hstore_contains, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} + + +PG_FUNCTION_INFO_V1(hstore_each); +Datum +hstore_each(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + HStore *hs; + int i; + + if (SRF_IS_FIRSTCALL()) + { + hs = PG_GETARG_HSTORE_P(0); + funcctx = SRF_FIRSTCALL_INIT(); + setup_firstcall(funcctx, hs, fcinfo); + } + + funcctx = SRF_PERCALL_SETUP(); + hs = (HStore *) funcctx->user_fctx; + i = funcctx->call_cntr; + + if (i < HS_COUNT(hs)) + { + HEntry *entries = ARRPTR(hs); + char *ptr = STRPTR(hs); + Datum res, + dvalues[2]; + bool nulls[2] = {false, false}; + text *item; + HeapTuple tuple; + + item = cstring_to_text_with_len(HSTORE_KEY(entries, ptr, i), + HSTORE_KEYLEN(entries, i)); + dvalues[0] = PointerGetDatum(item); + + if (HSTORE_VALISNULL(entries, i)) + { + dvalues[1] = (Datum) 0; + nulls[1] = true; + } + else + { + item = cstring_to_text_with_len(HSTORE_VAL(entries, ptr, i), + HSTORE_VALLEN(entries, i)); + dvalues[1] = PointerGetDatum(item); + } + + tuple = heap_form_tuple(funcctx->tuple_desc, dvalues, nulls); + res = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, PointerGetDatum(res)); + } + + SRF_RETURN_DONE(funcctx); +} + + +/* + * btree sort order for hstores isn't intended to be useful; we really only + * care about equality versus non-equality. we compare the entire string + * buffer first, then the entry pos array. + */ + +PG_FUNCTION_INFO_V1(hstore_cmp); +Datum +hstore_cmp(PG_FUNCTION_ARGS) +{ + HStore *hs1 = PG_GETARG_HSTORE_P(0); + HStore *hs2 = PG_GETARG_HSTORE_P(1); + int hcount1 = HS_COUNT(hs1); + int hcount2 = HS_COUNT(hs2); + int res = 0; + + if (hcount1 == 0 || hcount2 == 0) + { + /* + * if either operand is empty, and the other is nonempty, the nonempty + * one is larger. If both are empty they are equal. + */ + if (hcount1 > 0) + res = 1; + else if (hcount2 > 0) + res = -1; + } + else + { + /* here we know both operands are nonempty */ + char *str1 = STRPTR(hs1); + char *str2 = STRPTR(hs2); + HEntry *ent1 = ARRPTR(hs1); + HEntry *ent2 = ARRPTR(hs2); + size_t len1 = HSE_ENDPOS(ent1[2 * hcount1 - 1]); + size_t len2 = HSE_ENDPOS(ent2[2 * hcount2 - 1]); + + res = memcmp(str1, str2, Min(len1, len2)); + + if (res == 0) + { + if (len1 > len2) + res = 1; + else if (len1 < len2) + res = -1; + else if (hcount1 > hcount2) + res = 1; + else if (hcount2 > hcount1) + res = -1; + else + { + int count = hcount1 * 2; + int i; + + for (i = 0; i < count; ++i) + if (HSE_ENDPOS(ent1[i]) != HSE_ENDPOS(ent2[i]) || + HSE_ISNULL(ent1[i]) != HSE_ISNULL(ent2[i])) + break; + if (i < count) + { + if (HSE_ENDPOS(ent1[i]) < HSE_ENDPOS(ent2[i])) + res = -1; + else if (HSE_ENDPOS(ent1[i]) > HSE_ENDPOS(ent2[i])) + res = 1; + else if (HSE_ISNULL(ent1[i])) + res = 1; + else if (HSE_ISNULL(ent2[i])) + res = -1; + } + } + } + else + { + res = (res > 0) ? 1 : -1; + } + } + + /* + * this is a btree support function; this is one of the few places where + * memory needs to be explicitly freed. + */ + PG_FREE_IF_COPY(hs1, 0); + PG_FREE_IF_COPY(hs2, 1); + PG_RETURN_INT32(res); +} + + +PG_FUNCTION_INFO_V1(hstore_eq); +Datum +hstore_eq(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res == 0); +} + +PG_FUNCTION_INFO_V1(hstore_ne); +Datum +hstore_ne(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res != 0); +} + +PG_FUNCTION_INFO_V1(hstore_gt); +Datum +hstore_gt(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res > 0); +} + +PG_FUNCTION_INFO_V1(hstore_ge); +Datum +hstore_ge(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res >= 0); +} + +PG_FUNCTION_INFO_V1(hstore_lt); +Datum +hstore_lt(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res < 0); +} + +PG_FUNCTION_INFO_V1(hstore_le); +Datum +hstore_le(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res <= 0); +} + + +PG_FUNCTION_INFO_V1(hstore_hash); +Datum +hstore_hash(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + Datum hval = hash_any((unsigned char *) VARDATA(hs), + VARSIZE(hs) - VARHDRSZ); + + /* + * This (along with hstore_hash_extended) is the only place in the code + * that cares whether the overall varlena size exactly matches the true + * data size; this assertion should be maintained by all the other code, + * but we make it explicit here. + */ + Assert(VARSIZE(hs) == + (HS_COUNT(hs) != 0 ? + CALCDATASIZE(HS_COUNT(hs), + HSE_ENDPOS(ARRPTR(hs)[2 * HS_COUNT(hs) - 1])) : + HSHRDSIZE)); + + PG_FREE_IF_COPY(hs, 0); + PG_RETURN_DATUM(hval); +} + +PG_FUNCTION_INFO_V1(hstore_hash_extended); +Datum +hstore_hash_extended(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + uint64 seed = PG_GETARG_INT64(1); + Datum hval; + + hval = hash_any_extended((unsigned char *) VARDATA(hs), + VARSIZE(hs) - VARHDRSZ, + seed); + + /* See comment in hstore_hash */ + Assert(VARSIZE(hs) == + (HS_COUNT(hs) != 0 ? + CALCDATASIZE(HS_COUNT(hs), + HSE_ENDPOS(ARRPTR(hs)[2 * HS_COUNT(hs) - 1])) : + HSHRDSIZE)); + + PG_FREE_IF_COPY(hs, 0); + PG_RETURN_DATUM(hval); +} |