mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-15 08:20:16 +08:00
ba398969cd
In particular, make hstore @> '' succeed for all hstores, likewise hstore ?& '{}'. Previously the results were inconsistent and could depend on whether you were using a GiST index, GIN index, or seqscan.
1266 lines
27 KiB
C
1266 lines
27 KiB
C
/*
|
|
* contrib/hstore/hstore_op.c
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/hash.h"
|
|
#include "access/heapam.h"
|
|
#include "access/htup.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "funcapi.h"
|
|
#include "utils/builtins.h"
|
|
|
|
#include "hstore.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 (HS_KEYLEN(entries, stopMiddle) == keylen)
|
|
difference = memcmp(HS_KEY(entries, base, stopMiddle), key, keylen);
|
|
else
|
|
difference = (HS_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, 'i',
|
|
&key_datums, &key_nulls, &key_count);
|
|
|
|
if (key_count == 0)
|
|
{
|
|
*npairs = 0;
|
|
return NULL;
|
|
}
|
|
|
|
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);
|
|
Datum
|
|
hstore_fetchval(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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 || HS_VALISNULL(entries, idx))
|
|
PG_RETURN_NULL();
|
|
|
|
out = cstring_to_text_with_len(HS_VAL(entries, STRPTR(hs), idx),
|
|
HS_VALLEN(entries, idx));
|
|
|
|
PG_RETURN_TEXT_P(out);
|
|
}
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(hstore_exists);
|
|
Datum hstore_exists(PG_FUNCTION_ARGS);
|
|
Datum
|
|
hstore_exists(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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);
|
|
Datum
|
|
hstore_exists_any(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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);
|
|
Datum
|
|
hstore_exists_all(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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);
|
|
Datum
|
|
hstore_defined(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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 && !HS_VALISNULL(entries, idx));
|
|
|
|
PG_RETURN_BOOL(res);
|
|
}
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(hstore_delete);
|
|
Datum hstore_delete(PG_FUNCTION_ARGS);
|
|
Datum
|
|
hstore_delete(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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 = HS_KEYLEN(es, i);
|
|
char *ptrs = HS_KEY(es, bufs, i);
|
|
|
|
if (!(len == keylen && memcmp(ptrs, keyptr, keylen) == 0))
|
|
{
|
|
int vallen = HS_VALLEN(es, i);
|
|
|
|
HS_COPYITEM(ed, bufd, ptrd, ptrs, len, vallen, HS_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);
|
|
Datum
|
|
hstore_delete_array(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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 = HS_KEYLEN(es, i);
|
|
|
|
if (skeylen == key_pairs[j].keylen)
|
|
difference = memcmp(HS_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,
|
|
HS_KEY(es, ps, i), HS_KEYLEN(es, i),
|
|
HS_VALLEN(es, i), HS_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);
|
|
Datum
|
|
hstore_delete_hstore(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(0);
|
|
HStore *hs2 = PG_GETARG_HS(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 = HS_KEYLEN(es, i);
|
|
int s2keylen = HS_KEYLEN(es2, j);
|
|
|
|
if (skeylen == s2keylen)
|
|
difference = memcmp(HS_KEY(es, ps, i),
|
|
HS_KEY(es2, ps2, j),
|
|
skeylen);
|
|
else
|
|
difference = (skeylen > s2keylen) ? 1 : -1;
|
|
}
|
|
|
|
if (difference > 0)
|
|
++j;
|
|
else if (difference == 0)
|
|
{
|
|
int svallen = HS_VALLEN(es, i);
|
|
int snullval = HS_VALISNULL(es, i);
|
|
|
|
if (snullval != HS_VALISNULL(es2, j)
|
|
|| (!snullval
|
|
&& (svallen != HS_VALLEN(es2, j)
|
|
|| memcmp(HS_VAL(es, ps, i), HS_VAL(es2, ps2, j), svallen) != 0)))
|
|
{
|
|
HS_COPYITEM(ed, bufd, pd,
|
|
HS_KEY(es, ps, i), HS_KEYLEN(es, i),
|
|
svallen, snullval);
|
|
++outcount;
|
|
}
|
|
++i, ++j;
|
|
}
|
|
else
|
|
{
|
|
HS_COPYITEM(ed, bufd, pd,
|
|
HS_KEY(es, ps, i), HS_KEYLEN(es, i),
|
|
HS_VALLEN(es, i), HS_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);
|
|
Datum
|
|
hstore_concat(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *s1 = PG_GETARG_HS(0);
|
|
HStore *s2 = PG_GETARG_HS(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 = HS_KEYLEN(es1, s1idx);
|
|
int s2keylen = HS_KEYLEN(es2, s2idx);
|
|
|
|
if (s1keylen == s2keylen)
|
|
difference = memcmp(HS_KEY(es1, ps1, s1idx),
|
|
HS_KEY(es2, ps2, s2idx),
|
|
s1keylen);
|
|
else
|
|
difference = (s1keylen > s2keylen) ? 1 : -1;
|
|
}
|
|
|
|
if (difference >= 0)
|
|
{
|
|
HS_COPYITEM(ed, bufd, pd,
|
|
HS_KEY(es2, ps2, s2idx), HS_KEYLEN(es2, s2idx),
|
|
HS_VALLEN(es2, s2idx), HS_VALISNULL(es2, s2idx));
|
|
++s2idx;
|
|
if (difference == 0)
|
|
++s1idx;
|
|
}
|
|
else
|
|
{
|
|
HS_COPYITEM(ed, bufd, pd,
|
|
HS_KEY(es1, ps1, s1idx), HS_KEYLEN(es1, s1idx),
|
|
HS_VALLEN(es1, s1idx), HS_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);
|
|
Datum
|
|
hstore_slice_to_array(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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, 'i',
|
|
&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 || HS_VALISNULL(entries, idx))
|
|
{
|
|
out_nulls[i] = true;
|
|
out_datums[i] = (Datum) 0;
|
|
}
|
|
else
|
|
{
|
|
out_datums[i] = PointerGetDatum(
|
|
cstring_to_text_with_len(HS_VAL(entries, ptr, idx),
|
|
HS_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, 'i');
|
|
|
|
PG_RETURN_POINTER(aout);
|
|
}
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(hstore_slice_to_hstore);
|
|
Datum hstore_slice_to_hstore(PG_FUNCTION_ARGS);
|
|
Datum
|
|
hstore_slice_to_hstore(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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);
|
|
}
|
|
|
|
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 = HS_VAL(entries, ptr, idx);
|
|
bufsiz += (out_pairs[out_count].vallen = HS_VALLEN(entries, idx));
|
|
out_pairs[out_count].isnull = HS_VALISNULL(entries, idx);
|
|
out_pairs[out_count].needfree = false;
|
|
++out_count;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* we don't use uniquePairs 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);
|
|
Datum
|
|
hstore_akeys(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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 *item = cstring_to_text_with_len(HS_KEY(entries, base, i),
|
|
HS_KEYLEN(entries, i));
|
|
|
|
d[i] = PointerGetDatum(item);
|
|
}
|
|
|
|
a = construct_array(d, count,
|
|
TEXTOID, -1, false, 'i');
|
|
|
|
PG_RETURN_POINTER(a);
|
|
}
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(hstore_avals);
|
|
Datum hstore_avals(PG_FUNCTION_ARGS);
|
|
Datum
|
|
hstore_avals(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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 (HS_VALISNULL(entries, i))
|
|
{
|
|
d[i] = (Datum) 0;
|
|
nulls[i] = true;
|
|
}
|
|
else
|
|
{
|
|
text *item = cstring_to_text_with_len(HS_VAL(entries, base, i),
|
|
HS_VALLEN(entries, i));
|
|
|
|
d[i] = PointerGetDatum(item);
|
|
nulls[i] = false;
|
|
}
|
|
}
|
|
|
|
a = construct_md_array(d, nulls, 1, &count, &lb,
|
|
TEXTOID, -1, false, 'i');
|
|
|
|
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(HS_KEY(entries, base, i),
|
|
HS_KEYLEN(entries, i));
|
|
|
|
out_datums[i * 2] = PointerGetDatum(key);
|
|
out_nulls[i * 2] = false;
|
|
|
|
if (HS_VALISNULL(entries, i))
|
|
{
|
|
out_datums[i * 2 + 1] = (Datum) 0;
|
|
out_nulls[i * 2 + 1] = true;
|
|
}
|
|
else
|
|
{
|
|
text *item = cstring_to_text_with_len(HS_VAL(entries, base, i),
|
|
HS_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, 'i');
|
|
}
|
|
|
|
PG_FUNCTION_INFO_V1(hstore_to_array);
|
|
Datum hstore_to_array(PG_FUNCTION_ARGS);
|
|
Datum
|
|
hstore_to_array(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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);
|
|
Datum
|
|
hstore_to_matrix(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(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,
|
|
FunctionCallInfoData *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);
|
|
Datum
|
|
hstore_skeys(PG_FUNCTION_ARGS)
|
|
{
|
|
FuncCallContext *funcctx;
|
|
HStore *hs;
|
|
int i;
|
|
|
|
if (SRF_IS_FIRSTCALL())
|
|
{
|
|
hs = PG_GETARG_HS(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(HS_KEY(entries, STRPTR(hs), i),
|
|
HS_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);
|
|
Datum
|
|
hstore_svals(PG_FUNCTION_ARGS)
|
|
{
|
|
FuncCallContext *funcctx;
|
|
HStore *hs;
|
|
int i;
|
|
|
|
if (SRF_IS_FIRSTCALL())
|
|
{
|
|
hs = PG_GETARG_HS(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 (HS_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(HS_VAL(entries, STRPTR(hs), i),
|
|
HS_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);
|
|
Datum
|
|
hstore_contains(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *val = PG_GETARG_HS(0);
|
|
HStore *tmpl = PG_GETARG_HS(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,
|
|
HS_KEY(te, tstr, i), HS_KEYLEN(te, i));
|
|
|
|
if (idx >= 0)
|
|
{
|
|
bool nullval = HS_VALISNULL(te, i);
|
|
int vallen = HS_VALLEN(te, i);
|
|
|
|
if (nullval != HS_VALISNULL(ve, idx)
|
|
|| (!nullval
|
|
&& (vallen != HS_VALLEN(ve, idx)
|
|
|| memcmp(HS_VAL(te, tstr, i), HS_VAL(ve, vstr, idx), vallen))))
|
|
res = false;
|
|
}
|
|
else
|
|
res = false;
|
|
}
|
|
|
|
PG_RETURN_BOOL(res);
|
|
}
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(hstore_contained);
|
|
Datum hstore_contained(PG_FUNCTION_ARGS);
|
|
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);
|
|
Datum
|
|
hstore_each(PG_FUNCTION_ARGS)
|
|
{
|
|
FuncCallContext *funcctx;
|
|
HStore *hs;
|
|
int i;
|
|
|
|
if (SRF_IS_FIRSTCALL())
|
|
{
|
|
hs = PG_GETARG_HS(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(HS_KEY(entries, ptr, i),
|
|
HS_KEYLEN(entries, i));
|
|
dvalues[0] = PointerGetDatum(item);
|
|
|
|
if (HS_VALISNULL(entries, i))
|
|
{
|
|
dvalues[1] = (Datum) 0;
|
|
nulls[1] = true;
|
|
}
|
|
else
|
|
{
|
|
item = cstring_to_text_with_len(HS_VAL(entries, ptr, i),
|
|
HS_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);
|
|
Datum
|
|
hstore_cmp(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs1 = PG_GETARG_HS(0);
|
|
HStore *hs2 = PG_GETARG_HS(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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
Datum
|
|
hstore_hash(PG_FUNCTION_ARGS)
|
|
{
|
|
HStore *hs = PG_GETARG_HS(0);
|
|
Datum hval = hash_any((unsigned char *) VARDATA(hs),
|
|
VARSIZE(hs) - VARHDRSZ);
|
|
|
|
/*
|
|
* this 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);
|
|
}
|