/*
 * $PostgreSQL: pgsql/contrib/hstore/hstore_op.c,v 1.15 2009/09/30 21:26:17 tgl Exp $
 */
#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 = strncmp(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; !res && i < nkeys; ++i)
	{
		int idx = hstoreFindKey(hs, &lowbound,
								key_pairs[i].key, key_pairs[i].keylen);

		if (idx >= 0)
			res = true;
	}

	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 = nkeys ? true : 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; res && i < nkeys; ++i)
	{
		int idx = hstoreFindKey(hs, &lowbound,
								key_pairs[i].key, key_pairs[i].keylen);

		if (idx < 0)
			res = false;
	}

	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 && strncmp(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 = strncmp(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 = strncmp(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)
						|| strncmp(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 = strncmp(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)
						|| strncmp(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);
}
