hstore patch

Started by Andrew Gierthover 16 years ago1 messages
#1Andrew Gierth
andrew@tao11.riddles.org.uk
1 attachment(s)

This patch is the updated-to-current-HEAD version of the hstore stuff
I did some months back. It is believed to be fully API-compatible with
the old hstore, though it has a different storage layout. Dump and
restore of an old db should work (the new module provides all the
function names the old one did), though the new functionality
obviously won't be available that way.

Short list of new functionality:

- unlimited key/value length
- binary I/O
- btree and hash support
- construction of hstores from key/value arrays
- hstore(record) and populate_record(record,hstore) for dynamic
manipulations of records
- many operators for hstore difference, slices, etc.; very useful
for determining changes

--
Andrew (irc:RhodiumToad)

Attachments:

hstore_20090708.patch.gztext/x-patchDownload
Index: contrib/hstore/hstore.h
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/hstore/hstore.h,v
retrieving revision 1.8
diff -c -r1.8 hstore.h
*** contrib/hstore/hstore.h	11 Jun 2009 14:48:51 -0000	1.8
--- contrib/hstore/hstore.h	8 Jul 2009 18:38:56 -0000
***************
*** 5,38 ****
  #define __HSTORE_H__
  
  #include "fmgr.h"
  
  
  typedef struct
  {
! 	uint16		keylen;
! 	uint16		vallen;
! 	uint32
! 				valisnull:1,
! 				pos:31;
  } HEntry;
  
! /* these are determined by the sizes of the keylen and vallen fields */
! /* in struct HEntry and struct Pairs */
! #define HSTORE_MAX_KEY_LEN 65535
! #define HSTORE_MAX_VALUE_LEN 65535
! 
  
  typedef struct
  {
  	int32		vl_len_;		/* varlena header (do not touch directly!) */
  	int4		size;
! 	char		data[1];
  } HStore;
  
! #define HSHRDSIZE	(VARHDRSZ + sizeof(int4))
! #define CALCDATASIZE(x, lenstr) ( (x) * sizeof(HEntry) + HSHRDSIZE + (lenstr) )
! #define ARRPTR(x)		( (HEntry*) ( (char*)(x) + HSHRDSIZE ) )
! #define STRPTR(x)		( (char*)(x) + HSHRDSIZE + ( sizeof(HEntry) * ((HStore*)x)->size ) )
  
  
  #define PG_GETARG_HS(x) ((HStore*)PG_DETOAST_DATUM(PG_GETARG_DATUM(x)))
--- 5,114 ----
  #define __HSTORE_H__
  
  #include "fmgr.h"
+ #include "utils/array.h"
  
+ /* there is one of these for each key _and_ value */
+ /* the value points to the _end_ so that we can get the length
+  * by subtraction from the previous entry.
+  */
  
  typedef struct
  {
! 	uint32  entry;
  } HEntry;
  
! #define HENTRY_ISFIRST 0x80000000
! #define HENTRY_ISNULL  0x40000000
! #define HENTRY_POSMASK 0x3FFFFFFF
! 
! /* determined by the size of "endpos", though this is a bit academic
!  * since currently varlenas (and hence both the input and the whole hstore)
!  * have the same limit
!  */
! #define HSTORE_MAX_KEY_LEN 0x3FFFFFFF
! #define HSTORE_MAX_VALUE_LEN 0x3FFFFFFF
  
  typedef struct
  {
  	int32		vl_len_;		/* varlena header (do not touch directly!) */
  	int4		size;
!     /* array of HEntry follows */
  } HStore;
  
! #define HSHRDSIZE	(sizeof(HStore))
! #define CALCDATASIZE(x, lenstr) ( (x) * 2 * sizeof(HEntry) + HSHRDSIZE + (lenstr) )
! 
! /* note multiple evaluations of x */
! #define ARRPTR(x)		( (HEntry*) ( (HStore*)(x) + 1 ) )
! #define STRPTR(x)		( (char*)(ARRPTR(x) + ((HStore*)x)->size * 2) )
! 
! /* note multiple/non evaluations */
! #define HSE_ISFIRST(he_) (((he_).entry & HENTRY_ISFIRST) != 0)
! #define HSE_ISNULL(he_) (((he_).entry & HENTRY_ISNULL) != 0)
! #define HSE_ENDPOS(he_) ((he_).entry & HENTRY_POSMASK)
! #define HSE_OFF(he_) (HSE_ISFIRST(he_) ? 0 : HSE_ENDPOS((&(he_))[-1]))
! #define HSE_LEN(he_) (HSE_ISFIRST(he_)	\
! 					  ? HSE_ENDPOS(he_) \
! 					  : HSE_ENDPOS(he_) - HSE_ENDPOS((&(he_))[-1]))
! 
! #define HS_KEY(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)]))
! #define HS_VAL(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)+1]))
! #define HS_KEYLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)]))
! #define HS_VALLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)+1]))
! #define HS_VALISNULL(arr_,i_) (HSE_ISNULL((arr_)[2*(i_)+1]))
! 
! /* currently, these following macros are the _only_ places that rely
!  * on internal knowledge of HEntry. Everything else should be using
!  * the above macros.
!  */
! 
! /* copy one key/value pair (which must be contiguous starting at
!  * sptr_) into an under-construction hstore; dent_ is an HEntry*,
!  * dbuf_ is the destination's string buffer, dptr_ is the current
!  * position in the destination. lots of modification and multiple
!  * evaluation here.
!  */
! #define HS_COPYITEM(dent_,dbuf_,dptr_,sptr_,klen_,vlen_,vnull_)			\
!     do {																\
! 		memcpy((dptr_), (sptr_), (klen_)+(vlen_));						\
! 		(dptr_) += (klen_)+(vlen_);										\
! 		(dent_)++->entry = ((dptr_) - (dbuf_) - (vlen_)) & HENTRY_POSMASK; \
! 		(dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK)		\
! 							 | ((vnull_) ? HENTRY_ISNULL : 0));			\
! 	} while(0)
! 
! /* add one key/item pair, from a Pairs structure, into an
!  * under-construction hstore
!  */
! #define HS_ADDITEM(dent_,dbuf_,dptr_,pair_)								\
! 	do {																\
! 		memcpy((dptr_), (pair_).key, (pair_).keylen);					\
! 		(dptr_) += (pair_).keylen;										\
! 		(dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK;		\
! 		if ((pair_).isnull)												\
! 			(dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK)	\
! 								 | HENTRY_ISNULL);						\
! 		else															\
! 		{																\
! 			memcpy((dptr_), (pair_).val, (pair_).vallen);				\
! 			(dptr_) += (pair_).vallen;									\
! 			(dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK;	\
! 		}																\
! 	} while (0)
! 
! /* finalize a newly-constructed hstore */
! #define HS_FINALIZE(hsp_,count_,buf_,ptr_)							\
! 	do {															\
! 		if ((count_))												\
! 			ARRPTR(hsp_)[0].entry |= HENTRY_ISFIRST;				\
! 		if ((count_) != (hsp_)->size)								\
! 		{															\
! 			int	buflen = (ptr_) - (buf_);							\
! 			(hsp_)->size = (count_);								\
! 			memmove(STRPTR(hsp_), (buf_), buflen);					\
! 			SET_VARSIZE((hsp_), CALCDATASIZE((count_), buflen));	\
! 		}															\
! 	} while (0)
  
  
  #define PG_GETARG_HS(x) ((HStore*)PG_DETOAST_DATUM(PG_GETARG_DATUM(x)))
***************
*** 41,59 ****
  {
  	char	   *key;
  	char	   *val;
! 	uint16		keylen;
! 	uint16		vallen;
  	bool		isnull;
  	bool		needfree;
  } Pairs;
  
! int			comparePairs(const void *a, const void *b);
! int			uniquePairs(Pairs *a, int4 l, int4 *buflen);
  
  size_t		hstoreCheckKeyLen(size_t len);
  size_t		hstoreCheckValLen(size_t len);
  
  #define HStoreContainsStrategyNumber	7
  #define HStoreExistsStrategyNumber		9
  
  #endif   /* __HSTORE_H__ */
--- 117,157 ----
  {
  	char	   *key;
  	char	   *val;
! 	size_t		keylen;
! 	size_t		vallen;
  	bool		isnull;
  	bool		needfree;
  } Pairs;
  
! int			hstoreUniquePairs(Pairs * a, int4 l, int4 *buflen);
! HStore *    hstorePairs(Pairs *pairs, int4 pcount, int4 buflen);
  
  size_t		hstoreCheckKeyLen(size_t len);
  size_t		hstoreCheckValLen(size_t len);
  
+ int         hstoreFindKey(HStore * hs, int *lowbound, char *key, int keylen);
+ Pairs *     hstoreArrayToPairs(ArrayType *a, int* npairs);
+ 
  #define HStoreContainsStrategyNumber	7
  #define HStoreExistsStrategyNumber		9
+ #define HStoreExistsAnyStrategyNumber	10
+ #define HStoreExistsAllStrategyNumber	11
+ 
+ /* defining HSTORE_POLLUTE_NAMESPACE=0 will prevent this; for now, we default
+  * to on for the benefit of people restoring old dumps
+  */
+ #ifndef HSTORE_POLLUTE_NAMESPACE
+ #define HSTORE_POLLUTE_NAMESPACE 1
+ #endif
+ 
+ #if HSTORE_POLLUTE_NAMESPACE
+ #define HSTORE_POLLUTE(newname_,oldname_) \
+ 	PG_FUNCTION_INFO_V1(oldname_);		  \
+ 	Datum oldname_(PG_FUNCTION_ARGS);	  \
+ 	Datum newname_(PG_FUNCTION_ARGS);	  \
+ 	Datum oldname_(PG_FUNCTION_ARGS) { return newname_(fcinfo); }
+ #else
+ #define HSTORE_POLLUTE(newname_,oldname_)
+ #endif
  
  #endif   /* __HSTORE_H__ */
Index: contrib/hstore/hstore.sql.in
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/hstore/hstore.sql.in,v
retrieving revision 1.11
diff -c -r1.11 hstore.sql.in
*** contrib/hstore/hstore.sql.in	11 Jun 2009 18:30:03 -0000	1.11
--- contrib/hstore/hstore.sql.in	8 Jul 2009 18:38:57 -0000
***************
*** 15,30 ****
  AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT;
  
  CREATE TYPE hstore (
          INTERNALLENGTH = -1,
          INPUT = hstore_in,
          OUTPUT = hstore_out,
          STORAGE = extended
  );
  
  CREATE OR REPLACE FUNCTION fetchval(hstore,text)
  RETURNS text
! AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OPERATOR -> (
--- 15,42 ----
  AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT;
  
+ CREATE OR REPLACE FUNCTION hstore_recv(internal)
+ RETURNS hstore
+ AS 'MODULE_PATHNAME'
+ LANGUAGE C STRICT;
+ 
+ CREATE OR REPLACE FUNCTION hstore_send(hstore)
+ RETURNS bytea
+ AS 'MODULE_PATHNAME'
+ LANGUAGE C STRICT;
+ 
  CREATE TYPE hstore (
          INTERNALLENGTH = -1,
          INPUT = hstore_in,
          OUTPUT = hstore_out,
+         RECEIVE = hstore_recv,
+         SEND = hstore_send,
          STORAGE = extended
  );
  
  CREATE OR REPLACE FUNCTION fetchval(hstore,text)
  RETURNS text
! AS 'MODULE_PATHNAME','hstore_fetchval'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OPERATOR -> (
***************
*** 33,46 ****
  	PROCEDURE = fetchval
  );
  
  CREATE OR REPLACE FUNCTION isexists(hstore,text)
  RETURNS bool
! AS 'MODULE_PATHNAME','exists'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION exist(hstore,text)
  RETURNS bool
! AS 'MODULE_PATHNAME','exists'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OPERATOR ? (
--- 45,80 ----
  	PROCEDURE = fetchval
  );
  
+ CREATE OR REPLACE FUNCTION slice_array(hstore,text[])
+ RETURNS text[]
+ AS 'MODULE_PATHNAME','hstore_slice_to_array'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OPERATOR -> (
+ 	LEFTARG = hstore,
+ 	RIGHTARG = text[],
+ 	PROCEDURE = slice_array
+ );
+ 
+ CREATE OR REPLACE FUNCTION slice_hstore(hstore,text[])
+ RETURNS hstore
+ AS 'MODULE_PATHNAME','hstore_slice_to_hstore'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OPERATOR => (
+ 	LEFTARG = hstore,
+ 	RIGHTARG = text[],
+ 	PROCEDURE = slice_hstore
+ );
+ 
  CREATE OR REPLACE FUNCTION isexists(hstore,text)
  RETURNS bool
! AS 'MODULE_PATHNAME','hstore_exists'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION exist(hstore,text)
  RETURNS bool
! AS 'MODULE_PATHNAME','hstore_exists'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OPERATOR ? (
***************
*** 51,74 ****
  	JOIN = contjoinsel
  );
  
  CREATE OR REPLACE FUNCTION isdefined(hstore,text)
  RETURNS bool
! AS 'MODULE_PATHNAME','defined'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION defined(hstore,text)
  RETURNS bool
! AS 'MODULE_PATHNAME','defined'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION delete(hstore,text)
  RETURNS hstore
! AS 'MODULE_PATHNAME','delete'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION hs_concat(hstore,hstore)
  RETURNS hstore
! AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OPERATOR || (
--- 85,162 ----
  	JOIN = contjoinsel
  );
  
+ CREATE OR REPLACE FUNCTION exists_any(hstore,text[])
+ RETURNS bool
+ AS 'MODULE_PATHNAME','hstore_exists_any'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OPERATOR ?| (
+ 	LEFTARG = hstore,
+ 	RIGHTARG = text[],
+ 	PROCEDURE = exists_any,
+ 	RESTRICT = contsel,
+ 	JOIN = contjoinsel
+ );
+ 
+ CREATE OR REPLACE FUNCTION exists_all(hstore,text[])
+ RETURNS bool
+ AS 'MODULE_PATHNAME','hstore_exists_all'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OPERATOR ?& (
+ 	LEFTARG = hstore,
+ 	RIGHTARG = text[],
+ 	PROCEDURE = exists_all,
+ 	RESTRICT = contsel,
+ 	JOIN = contjoinsel
+ );
+ 
  CREATE OR REPLACE FUNCTION isdefined(hstore,text)
  RETURNS bool
! AS 'MODULE_PATHNAME','hstore_defined'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION defined(hstore,text)
  RETURNS bool
! AS 'MODULE_PATHNAME','hstore_defined'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION delete(hstore,text)
  RETURNS hstore
! AS 'MODULE_PATHNAME','hstore_delete'
! LANGUAGE C STRICT IMMUTABLE;
! 
! CREATE OR REPLACE FUNCTION delete(hstore,text[])
! RETURNS hstore
! AS 'MODULE_PATHNAME','hstore_delete_array'
! LANGUAGE C STRICT IMMUTABLE;
! 
! CREATE OR REPLACE FUNCTION delete(hstore,hstore)
! RETURNS hstore
! AS 'MODULE_PATHNAME','hstore_delete_hstore'
  LANGUAGE C STRICT IMMUTABLE;
  
+ CREATE OPERATOR - (
+ 	LEFTARG = hstore,
+ 	RIGHTARG = text,
+ 	PROCEDURE = delete
+ );
+ 
+ CREATE OPERATOR - (
+ 	LEFTARG = hstore,
+ 	RIGHTARG = text[],
+ 	PROCEDURE = delete
+ );
+ 
+ CREATE OPERATOR - (
+ 	LEFTARG = hstore,
+ 	RIGHTARG = hstore,
+ 	PROCEDURE = delete
+ );
+ 
  CREATE OR REPLACE FUNCTION hs_concat(hstore,hstore)
  RETURNS hstore
! AS 'MODULE_PATHNAME','hstore_concat'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OPERATOR || (
***************
*** 79,90 ****
  
  CREATE OR REPLACE FUNCTION hs_contains(hstore,hstore)
  RETURNS bool
! AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION hs_contained(hstore,hstore)
  RETURNS bool
! AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OPERATOR @> (
--- 167,178 ----
  
  CREATE OR REPLACE FUNCTION hs_contains(hstore,hstore)
  RETURNS bool
! AS 'MODULE_PATHNAME','hstore_contains'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION hs_contained(hstore,hstore)
  RETURNS bool
! AS 'MODULE_PATHNAME','hstore_contained'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OPERATOR @> (
***************
*** 126,168 ****
  
  CREATE OR REPLACE FUNCTION tconvert(text,text)
  RETURNS hstore
! AS 'MODULE_PATHNAME'
! LANGUAGE C IMMUTABLE; -- not STRICT
  
  CREATE OPERATOR => (
  	LEFTARG = text,
  	RIGHTARG = text,
! 	PROCEDURE = tconvert
  );
  
  CREATE OR REPLACE FUNCTION akeys(hstore)
  RETURNS _text
! AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION avals(hstore)
  RETURNS _text
! AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION skeys(hstore)
  RETURNS setof text
! AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION svals(hstore)
  RETURNS setof text
! AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION each(IN hs hstore,
      OUT key text,
      OUT value text)
  RETURNS SETOF record
! AS 'MODULE_PATHNAME'
  LANGUAGE C STRICT IMMUTABLE;
  
  
  
  -- define the GiST support methods
  
--- 214,406 ----
  
  CREATE OR REPLACE FUNCTION tconvert(text,text)
  RETURNS hstore
! AS 'MODULE_PATHNAME','hstore_from_text'
! LANGUAGE C IMMUTABLE; -- not STRICT; needs to allow (key,NULL)
! 
! CREATE OR REPLACE FUNCTION hstore(text,text)
! RETURNS hstore
! AS 'MODULE_PATHNAME','hstore_from_text'
! LANGUAGE C IMMUTABLE; -- not STRICT; needs to allow (key,NULL)
  
  CREATE OPERATOR => (
  	LEFTARG = text,
  	RIGHTARG = text,
! 	PROCEDURE = hstore
! );
! 
! CREATE OR REPLACE FUNCTION hstore(text[],text[])
! RETURNS hstore
! AS 'MODULE_PATHNAME', 'hstore_from_arrays'
! LANGUAGE C IMMUTABLE; -- not STRICT; allows (keys,null)
! 
! CREATE OPERATOR => (
! 	LEFTARG = text[],
! 	RIGHTARG = text[],
! 	PROCEDURE = hstore
  );
  
+ CREATE OR REPLACE FUNCTION hstore(record)
+ RETURNS hstore
+ AS 'MODULE_PATHNAME', 'hstore_from_record'
+ LANGUAGE C IMMUTABLE; -- not STRICT; allows (null::recordtype)
+ 
  CREATE OR REPLACE FUNCTION akeys(hstore)
  RETURNS _text
! AS 'MODULE_PATHNAME','hstore_akeys'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION avals(hstore)
  RETURNS _text
! AS 'MODULE_PATHNAME','hstore_avals'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION skeys(hstore)
  RETURNS setof text
! AS 'MODULE_PATHNAME','hstore_skeys'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION svals(hstore)
  RETURNS setof text
! AS 'MODULE_PATHNAME','hstore_svals'
  LANGUAGE C STRICT IMMUTABLE;
  
  CREATE OR REPLACE FUNCTION each(IN hs hstore,
      OUT key text,
      OUT value text)
  RETURNS SETOF record
! AS 'MODULE_PATHNAME','hstore_each'
  LANGUAGE C STRICT IMMUTABLE;
  
+ CREATE OR REPLACE FUNCTION populate_record(anyelement,hstore)
+ RETURNS anyelement
+ AS 'MODULE_PATHNAME', 'hstore_populate_record'
+ LANGUAGE C IMMUTABLE; -- not STRICT; allows (null::rectype,hstore)
  
+ CREATE OPERATOR #= (
+ 	LEFTARG = anyelement,
+ 	RIGHTARG = hstore,
+ 	PROCEDURE = populate_record
+ );
+ 
+ -- btree
+ 
+ CREATE OR REPLACE FUNCTION hstore_eq(hstore,hstore)
+ RETURNS boolean
+ AS 'MODULE_PATHNAME','hstore_eq'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION hstore_ne(hstore,hstore)
+ RETURNS boolean
+ AS 'MODULE_PATHNAME','hstore_ne'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION hstore_gt(hstore,hstore)
+ RETURNS boolean
+ AS 'MODULE_PATHNAME','hstore_gt'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION hstore_ge(hstore,hstore)
+ RETURNS boolean
+ AS 'MODULE_PATHNAME','hstore_ge'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION hstore_lt(hstore,hstore)
+ RETURNS boolean
+ AS 'MODULE_PATHNAME','hstore_lt'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION hstore_le(hstore,hstore)
+ RETURNS boolean
+ AS 'MODULE_PATHNAME','hstore_le'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION hstore_cmp(hstore,hstore)
+ RETURNS integer
+ AS 'MODULE_PATHNAME','hstore_cmp'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OPERATOR = (
+        LEFTARG = hstore,
+        RIGHTARG = hstore,
+        PROCEDURE = hstore_eq,
+        COMMUTATOR = =,
+        NEGATOR = <>,
+        RESTRICT = eqsel,
+        JOIN = eqjoinsel,
+        MERGES,
+        HASHES
+ );
+ CREATE OPERATOR <> (
+        LEFTARG = hstore,
+        RIGHTARG = hstore,
+        PROCEDURE = hstore_ne,
+        COMMUTATOR = <>,
+        NEGATOR = =,
+        RESTRICT = neqsel,
+        JOIN = neqjoinsel
+ );
+ 
+ -- the comparison operators have funky names (and are undocumented)
+ -- in an attempt to discourage anyone from actually using them. they
+ -- only exist to support the btree opclass
+ 
+ CREATE OPERATOR #<# (
+        LEFTARG = hstore,
+        RIGHTARG = hstore,
+        PROCEDURE = hstore_lt,
+        COMMUTATOR = #>#,
+        NEGATOR = #>=#,
+        RESTRICT = scalarltsel,
+        JOIN = scalarltjoinsel
+ );
+ CREATE OPERATOR #<=# (
+        LEFTARG = hstore,
+        RIGHTARG = hstore,
+        PROCEDURE = hstore_le,
+        COMMUTATOR = #>=#,
+        NEGATOR = #>#,
+        RESTRICT = scalarltsel,
+        JOIN = scalarltjoinsel
+ );
+ CREATE OPERATOR #># (
+        LEFTARG = hstore,
+        RIGHTARG = hstore,
+        PROCEDURE = hstore_gt,
+        COMMUTATOR = #<#,
+        NEGATOR = #<=#,
+        RESTRICT = scalargtsel,
+        JOIN = scalargtjoinsel
+ );
+ CREATE OPERATOR #>=# (
+        LEFTARG = hstore,
+        RIGHTARG = hstore,
+        PROCEDURE = hstore_ge,
+        COMMUTATOR = #<=#,
+        NEGATOR = #<#,
+        RESTRICT = scalargtsel,
+        JOIN = scalargtjoinsel
+ );
+ 
+ CREATE OPERATOR CLASS btree_hstore_ops
+ DEFAULT FOR TYPE hstore USING btree
+ AS
+ 	OPERATOR	1	#<# ,
+ 	OPERATOR	2	#<=# ,
+ 	OPERATOR	3	= ,
+ 	OPERATOR	4	#>=# ,
+ 	OPERATOR	5	#># ,
+ 	FUNCTION	1	hstore_cmp(hstore,hstore);
+ 
+ CREATE OR REPLACE FUNCTION hstore_hash(hstore)
+ RETURNS integer
+ AS 'MODULE_PATHNAME','hstore_hash'
+ LANGUAGE C STRICT IMMUTABLE;
+ 
+ CREATE OPERATOR CLASS hash_hstore_ops
+ DEFAULT FOR TYPE hstore USING hash
+ AS
+ 	OPERATOR	1	= ,
+ 	FUNCTION	1	hstore_hash(hstore);
  
  -- define the GiST support methods
  
***************
*** 194,200 ****
  AS 'MODULE_PATHNAME'
  LANGUAGE C IMMUTABLE STRICT;
  
! CREATE OR REPLACE FUNCTION ghstore_penalty(internal,internal,internal)
  RETURNS internal
  AS 'MODULE_PATHNAME'
  LANGUAGE C IMMUTABLE STRICT;
--- 432,438 ----
  AS 'MODULE_PATHNAME'
  LANGUAGE C IMMUTABLE STRICT;
  
! CREATE OR REPLACE FUNCTION ghstore_penalty(internal, internal, internal)
  RETURNS internal
  AS 'MODULE_PATHNAME'
  LANGUAGE C IMMUTABLE STRICT;
***************
*** 214,220 ****
  AS 'MODULE_PATHNAME'
  LANGUAGE C IMMUTABLE STRICT;
  
! CREATE OR REPLACE FUNCTION ghstore_consistent(internal,internal,int,oid,internal)
  RETURNS bool
  AS 'MODULE_PATHNAME'
  LANGUAGE C IMMUTABLE STRICT;
--- 452,458 ----
  AS 'MODULE_PATHNAME'
  LANGUAGE C IMMUTABLE STRICT;
  
! CREATE OR REPLACE FUNCTION ghstore_consistent(internal, internal, int, oid, internal)
  RETURNS bool
  AS 'MODULE_PATHNAME'
  LANGUAGE C IMMUTABLE STRICT;
***************
*** 225,230 ****
--- 463,470 ----
  AS
         	OPERATOR        7       @> ,
         	OPERATOR        9       ?(hstore,text) ,
+ 	OPERATOR        10      ?|(hstore,text[]),
+ 	OPERATOR        11      ?&(hstore,text[]),
          --OPERATOR        8       <@ ,
          OPERATOR        13      @ ,
          --OPERATOR        14      ~ ,
***************
*** 259,266 ****
  AS
  	OPERATOR        7       @> ,
  	OPERATOR        9       ?(hstore,text),
  	FUNCTION        1       bttextcmp(text,text),
  	FUNCTION        2       gin_extract_hstore(internal, internal),
  	FUNCTION        3       gin_extract_hstore_query(internal, internal, int2, internal, internal),
  	FUNCTION        4       gin_consistent_hstore(internal, int2, internal, int4, internal, internal),
! STORAGE         text;
--- 499,510 ----
  AS
  	OPERATOR        7       @> ,
  	OPERATOR        9       ?(hstore,text),
+ 	OPERATOR        10      ?|(hstore,text[]),
+ 	OPERATOR        11      ?&(hstore,text[]),
  	FUNCTION        1       bttextcmp(text,text),
  	FUNCTION        2       gin_extract_hstore(internal, internal),
  	FUNCTION        3       gin_extract_hstore_query(internal, internal, int2, internal, internal),
  	FUNCTION        4       gin_consistent_hstore(internal, int2, internal, int4, internal, internal),
! 	STORAGE         text;
! 
! --
Index: contrib/hstore/hstore_gin.c
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/hstore/hstore_gin.c,v
retrieving revision 1.6
diff -c -r1.6 hstore_gin.c
*** contrib/hstore/hstore_gin.c	11 Jun 2009 14:48:51 -0000	1.6
--- contrib/hstore/hstore_gin.c	8 Jul 2009 18:38:57 -0000
***************
*** 4,9 ****
--- 4,10 ----
  #include "postgres.h"
  
  #include "access/gin.h"
+ #include "catalog/pg_type.h"
  
  #include "hstore.h"
  
***************
*** 35,77 ****
  	HStore	   *hs = PG_GETARG_HS(0);
  	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
  	Datum	   *entries = NULL;
  
! 	*nentries = 2 * hs->size;
! 
! 	if (hs->size > 0)
  	{
! 		HEntry	   *ptr = ARRPTR(hs);
! 		char	   *words = STRPTR(hs);
! 		int			i = 0;
  
! 		entries = (Datum *) palloc(sizeof(Datum) * 2 * hs->size);
  
! 		while (ptr - ARRPTR(hs) < hs->size)
  		{
! 			text	   *item;
! 
! 			item = makeitem(words + ptr->pos, ptr->keylen);
! 			*VARDATA(item) = KEYFLAG;
! 			entries[i++] = PointerGetDatum(item);
! 
! 			if (ptr->valisnull)
! 			{
! 				item = makeitem(NULL, 0);
! 				*VARDATA(item) = NULLFLAG;
! 
! 			}
! 			else
! 			{
! 				item = makeitem(words + ptr->pos + ptr->keylen, ptr->vallen);
! 				*VARDATA(item) = VALFLAG;
! 			}
! 			entries[i++] = PointerGetDatum(item);
! 
! 			ptr++;
  		}
  	}
  
- 	PG_FREE_IF_COPY(hs, 0);
  	PG_RETURN_POINTER(entries);
  }
  
--- 36,71 ----
  	HStore	   *hs = PG_GETARG_HS(0);
  	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
  	Datum	   *entries = NULL;
+ 	HEntry     *hsent = ARRPTR(hs);
+ 	char       *ptr = STRPTR(hs);
+ 	int        count = hs->size;
+ 	int        i;
+ 
+ 	*nentries = 2 * count;
+ 	if (count)
+ 		entries = (Datum *) palloc(sizeof(Datum) * 2 * count);
  
! 	for (i = 0; i < count; ++i)
  	{
! 		text	   *item;
  
! 		item = makeitem(HS_KEY(hsent,ptr,i), HS_KEYLEN(hsent,i));
! 		*VARDATA(item) = KEYFLAG;
! 		entries[2*i] = PointerGetDatum(item);
  
! 		if (HS_VALISNULL(hsent,i))
  		{
! 			item = makeitem(NULL, 0);
! 			*VARDATA(item) = NULLFLAG;
! 		}
! 		else
! 		{
! 			item = makeitem(HS_VAL(hsent,ptr,i), HS_VALLEN(hsent,i));
! 			*VARDATA(item) = VALFLAG;
  		}
+ 		entries[2*i+1] = PointerGetDatum(item);
  	}
  
  	PG_RETURN_POINTER(entries);
  }
  
***************
*** 85,92 ****
  
  	if (strategy == HStoreContainsStrategyNumber)
  	{
! 		PG_RETURN_DATUM(DirectFunctionCall2(
! 											gin_extract_hstore,
  											PG_GETARG_DATUM(0),
  											PG_GETARG_DATUM(1)
  											));
--- 79,85 ----
  
  	if (strategy == HStoreContainsStrategyNumber)
  	{
! 		PG_RETURN_DATUM(DirectFunctionCall2(gin_extract_hstore,
  											PG_GETARG_DATUM(0),
  											PG_GETARG_DATUM(1)
  											));
***************
*** 94,112 ****
  	else if (strategy == HStoreExistsStrategyNumber)
  	{
  		text	   *item,
! 				   *q = PG_GETARG_TEXT_P(0);
  		int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
  		Datum	   *entries = NULL;
  
  		*nentries = 1;
  		entries = (Datum *) palloc(sizeof(Datum));
  
! 		item = makeitem(VARDATA(q), VARSIZE(q) - VARHDRSZ);
  		*VARDATA(item) = KEYFLAG;
  		entries[0] = PointerGetDatum(item);
  
  		PG_RETURN_POINTER(entries);
  	}
  	else
  		elog(ERROR, "Unsupported strategy number: %d", strategy);
  
--- 87,136 ----
  	else if (strategy == HStoreExistsStrategyNumber)
  	{
  		text	   *item,
! 				   *query = PG_GETARG_TEXT_PP(0);
  		int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
  		Datum	   *entries = NULL;
  
  		*nentries = 1;
  		entries = (Datum *) palloc(sizeof(Datum));
  
! 		item = makeitem(VARDATA_ANY(query), VARSIZE_ANY_EXHDR(query));
  		*VARDATA(item) = KEYFLAG;
  		entries[0] = PointerGetDatum(item);
  
  		PG_RETURN_POINTER(entries);
  	}
+ 	else if (strategy == HStoreExistsAnyStrategyNumber
+ 			 || strategy == HStoreExistsAllStrategyNumber)
+ 	{
+ 		ArrayType   *query = PG_GETARG_ARRAYTYPE_P(0);
+ 		Datum      *key_datums;
+ 		bool       *key_nulls;
+ 		int        key_count;
+ 		int        i,j;
+ 		int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
+ 		Datum	   *entries = NULL;
+ 		text       *item;
+ 
+ 		deconstruct_array(query,
+ 						  TEXTOID, -1, false, 'i',
+ 						  &key_datums, &key_nulls, &key_count);
+ 
+ 		entries = (Datum *) palloc(sizeof(Datum) * key_count);
+ 
+ 		for (i = 0, j = 0; i < key_count; ++i)
+ 		{
+ 			if (key_nulls[i])
+ 				continue;
+ 			item = makeitem(VARDATA(key_datums[i]), VARSIZE(key_datums[i]) - VARHDRSZ);
+ 			*VARDATA(item) = KEYFLAG;
+ 			entries[j++] = PointerGetDatum(item);
+ 		}
+ 
+ 		*nentries = j ? j : -1;
+ 
+ 		PG_RETURN_POINTER(entries);
+ 	}
  	else
  		elog(ERROR, "Unsupported strategy number: %d", strategy);
  
***************
*** 121,129 ****
  {
  	bool	   *check = (bool *) PG_GETARG_POINTER(0);
  	StrategyNumber strategy = PG_GETARG_UINT16(1);
! 	HStore	   *query = PG_GETARG_HS(2);
! 
! 	/* int32	nkeys = PG_GETARG_INT32(3); */
  	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
  	bool	   *recheck = (bool *) PG_GETARG_POINTER(5);
  	bool		res = true;
--- 145,151 ----
  {
  	bool	   *check = (bool *) PG_GETARG_POINTER(0);
  	StrategyNumber strategy = PG_GETARG_UINT16(1);
! 	int32		nkeys = PG_GETARG_INT32(3);
  	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
  	bool	   *recheck = (bool *) PG_GETARG_POINTER(5);
  	bool		res = true;
***************
*** 137,143 ****
  		 * we need recheck
  		 */
  		*recheck = true;
! 		for (i = 0; res && i < 2 * query->size; i++)
  			if (check[i] == false)
  				res = false;
  	}
--- 159,165 ----
  		 * we need recheck
  		 */
  		*recheck = true;
! 		for (i = 0; res && i < nkeys; i++)
  			if (check[i] == false)
  				res = false;
  	}
***************
*** 147,152 ****
--- 169,191 ----
  		*recheck = false;
  		res = true;
  	}
+ 	else if (strategy == HStoreExistsAnyStrategyNumber)
+ 	{
+ 		/* Existence of key is guaranteed */
+ 		*recheck = false;
+ 		res = true;
+ 	}
+ 	else if (strategy == HStoreExistsAllStrategyNumber)
+ 	{
+ 		int        i;
+ 
+ 		for (i = 0; res && i < nkeys; ++i)
+ 			if (!check[i])
+ 				res = false;
+ 
+ 		/* Existence of key is guaranteed */
+ 		*recheck = false;
+ 	}
  	else
  		elog(ERROR, "Unsupported strategy number: %d", strategy);
  
Index: contrib/hstore/hstore_gist.c
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/hstore/hstore_gist.c,v
retrieving revision 1.10
diff -c -r1.10 hstore_gist.c
*** contrib/hstore/hstore_gist.c	11 Jun 2009 14:48:51 -0000	1.10
--- contrib/hstore/hstore_gist.c	8 Jul 2009 18:38:58 -0000
***************
*** 3,8 ****
--- 3,9 ----
   */
  #include "postgres.h"
  
+ #include "catalog/pg_type.h"
  #include "access/gist.h"
  #include "access/itup.h"
  #include "access/skey.h"
***************
*** 114,143 ****
  	if (entry->leafkey)
  	{
  		GISTTYPE   *res = (GISTTYPE *) palloc0(CALCGTSIZE(0));
- 		HStore	   *toastedval = (HStore *) DatumGetPointer(entry->key);
  		HStore	   *val = (HStore *) DatumGetPointer(PG_DETOAST_DATUM(entry->key));
! 		HEntry	   *ptr = ARRPTR(val);
! 		char	   *words = STRPTR(val);
  
  		SET_VARSIZE(res, CALCGTSIZE(0));
  
! 		while (ptr - ARRPTR(val) < val->size)
  		{
! 			int			h;
  
! 			h = crc32_sz((char *) (words + ptr->pos), ptr->keylen);
  			HASH(GETSIGN(res), h);
! 			if (!ptr->valisnull)
  			{
! 				h = crc32_sz((char *) (words + ptr->pos + ptr->keylen), ptr->vallen);
  				HASH(GETSIGN(res), h);
  			}
- 			ptr++;
  		}
  
- 		if (val != toastedval)
- 			pfree(val);
- 
  		retval = (GISTENTRY *) palloc(sizeof(GISTENTRY));
  		gistentryinit(*retval, PointerGetDatum(res),
  					  entry->rel, entry->page,
--- 115,141 ----
  	if (entry->leafkey)
  	{
  		GISTTYPE   *res = (GISTTYPE *) palloc0(CALCGTSIZE(0));
  		HStore	   *val = (HStore *) DatumGetPointer(PG_DETOAST_DATUM(entry->key));
! 		HEntry	   *hsent = ARRPTR(val);
! 		char	   *ptr = STRPTR(val);
! 		int        count = val->size;
! 		int        i;
  
  		SET_VARSIZE(res, CALCGTSIZE(0));
  
! 		for (i = 0; i < count; ++i)
  		{
! 			int	h;
  
! 			h = crc32_sz((char *) HS_KEY(hsent,ptr,i), HS_KEYLEN(hsent,i));
  			HASH(GETSIGN(res), h);
! 			if (!HS_VALISNULL(hsent,i))
  			{
! 				h = crc32_sz((char *) HS_VAL(hsent,ptr,i), HS_VALLEN(hsent,i));
  				HASH(GETSIGN(res), h);
  			}
  		}
  
  		retval = (GISTENTRY *) palloc(sizeof(GISTENTRY));
  		gistentryinit(*retval, PointerGetDatum(res),
  					  entry->rel, entry->page,
***************
*** 500,506 ****
  	}
  
  	*right = *left = FirstOffsetNumber;
- 	pfree(costvector);
  
  	v->spl_ldatum = PointerGetDatum(datum_l);
  	v->spl_rdatum = PointerGetDatum(datum_r);
--- 498,503 ----
***************
*** 533,564 ****
  		HStore	   *query = PG_GETARG_HS(1);
  		HEntry	   *qe = ARRPTR(query);
  		char	   *qv = STRPTR(query);
  
! 		while (res && qe - ARRPTR(query) < query->size)
  		{
! 			int			crc = crc32_sz((char *) (qv + qe->pos), qe->keylen);
  
  			if (GETBIT(sign, HASHVAL(crc)))
  			{
! 				if (!qe->valisnull)
  				{
! 					crc = crc32_sz((char *) (qv + qe->pos + qe->keylen), qe->vallen);
  					if (!GETBIT(sign, HASHVAL(crc)))
  						res = false;
  				}
  			}
  			else
  				res = false;
- 			qe++;
  		}
  	}
  	else if (strategy == HStoreExistsStrategyNumber)
  	{
! 		text	   *query = PG_GETARG_TEXT_P(1);
! 		int			crc = crc32_sz(VARDATA(query), VARSIZE(query) - VARHDRSZ);
  
  		res = (GETBIT(sign, HASHVAL(crc))) ? true : false;
  	}
  	else
  		elog(ERROR, "Unsupported strategy number: %d", strategy);
  
--- 530,608 ----
  		HStore	   *query = PG_GETARG_HS(1);
  		HEntry	   *qe = ARRPTR(query);
  		char	   *qv = STRPTR(query);
+ 		int        count = query->size;
+ 		int        i;
  
! 		for (i = 0; res && i < count; ++i)
  		{
! 			int	crc = crc32_sz((char *) HS_KEY(qe,qv,i), HS_KEYLEN(qe,i));
  
  			if (GETBIT(sign, HASHVAL(crc)))
  			{
! 				if (!HS_VALISNULL(qe,i))
  				{
! 					crc = crc32_sz((char *) HS_VAL(qe,qv,i), HS_VALLEN(qe,i));
  					if (!GETBIT(sign, HASHVAL(crc)))
  						res = false;
  				}
  			}
  			else
  				res = false;
  		}
  	}
  	else if (strategy == HStoreExistsStrategyNumber)
  	{
! 		text	   *query = PG_GETARG_TEXT_PP(1);
! 		int			crc = crc32_sz(VARDATA_ANY(query), VARSIZE_ANY_EXHDR(query));
  
  		res = (GETBIT(sign, HASHVAL(crc))) ? true : false;
  	}
+ 	else if (strategy == HStoreExistsAllStrategyNumber)
+ 	{
+ 		ArrayType   *query = PG_GETARG_ARRAYTYPE_P(1);
+ 		Datum      *key_datums;
+ 		bool       *key_nulls;
+ 		int        key_count;
+ 		int        i;
+ 
+ 		deconstruct_array(query,
+ 						  TEXTOID, -1, false, 'i',
+ 						  &key_datums, &key_nulls, &key_count);
+ 
+ 		for (i = 0; res && i < key_count; ++i)
+ 		{
+ 			int	crc;
+ 			if (key_nulls[i])
+ 				continue;
+ 			crc = crc32_sz(VARDATA(key_datums[i]), VARSIZE(key_datums[i]) - VARHDRSZ);
+ 			if (!(GETBIT(sign, HASHVAL(crc))))
+ 				res = FALSE;
+ 		}
+ 	}
+ 	else if (strategy == HStoreExistsAnyStrategyNumber)
+ 	{
+ 		ArrayType   *query = PG_GETARG_ARRAYTYPE_P(1);
+ 		Datum      *key_datums;
+ 		bool       *key_nulls;
+ 		int        key_count;
+ 		int        i;
+ 
+ 		deconstruct_array(query,
+ 						  TEXTOID, -1, false, 'i',
+ 						  &key_datums, &key_nulls, &key_count);
+ 
+ 		res = FALSE;
+ 
+ 		for (i = 0; !res && i < key_count; ++i)
+ 		{
+ 			int	crc;
+ 			if (key_nulls[i])
+ 				continue;
+ 			crc = crc32_sz(VARDATA(key_datums[i]), VARSIZE(key_datums[i]) - VARHDRSZ);
+ 			if (GETBIT(sign, HASHVAL(crc)))
+ 				res = TRUE;
+ 		}
+ 	}
  	else
  		elog(ERROR, "Unsupported strategy number: %d", strategy);
  
Index: contrib/hstore/hstore_io.c
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/hstore/hstore_io.c,v
retrieving revision 1.11
diff -c -r1.11 hstore_io.c
*** contrib/hstore/hstore_io.c	11 Jun 2009 14:48:51 -0000	1.11
--- contrib/hstore/hstore_io.c	8 Jul 2009 18:38:59 -0000
***************
*** 2,7 ****
--- 2,14 ----
   * $PostgreSQL: pgsql/contrib/hstore/hstore_io.c,v 1.11 2009/06/11 14:48:51 momjian Exp $
   */
  #include "postgres.h"
+ #include "catalog/pg_type.h"
+ #include "utils/lsyscache.h"
+ #include "utils/typcache.h"
+ #include "access/htup.h"
+ #include "access/heapam.h"
+ #include "libpq/pqformat.h"
+ #include "funcapi.h"
  
  #include <ctype.h>
  
***************
*** 9,14 ****
--- 16,26 ----
  
  PG_MODULE_MAGIC;
  
+ #if HSTORE_POLLUTE_NAMESPACE
+ HSTORE_POLLUTE(hstore_from_text,tconvert)
+ #endif
+ 
+ 
  typedef struct
  {
  	char	   *begin;
***************
*** 263,269 ****
  	}
  }
  
! int
  comparePairs(const void *a, const void *b)
  {
  	if (((Pairs *) a)->keylen == ((Pairs *) b)->keylen)
--- 275,281 ----
  	}
  }
  
! static int
  comparePairs(const void *a, const void *b)
  {
  	if (((Pairs *) a)->keylen == ((Pairs *) b)->keylen)
***************
*** 286,293 ****
  	return (((Pairs *) a)->keylen > ((Pairs *) b)->keylen) ? 1 : -1;
  }
  
  int
! uniquePairs(Pairs *a, int4 l, int4 *buflen)
  {
  	Pairs	   *ptr,
  			   *res;
--- 298,311 ----
  	return (((Pairs *) a)->keylen > ((Pairs *) b)->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,
  			   *res;
***************
*** 305,311 ****
  	res = a;
  	while (ptr - a < l)
  	{
! 		if (ptr->keylen == res->keylen && strncmp(ptr->key, res->key, res->keylen) == 0)
  		{
  			if (ptr->needfree)
  			{
--- 323,330 ----
  	res = a;
  	while (ptr - a < l)
  	{
! 		if (ptr->keylen == res->keylen
! 			&& strncmp(ptr->key, res->key, res->keylen) == 0)
  		{
  			if (ptr->needfree)
  			{
***************
*** 327,332 ****
--- 346,352 ----
  	return res + 1 - a;
  }
  
+ #if 0   /* this superfluous code was >15% of hstore_in's runtime. */
  static void
  freeHSParse(HSParser *state)
  {
***************
*** 344,349 ****
--- 364,370 ----
  		}
  	pfree(state->pairs);
  }
+ #endif
  
  size_t
  hstoreCheckKeyLen(size_t len)
***************
*** 366,430 ****
  }
  
  
  PG_FUNCTION_INFO_V1(hstore_in);
  Datum		hstore_in(PG_FUNCTION_ARGS);
  Datum
  hstore_in(PG_FUNCTION_ARGS)
  {
  	HSParser	state;
! 	int4		len,
! 				buflen,
! 				i;
  	HStore	   *out;
- 	HEntry	   *entries;
- 	char	   *ptr;
  
  	state.begin = PG_GETARG_CSTRING(0);
  
  	parse_hstore(&state);
  
! 	if (state.pcur == 0)
  	{
! 		freeHSParse(&state);
! 		len = CALCDATASIZE(0, 0);
! 		out = palloc(len);
! 		SET_VARSIZE(out, len);
! 		out->size = 0;
  		PG_RETURN_POINTER(out);
  	}
  
! 	state.pcur = uniquePairs(state.pairs, state.pcur, &buflen);
  
! 	len = CALCDATASIZE(state.pcur, buflen);
! 	out = palloc(len);
! 	SET_VARSIZE(out, len);
! 	out->size = state.pcur;
  
- 	entries = ARRPTR(out);
- 	ptr = STRPTR(out);
  
! 	for (i = 0; i < out->size; i++)
  	{
! 		entries[i].keylen = state.pairs[i].keylen;
! 		entries[i].pos = ptr - STRPTR(out);
! 		memcpy(ptr, state.pairs[i].key, state.pairs[i].keylen);
! 		ptr += entries[i].keylen;
  
! 		entries[i].valisnull = state.pairs[i].isnull;
! 		if (entries[i].valisnull)
! 			entries[i].vallen = 4;		/* null */
  		else
  		{
! 			entries[i].vallen = state.pairs[i].vallen;
! 			memcpy(ptr, state.pairs[i].val, state.pairs[i].vallen);
! 			ptr += entries[i].vallen;
  		}
  	}
  
! 	freeHSParse(&state);
  	PG_RETURN_POINTER(out);
  }
  
  static char *
  cpw(char *dst, char *src, int len)
  {
--- 387,993 ----
  }
  
  
+ HStore *
+ hstorePairs(Pairs *pairs, int4 pcount, int4 buflen)
+ {
+ 	HStore     *out;
+ 	HEntry	   *entry;
+ 	char	   *ptr;
+ 	char	   *buf;
+ 	int4       len;
+ 	int4       i;
+ 
+ 	len = CALCDATASIZE(pcount, buflen);
+ 	out = palloc(len);
+ 	SET_VARSIZE(out, len);
+ 	out->size = pcount;
+ 
+ 	if (pcount == 0)
+ 		return out;
+ 
+ 	entry = ARRPTR(out);
+ 	buf = ptr = STRPTR(out);
+ 
+ 	for (i = 0; i < pcount; i++)
+ 		HS_ADDITEM(entry,buf,ptr,pairs[i]);
+ 
+ 	HS_FINALIZE(out,pcount,buf,ptr);
+ 
+ 	return out;
+ }
+ 
+ 
  PG_FUNCTION_INFO_V1(hstore_in);
  Datum		hstore_in(PG_FUNCTION_ARGS);
  Datum
  hstore_in(PG_FUNCTION_ARGS)
  {
  	HSParser	state;
! 	int4		buflen;
  	HStore	   *out;
  
  	state.begin = PG_GETARG_CSTRING(0);
  
  	parse_hstore(&state);
  
! 	state.pcur = hstoreUniquePairs(state.pairs, state.pcur, &buflen);
! 
! 	out = hstorePairs(state.pairs, state.pcur, buflen);
! 
! #if 0  /* see comment above */
! 	freeHSParse(&state);
! #endif
! 
! 	PG_RETURN_POINTER(out);
! }
! 
! 
! PG_FUNCTION_INFO_V1(hstore_recv);
! Datum		hstore_recv(PG_FUNCTION_ARGS);
! Datum
! hstore_recv(PG_FUNCTION_ARGS)
! {
! 	int4		buflen;
! 	HStore	   *out;
! 	Pairs	   *pairs;
! 	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);
  	}
  
! 	pairs = palloc(pcount * sizeof(Pairs));
  
! 	for (i = 0; i < pcount; ++i)
! 	{
! 		int rawlen = pq_getmsgint(buf, 4);
! 		int len;
! 
! 		if (rawlen < 0)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
! 					 errmsg("null value not allowed for hstore key")));
! 
! 		pairs[i].key = pq_getmsgtext(buf, rawlen, &len);
! 		pairs[i].keylen = hstoreCheckKeyLen(len);
! 		pairs[i].needfree = true;
! 
! 		rawlen = pq_getmsgint(buf, 4);
! 		if (rawlen < 0)
! 		{
! 			pairs[i].val = NULL;
! 			pairs[i].vallen = 0;
! 			pairs[i].isnull = true;
! 		}
! 		else
! 		{
! 			pairs[i].val = pq_getmsgtext(buf, rawlen, &len);
! 			pairs[i].vallen = hstoreCheckValLen(len);
! 			pairs[i].isnull = false;
! 		}
! 	}
! 
! 	pcount = hstoreUniquePairs(pairs, pcount, &buflen);
! 
! 	out = hstorePairs(pairs, pcount, buflen);
! 
! 	PG_RETURN_POINTER(out);
! }
  
  
! PG_FUNCTION_INFO_V1(hstore_from_text);
! Datum		hstore_from_text(PG_FUNCTION_ARGS);
! Datum
! hstore_from_text(PG_FUNCTION_ARGS)
! {
! 	text       *key;
! 	text       *val = NULL;
! 	Pairs      p;
! 	HStore	   *out;
! 
! 	if (PG_ARGISNULL(0))
! 		PG_RETURN_NULL();
! 
! 	p.needfree = false;
! 	key = PG_GETARG_TEXT_PP(0);
! 	p.key = VARDATA_ANY(key);
! 	p.keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key));
! 
! 	if (PG_ARGISNULL(1))
  	{
! 		p.vallen = 0;
! 		p.isnull = true;
! 	}
! 	else
! 	{
! 		val = PG_GETARG_TEXT_PP(1);
! 		p.val = VARDATA_ANY(val);
! 		p.vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(val));
! 		p.isnull = false;
! 	}
! 
! 	out = hstorePairs(&p, 1, p.keylen + p.vallen);
! 
! 	PG_RETURN_POINTER(out);
! }
! 
! 
! PG_FUNCTION_INFO_V1(hstore_from_arrays);
! Datum		hstore_from_arrays(PG_FUNCTION_ARGS);
! Datum
! hstore_from_arrays(PG_FUNCTION_ARGS)
! {
! 	int4		buflen;
! 	HStore	   *out;
! 	Pairs	   *pairs;
! 	Datum	   *key_datums;
! 	bool	   *key_nulls;
! 	int		   key_count;
! 	Datum	   *value_datums;
! 	bool	   *value_nulls;
! 	int		   value_count;
! 	ArrayType  *key_array;
! 	ArrayType  *value_array;
! 	int		   i;
! 
! 	if (PG_ARGISNULL(0))
! 		PG_RETURN_NULL();
! 
! 	key_array = PG_GETARG_ARRAYTYPE_P(0);
! 
! 	Assert(ARR_ELEMTYPE(key_array) == TEXTOID);
! 
! 	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);
! 
! 	/* 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_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("cannot construct hstore from arrays of differing bounds")));
! 
! 		deconstruct_array(value_array,
! 						  TEXTOID, -1, false, 'i',
! 						  &value_datums, &value_nulls, &value_count);
! 
! 		Assert(key_count == value_count);
! 	}
! 
! 	pairs = palloc(key_count * sizeof(Pairs));
  
! 	for (i = 0; i < key_count; ++i)
! 	{
! 		if (key_nulls[i])
! 			ereport(ERROR,
! 					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
! 					 errmsg("null value not allowed for hstore key")));
! 
! 		if (!value_nulls || value_nulls[i])
! 		{
! 			pairs[i].key = VARDATA_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);
! }
! 
! /* 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);
! Datum		hstore_from_record(PG_FUNCTION_ARGS);
! Datum
! hstore_from_record(PG_FUNCTION_ARGS)
! {
! 	HeapTupleHeader rec;
! 	int4		buflen;
! 	HStore	   *out;
! 	Pairs      *pairs;
! 	Oid			tupType;
! 	int32		tupTypmod;
! 	TupleDesc	tupdesc;
! 	HeapTupleData tuple;
! 	RecordIOData *my_extra;
! 	int			ncolumns;
! 	int			i,j;
! 	Datum	   *values;
! 	bool	   *nulls;
! 
! 	if (PG_ARGISNULL(0))
! 	{
! 		Oid     argtype = get_fn_expr_argtype(fcinfo->flinfo,0);
! 
! 		/* 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)
! 	{
! 		MemSet(my_extra, 0,
! 			   sizeof(RecordIOData) - sizeof(ColumnIOData)
! 			   + ncolumns * sizeof(ColumnIOData));
! 		my_extra->record_type = tupType;
! 		my_extra->record_typmod = tupTypmod;
! 		my_extra->ncolumns = ncolumns;
! 	}
! 
! 	pairs = palloc(ncolumns * sizeof(Pairs));
! 
! 	if (rec)
! 	{
! 		/* Build a temporary HeapTuple control structure */
! 		tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
! 		ItemPointerSetInvalid(&(tuple.t_self));
! 		tuple.t_tableOid = InvalidOid;
! 		tuple.t_data = rec;
! 
! 		values = (Datum *) palloc(ncolumns * sizeof(Datum));
! 		nulls = (bool *) palloc(ncolumns * sizeof(bool));
! 
! 		/* Break down the tuple into fields */
! 		heap_deform_tuple(&tuple, tupdesc, values, nulls);
! 	}
! 	else
! 	{
! 		values = NULL;
! 		nulls = NULL;
! 	}
! 
! 	for (i = 0, j = 0; i < ncolumns; ++i)
! 	{
! 		ColumnIOData *column_info = &my_extra->columns[i];
! 		Oid			column_type = tupdesc->attrs[i]->atttypid;
! 		char	   *value;
! 
! 		/* 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 || nulls[i])
! 		{
! 			pairs[j].val = NULL;
! 			pairs[j].vallen = 4;
! 			pairs[j].isnull = true;
! 			pairs[j].needfree = false;
! 			++j;
! 			continue;
! 		}
! 
! 		/*
! 		 * Convert the column value to text
! 		 */
! 		if (column_info->column_type != column_type)
! 		{
! 			bool typIsVarlena;
! 
! 			getTypeOutputInfo(column_type,
! 							  &column_info->typiofunc,
! 							  &typIsVarlena);
! 			fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
! 						  fcinfo->flinfo->fn_mcxt);
! 			column_info->column_type = column_type;
! 		}
! 
! 		value = OutputFunctionCall(&column_info->proc, values[i]);
! 
! 		pairs[j].val = value;
! 		pairs[j].vallen = hstoreCheckValLen(strlen(value));
! 		pairs[j].isnull = false;
! 		pairs[j].needfree = false;
! 		++j;
! 	}
! 
! 	ncolumns = hstoreUniquePairs(pairs, j, &buflen);
! 
! 	out = hstorePairs(pairs, ncolumns, buflen);
! 
! 	ReleaseTupleDesc(tupdesc);
! 
  	PG_RETURN_POINTER(out);
  }
  
+ 
+ PG_FUNCTION_INFO_V1(hstore_populate_record);
+ Datum		hstore_populate_record(PG_FUNCTION_ARGS);
+ Datum
+ hstore_populate_record(PG_FUNCTION_ARGS)
+ {
+ 	Oid         argtype = get_fn_expr_argtype(fcinfo->flinfo,0);
+ 	HStore     *hs;
+ 	HEntry     *entries;
+ 	char       *ptr;
+ 	HeapTupleHeader rec;
+ 	Oid			tupType;
+ 	int32		tupTypmod;
+ 	TupleDesc	tupdesc;
+ 	HeapTupleData tuple;
+ 	HeapTuple   rettuple;
+ 	RecordIOData *my_extra;
+ 	int         ncolumns;
+ 	int			i;
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 
+ 	if (!type_is_rowtype(argtype))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("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);
+ 	entries = ARRPTR(hs);
+ 	ptr = STRPTR(hs);
+ 
+ 	if (hs->size == 0)
+ 		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_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)
+ 	{
+ 		MemSet(my_extra, 0,
+ 			   sizeof(RecordIOData) - sizeof(ColumnIOData)
+ 			   + ncolumns * sizeof(ColumnIOData));
+ 		my_extra->record_type = tupType;
+ 		my_extra->record_typmod = tupTypmod;
+ 		my_extra->ncolumns = ncolumns;
+ 	}
+ 
+ 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
+ 	nulls = (bool *) palloc(ncolumns * sizeof(bool));
+ 
+ 	if (rec)
+ 	{
+ 		/* Break down the tuple into fields */
+ 		heap_deform_tuple(&tuple, tupdesc, values, nulls);
+ 	}
+ 	else
+ 	{
+ 		for (i = 0; i < ncolumns; ++i)
+ 		{
+ 			values[i] = (Datum) 0;
+ 			nulls[i] = true;
+ 		}
+ 	}
+ 
+ 	for (i = 0; i < ncolumns; ++i)
+ 	{
+ 		ColumnIOData *column_info = &my_extra->columns[i];
+ 		Oid			column_type = tupdesc->attrs[i]->atttypid;
+ 		char	   *value;
+ 		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)));
+ 		if (idx < 0)
+ 			continue;
+ 
+ 		if (HS_VALISNULL(entries,idx))
+ 		{
+ 			nulls[i] = true;
+ 			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;
+ 		}
+ 
+ 		vallen = HS_VALLEN(entries,idx);
+ 		value = palloc(1 + vallen);
+ 		memcpy(value, HS_VAL(entries,ptr,idx), vallen);
+ 		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)
  {
***************
*** 446,453 ****
  {
  	HStore	   *in = PG_GETARG_HS(0);
  	int			buflen,
! 				i,
! 				nnulls = 0;
  	char	   *out,
  			   *ptr;
  	char	   *base = STRPTR(in);
--- 1009,1016 ----
  {
  	HStore	   *in = PG_GETARG_HS(0);
  	int			buflen,
! 				i;
! 	int        count = in->size;
  	char	   *out,
  			   *ptr;
  	char	   *base = STRPTR(in);
***************
*** 457,485 ****
  	{
  		out = palloc(1);
  		*out = '\0';
- 		PG_FREE_IF_COPY(in, 0);
  		PG_RETURN_CSTRING(out);
  	}
  
! 	for (i = 0; i < in->size; i++)
! 		if (entries[i].valisnull)
! 			nnulls++;
! 
! 	buflen = (4 /* " */ + 2 /* => */ ) * (in->size - nnulls) +
! 		(2 /* " */ + 2 /* => */ + 4 /* NULL */ ) * nnulls +
! 		2 /* ,	*/ * (in->size - 1) +
! 		2 /* esc */ * (VARSIZE(in) - CALCDATASIZE(in->size, 0)) +
! 		1 /* \0 */ ;
  
  	out = ptr = palloc(buflen);
! 	for (i = 0; i < in->size; i++)
  	{
  		*ptr++ = '"';
! 		ptr = cpw(ptr, base + entries[i].pos, entries[i].keylen);
  		*ptr++ = '"';
  		*ptr++ = '=';
  		*ptr++ = '>';
! 		if (entries[i].valisnull)
  		{
  			*ptr++ = 'N';
  			*ptr++ = 'U';
--- 1020,1057 ----
  	{
  		out = 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 = 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';
***************
*** 489,495 ****
  		else
  		{
  			*ptr++ = '"';
! 			ptr = cpw(ptr, base + entries[i].pos + entries[i].keylen, entries[i].vallen);
  			*ptr++ = '"';
  		}
  
--- 1061,1067 ----
  		else
  		{
  			*ptr++ = '"';
! 			ptr = cpw(ptr, HS_VAL(entries,base,i), HS_VALLEN(entries,i));
  			*ptr++ = '"';
  		}
  
***************
*** 501,506 ****
  	}
  	*ptr = '\0';
  
- 	PG_FREE_IF_COPY(in, 0);
  	PG_RETURN_CSTRING(out);
  }
--- 1073,1114 ----
  	}
  	*ptr = '\0';
  
  	PG_RETURN_CSTRING(out);
  }
+ 
+ 
+ PG_FUNCTION_INFO_V1(hstore_send);
+ Datum		hstore_send(PG_FUNCTION_ARGS);
+ Datum
+ hstore_send(PG_FUNCTION_ARGS)
+ {
+ 	HStore	   *in = PG_GETARG_HS(0);
+ 	int        i;
+ 	int        count = in->size;
+ 	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));
+ }
Index: contrib/hstore/hstore_op.c
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/hstore/hstore_op.c,v
retrieving revision 1.13
diff -c -r1.13 hstore_op.c
*** contrib/hstore/hstore_op.c	11 Jun 2009 14:48:51 -0000	1.13
--- contrib/hstore/hstore_op.c	8 Jul 2009 18:39:00 -0000
***************
*** 1,498 ****
  /*
!  * $PostgreSQL
   */
  #include "postgres.h"
  
  #include "catalog/pg_type.h"
  #include "funcapi.h"
- #include "utils/array.h"
  #include "utils/builtins.h"
  
  #include "hstore.h"
  
  
! static HEntry *
! findkey(HStore *hs, char *key, int keylen)
  {
! 	HEntry	   *StopLow = ARRPTR(hs);
! 	HEntry	   *StopHigh = StopLow + hs->size;
! 	HEntry	   *StopMiddle;
! 	int			difference;
  	char	   *base = STRPTR(hs);
  
! 	while (StopLow < StopHigh)
  	{
! 		StopMiddle = StopLow + (StopHigh - StopLow) / 2;
  
! 		if (StopMiddle->keylen == keylen)
! 			difference = strncmp(base + StopMiddle->pos, key, StopMiddle->keylen);
  		else
! 			difference = (StopMiddle->keylen > keylen) ? 1 : -1;
  
  		if (difference == 0)
! 			return StopMiddle;
  		else if (difference < 0)
! 			StopLow = StopMiddle + 1;
  		else
! 			StopHigh = StopMiddle;
  	}
  
! 	return NULL;
  }
  
! PG_FUNCTION_INFO_V1(fetchval);
! Datum		fetchval(PG_FUNCTION_ARGS);
  Datum
! fetchval(PG_FUNCTION_ARGS)
  {
  	HStore	   *hs = PG_GETARG_HS(0);
! 	text	   *key = PG_GETARG_TEXT_P(1);
! 	HEntry	   *entry;
  	text	   *out;
  
! 	if ((entry = findkey(hs, VARDATA(key), VARSIZE(key) - VARHDRSZ)) == NULL || entry->valisnull)
! 	{
! 		PG_FREE_IF_COPY(hs, 0);
! 		PG_FREE_IF_COPY(key, 1);
  		PG_RETURN_NULL();
- 	}
  
! 	out = cstring_to_text_with_len(STRPTR(hs) + entry->pos + entry->keylen,
! 								   entry->vallen);
  
- 	PG_FREE_IF_COPY(hs, 0);
- 	PG_FREE_IF_COPY(key, 1);
  	PG_RETURN_TEXT_P(out);
  }
  
! PG_FUNCTION_INFO_V1(exists);
! Datum		exists(PG_FUNCTION_ARGS);
  Datum
! exists(PG_FUNCTION_ARGS)
  {
  	HStore	   *hs = PG_GETARG_HS(0);
! 	text	   *key = PG_GETARG_TEXT_P(1);
! 	HEntry	   *entry;
  
! 	entry = findkey(hs, VARDATA(key), VARSIZE(key) - VARHDRSZ);
  
! 	PG_FREE_IF_COPY(hs, 0);
! 	PG_FREE_IF_COPY(key, 1);
  
! 	PG_RETURN_BOOL(entry);
  }
  
! PG_FUNCTION_INFO_V1(defined);
! Datum		defined(PG_FUNCTION_ARGS);
  Datum
! defined(PG_FUNCTION_ARGS)
  {
  	HStore	   *hs = PG_GETARG_HS(0);
! 	text	   *key = PG_GETARG_TEXT_P(1);
! 	HEntry	   *entry;
! 	bool		res;
  
! 	entry = findkey(hs, VARDATA(key), VARSIZE(key) - VARHDRSZ);
  
- 	res = (entry && !entry->valisnull) ? true : false;
  
! 	PG_FREE_IF_COPY(hs, 0);
! 	PG_FREE_IF_COPY(key, 1);
  
  	PG_RETURN_BOOL(res);
  }
  
! PG_FUNCTION_INFO_V1(delete);
! Datum		delete(PG_FUNCTION_ARGS);
  Datum
! delete(PG_FUNCTION_ARGS)
  {
  	HStore	   *hs = PG_GETARG_HS(0);
! 	text	   *key = PG_GETARG_TEXT_P(1);
  	HStore	   *out = palloc(VARSIZE(hs));
! 	char	   *ptrs,
  			   *ptrd;
  	HEntry	   *es,
  			   *ed;
  
  	SET_VARSIZE(out, VARSIZE(hs));
  	out->size = hs->size;		/* temporary! */
  
! 	ptrs = STRPTR(hs);
  	es = ARRPTR(hs);
! 	ptrd = STRPTR(out);
  	ed = ARRPTR(out);
  
! 	while (es - ARRPTR(hs) < hs->size)
  	{
! 		if (!(es->keylen == VARSIZE(key) - VARHDRSZ && strncmp(ptrs, VARDATA(key), es->keylen) == 0))
  		{
! 			memcpy(ed, es, sizeof(HEntry));
! 			memcpy(ptrd, ptrs, es->keylen + ((es->valisnull) ? 0 : es->vallen));
! 			ed->pos = ptrd - STRPTR(out);
! 			ptrd += es->keylen + ((es->valisnull) ? 0 : es->vallen);
! 			ed++;
  		}
- 		ptrs += es->keylen + ((es->valisnull) ? 0 : es->vallen);
- 		es++;
  	}
  
! 	if (ed - ARRPTR(out) != out->size)
  	{
! 		int			buflen = ptrd - STRPTR(out);
  
! 		ptrd = STRPTR(out);
  
! 		out->size = ed - ARRPTR(out);
  
! 		memmove(STRPTR(out), ptrd, buflen);
! 		SET_VARSIZE(out, CALCDATASIZE(out->size, buflen));
  	}
  
  
! 	PG_FREE_IF_COPY(hs, 0);
! 	PG_FREE_IF_COPY(key, 1);
  
  	PG_RETURN_POINTER(out);
  }
  
! PG_FUNCTION_INFO_V1(hs_concat);
! Datum		hs_concat(PG_FUNCTION_ARGS);
  Datum
! hs_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,
  			   *pd;
  	HEntry	   *es1,
  			   *es2,
  			   *ed;
  
  	SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2));
! 	out->size = s1->size + s2->size;
  
  	ps1 = STRPTR(s1);
  	ps2 = STRPTR(s2);
! 	pd = STRPTR(out);
  	es1 = ARRPTR(s1);
  	es2 = ARRPTR(s2);
  	ed = ARRPTR(out);
  
! 	while (es1 - ARRPTR(s1) < s1->size && es2 - ARRPTR(s2) < s2->size)
! 	{
! 		int			difference;
! 
! 		if (es1->keylen == es2->keylen)
! 			difference = strncmp(ps1, ps2, es1->keylen);
  		else
- 			difference = (es1->keylen > es2->keylen) ? 1 : -1;
- 
- 		if (difference == 0)
  		{
! 			memcpy(ed, es2, sizeof(HEntry));
! 			memcpy(pd, ps2, es2->keylen + ((es2->valisnull) ? 0 : es2->vallen));
! 			ed->pos = pd - STRPTR(out);
! 			pd += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
! 			ed++;
! 
! 			ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
! 			es1++;
! 			ps2 += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
! 			es2++;
  		}
- 		else if (difference > 0)
- 		{
- 			memcpy(ed, es2, sizeof(HEntry));
- 			memcpy(pd, ps2, es2->keylen + ((es2->valisnull) ? 0 : es2->vallen));
- 			ed->pos = pd - STRPTR(out);
- 			pd += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
- 			ed++;
  
! 			ps2 += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
! 			es2++;
  		}
  		else
  		{
! 			memcpy(ed, es1, sizeof(HEntry));
! 			memcpy(pd, ps1, es1->keylen + ((es1->valisnull) ? 0 : es1->vallen));
! 			ed->pos = pd - STRPTR(out);
! 			pd += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
! 			ed++;
! 
! 			ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
! 			es1++;
  		}
  	}
  
! 	while (es1 - ARRPTR(s1) < s1->size)
! 	{
! 		memcpy(ed, es1, sizeof(HEntry));
! 		memcpy(pd, ps1, es1->keylen + ((es1->valisnull) ? 0 : es1->vallen));
! 		ed->pos = pd - STRPTR(out);
! 		pd += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
! 		ed++;
  
! 		ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
! 		es1++;
! 	}
  
- 	while (es2 - ARRPTR(s2) < s2->size)
- 	{
- 		memcpy(ed, es2, sizeof(HEntry));
- 		memcpy(pd, ps2, es2->keylen + ((es2->valisnull) ? 0 : es2->vallen));
- 		ed->pos = pd - STRPTR(out);
- 		pd += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
- 		ed++;
  
! 		ps2 += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
! 		es2++;
! 	}
  
! 	if (ed - ARRPTR(out) != out->size)
  	{
! 		int			buflen = pd - STRPTR(out);
  
! 		pd = STRPTR(out);
  
! 		out->size = ed - ARRPTR(out);
  
! 		memmove(STRPTR(out), pd, buflen);
! 		SET_VARSIZE(out, CALCDATASIZE(out->size, buflen));
  	}
  
! 	PG_FREE_IF_COPY(s1, 0);
! 	PG_FREE_IF_COPY(s2, 1);
  
! 	PG_RETURN_POINTER(out);
  }
  
! PG_FUNCTION_INFO_V1(tconvert);
! Datum		tconvert(PG_FUNCTION_ARGS);
  Datum
! tconvert(PG_FUNCTION_ARGS)
  {
! 	text	   *key;
! 	text	   *val = NULL;
! 	int			len;
! 	HStore	   *out;
! 
! 	if (PG_ARGISNULL(0))
! 		PG_RETURN_NULL();
! 
! 	key = PG_GETARG_TEXT_P(0);
  
! 	if (PG_ARGISNULL(1))
! 		len = CALCDATASIZE(1, VARSIZE(key));
! 	else
  	{
! 		val = PG_GETARG_TEXT_P(1);
! 		len = CALCDATASIZE(1, VARSIZE(key) + VARSIZE(val) - 2 * VARHDRSZ);
! 	}
! 	out = palloc(len);
! 	SET_VARSIZE(out, len);
! 	out->size = 1;
  
! 	ARRPTR(out)->keylen = hstoreCheckKeyLen(VARSIZE(key) - VARHDRSZ);
! 	if (PG_ARGISNULL(1))
! 	{
! 		ARRPTR(out)->vallen = 0;
! 		ARRPTR(out)->valisnull = true;
! 	}
! 	else
! 	{
! 		ARRPTR(out)->vallen = hstoreCheckValLen(VARSIZE(val) - VARHDRSZ);
! 		ARRPTR(out)->valisnull = false;
  	}
- 	ARRPTR(out)->pos = 0;
  
! 	memcpy(STRPTR(out), VARDATA(key), ARRPTR(out)->keylen);
! 	if (!PG_ARGISNULL(1))
! 	{
! 		memcpy(STRPTR(out) + ARRPTR(out)->keylen, VARDATA(val), ARRPTR(out)->vallen);
! 		PG_FREE_IF_COPY(val, 1);
! 	}
  
! 	PG_FREE_IF_COPY(key, 0);
  
  	PG_RETURN_POINTER(out);
  }
  
! PG_FUNCTION_INFO_V1(akeys);
! Datum		akeys(PG_FUNCTION_ARGS);
  Datum
! akeys(PG_FUNCTION_ARGS)
  {
  	HStore	   *hs = PG_GETARG_HS(0);
  	Datum	   *d;
  	ArrayType  *a;
! 	HEntry	   *ptr = ARRPTR(hs);
  	char	   *base = STRPTR(hs);
  
! 	d = (Datum *) palloc(sizeof(Datum) * (hs->size + 1));
! 	while (ptr - ARRPTR(hs) < hs->size)
  	{
! 		text	   *item;
! 
! 		item = cstring_to_text_with_len(base + ptr->pos, ptr->keylen);
! 		d[ptr - ARRPTR(hs)] = PointerGetDatum(item);
! 		ptr++;
  	}
  
! 	a = construct_array(d,
! 						hs->size,
! 						TEXTOID,
! 						-1,
! 						false,
! 						'i');
  
! 	ptr = ARRPTR(hs);
! 	while (ptr - ARRPTR(hs) < hs->size)
  	{
! 		pfree(DatumGetPointer(d[ptr - ARRPTR(hs)]));
! 		ptr++;
  	}
  
! 	pfree(d);
! 	PG_FREE_IF_COPY(hs, 0);
  
  	PG_RETURN_POINTER(a);
  }
  
! PG_FUNCTION_INFO_V1(avals);
! Datum		avals(PG_FUNCTION_ARGS);
  Datum
! avals(PG_FUNCTION_ARGS)
  {
  	HStore	   *hs = PG_GETARG_HS(0);
  	Datum	   *d;
  	ArrayType  *a;
! 	HEntry	   *ptr = ARRPTR(hs);
  	char	   *base = STRPTR(hs);
  
! 	d = (Datum *) palloc(sizeof(Datum) * (hs->size + 1));
! 	while (ptr - ARRPTR(hs) < hs->size)
  	{
! 		text	   *item;
! 
! 		item = cstring_to_text_with_len(base + ptr->pos + ptr->keylen,
! 										(ptr->valisnull) ? 0 : ptr->vallen);
! 		d[ptr - ARRPTR(hs)] = PointerGetDatum(item);
! 		ptr++;
  	}
  
! 	a = construct_array(d,
! 						hs->size,
! 						TEXTOID,
! 						-1,
! 						false,
! 						'i');
  
! 	ptr = ARRPTR(hs);
! 	while (ptr - ARRPTR(hs) < hs->size)
  	{
! 		pfree(DatumGetPointer(d[ptr - ARRPTR(hs)]));
! 		ptr++;
  	}
  
! 	pfree(d);
! 	PG_FREE_IF_COPY(hs, 0);
  
  	PG_RETURN_POINTER(a);
  }
  
! typedef struct
! {
! 	HStore	   *hs;
! 	int			i;
! } AKStore;
  
  static void
! setup_firstcall(FuncCallContext *funcctx, HStore *hs)
  {
  	MemoryContext oldcontext;
! 	AKStore    *st;
  
  	oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
  
! 	st = (AKStore *) palloc(sizeof(AKStore));
! 	st->i = 0;
! 	st->hs = (HStore *) palloc(VARSIZE(hs));
! 	memcpy(st->hs, hs, VARSIZE(hs));
  
  	funcctx->user_fctx = (void *) st;
  	MemoryContextSwitchTo(oldcontext);
  }
  
! PG_FUNCTION_INFO_V1(skeys);
! Datum		skeys(PG_FUNCTION_ARGS);
  Datum
! skeys(PG_FUNCTION_ARGS)
  {
  	FuncCallContext *funcctx;
! 	AKStore    *st;
  
  	if (SRF_IS_FIRSTCALL())
  	{
! 		HStore	   *hs = PG_GETARG_HS(0);
! 
  		funcctx = SRF_FIRSTCALL_INIT();
! 		setup_firstcall(funcctx, hs);
! 		PG_FREE_IF_COPY(hs, 0);
  	}
  
  	funcctx = SRF_PERCALL_SETUP();
! 	st = (AKStore *) funcctx->user_fctx;
  
! 	if (st->i < st->hs->size)
  	{
! 		HEntry	   *ptr = &(ARRPTR(st->hs)[st->i]);
  		text	   *item;
  
! 		item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos,
! 										ptr->keylen);
! 		st->i++;
  
  		SRF_RETURN_NEXT(funcctx, PointerGetDatum(item));
  	}
  
- 	pfree(st->hs);
- 	pfree(st);
- 
  	SRF_RETURN_DONE(funcctx);
  }
  
! PG_FUNCTION_INFO_V1(svals);
! Datum		svals(PG_FUNCTION_ARGS);
  Datum
! svals(PG_FUNCTION_ARGS)
  {
  	FuncCallContext *funcctx;
! 	AKStore    *st;
  
  	if (SRF_IS_FIRSTCALL())
  	{
! 		HStore	   *hs = PG_GETARG_HS(0);
! 
  		funcctx = SRF_FIRSTCALL_INIT();
! 		setup_firstcall(funcctx, hs);
! 		PG_FREE_IF_COPY(hs, 0);
  	}
  
  	funcctx = SRF_PERCALL_SETUP();
! 	st = (AKStore *) funcctx->user_fctx;
  
! 	if (st->i < st->hs->size)
  	{
! 		HEntry	   *ptr = &(ARRPTR(st->hs)[st->i]);
  
! 		if (ptr->valisnull)
  		{
  			ReturnSetInfo *rsi;
  
! 			st->i++;
  			(funcctx)->call_cntr++;
  			rsi = (ReturnSetInfo *) fcinfo->resultinfo;
  			rsi->isDone = ExprMultipleResult;
--- 1,834 ----
  /*
!  * $PostgreSQL$
   */
  #include "postgres.h"
  
  #include "catalog/pg_type.h"
  #include "funcapi.h"
  #include "utils/builtins.h"
+ #include "access/hash.h"
+ #include "access/htup.h"
+ #include "access/heapam.h"
  
  #include "hstore.h"
  
+ #if HSTORE_POLLUTE_NAMESPACE
+ 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)
+ #endif
+ 
+ /* 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->size;
! 	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         outcount = 0;
  
  	SET_VARSIZE(out, VARSIZE(hs));
  	out->size = hs->size;		/* temporary! */
  
! 	bufs = STRPTR(hs);
  	es = ARRPTR(hs);
! 	bufd = ptrd = STRPTR(out);
  	ed = ARRPTR(out);
  
! 	for (i = 0; i < hs->size; ++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->size;
! 	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));
! 	out->size = hs->size;		/* 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));
! 		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->size;
! 	int         hs2_count = hs2->size;
! 	char	   *ps,
! 		       *ps2,
! 		       *bufd,
! 			   *pd;
! 	HEntry	   *es,
! 		       *es2,
! 			   *ed;
! 	int         i,j;
! 	int         outcount = 0;
  
! 	SET_VARSIZE(out, VARSIZE(hs));
! 	out->size = hs->size;		/* 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));
! 		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 = s1->size;
+ 	int         s2count = s2->size;
+ 	int         outcount = 0;
  
  	SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2));
! 	out->size = s1count + s2count;
  
  	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->size;
+ 	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->size;
+ 	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);
  }
  
! /* 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->size)
  	{
! 		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->size)
  	{
! 		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;
***************
*** 502,645 ****
  		{
  			text	   *item;
  
! 			item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos + ptr->keylen,
! 											ptr->vallen);
! 			st->i++;
  
  			SRF_RETURN_NEXT(funcctx, PointerGetDatum(item));
  		}
  	}
  
- 	pfree(st->hs);
- 	pfree(st);
- 
  	SRF_RETURN_DONE(funcctx);
  }
  
! PG_FUNCTION_INFO_V1(hs_contains);
! Datum		hs_contains(PG_FUNCTION_ARGS);
  Datum
! hs_contains(PG_FUNCTION_ARGS)
  {
  	HStore	   *val = PG_GETARG_HS(0);
  	HStore	   *tmpl = PG_GETARG_HS(1);
  	bool		res = true;
  	HEntry	   *te = ARRPTR(tmpl);
! 	char	   *vv = STRPTR(val);
! 	char	   *tv = STRPTR(tmpl);
  
! 	while (res && te - ARRPTR(tmpl) < tmpl->size)
  	{
! 		HEntry	   *entry = findkey(val, tv + te->pos, te->keylen);
  
! 		if (entry)
  		{
! 			if (te->valisnull || entry->valisnull)
! 			{
! 				if (!(te->valisnull && entry->valisnull))
! 					res = false;
! 			}
! 			else if (te->vallen != entry->vallen ||
! 					 strncmp(vv + entry->pos + entry->keylen,
! 							 tv + te->pos + te->keylen,
! 							 te->vallen))
  				res = false;
  		}
  		else
  			res = false;
- 		te++;
  	}
  
- 	PG_FREE_IF_COPY(val, 0);
- 	PG_FREE_IF_COPY(tmpl, 1);
- 
  	PG_RETURN_BOOL(res);
  }
  
! PG_FUNCTION_INFO_V1(hs_contained);
! Datum		hs_contained(PG_FUNCTION_ARGS);
  Datum
! hs_contained(PG_FUNCTION_ARGS)
  {
! 	PG_RETURN_DATUM(DirectFunctionCall2(
! 										hs_contains,
  										PG_GETARG_DATUM(1),
  										PG_GETARG_DATUM(0)
  										));
  }
  
! PG_FUNCTION_INFO_V1(each);
! Datum		each(PG_FUNCTION_ARGS);
  Datum
! each(PG_FUNCTION_ARGS)
  {
  	FuncCallContext *funcctx;
! 	AKStore    *st;
  
  	if (SRF_IS_FIRSTCALL())
  	{
! 		TupleDesc	tupdesc;
! 		MemoryContext oldcontext;
! 		HStore	   *hs = PG_GETARG_HS(0);
! 
  		funcctx = SRF_FIRSTCALL_INIT();
! 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
! 		st = (AKStore *) palloc(sizeof(AKStore));
! 		st->i = 0;
! 		st->hs = (HStore *) palloc(VARSIZE(hs));
! 		memcpy(st->hs, hs, VARSIZE(hs));
! 		funcctx->user_fctx = (void *) st;
! 
! 		/* 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->attinmeta = TupleDescGetAttInMetadata(tupdesc);
! 
! 		MemoryContextSwitchTo(oldcontext);
! 		PG_FREE_IF_COPY(hs, 0);
  	}
  
  	funcctx = SRF_PERCALL_SETUP();
! 	st = (AKStore *) funcctx->user_fctx;
  
! 	if (st->i < st->hs->size)
  	{
! 		HEntry	   *ptr = &(ARRPTR(st->hs)[st->i]);
  		Datum		res,
  					dvalues[2];
  		bool		nulls[2] = {false, false};
  		text	   *item;
  		HeapTuple	tuple;
  
! 		item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos, ptr->keylen);
  		dvalues[0] = PointerGetDatum(item);
  
! 		if (ptr->valisnull)
  		{
  			dvalues[1] = (Datum) 0;
  			nulls[1] = true;
  		}
  		else
  		{
! 			item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos + ptr->keylen,
! 											ptr->vallen);
  			dvalues[1] = PointerGetDatum(item);
  		}
- 		st->i++;
  
! 		tuple = heap_form_tuple(funcctx->attinmeta->tupdesc, dvalues, nulls);
  		res = HeapTupleGetDatum(tuple);
  
- 		pfree(DatumGetPointer(dvalues[0]));
- 		if (!nulls[1])
- 			pfree(DatumGetPointer(dvalues[1]));
- 
  		SRF_RETURN_NEXT(funcctx, PointerGetDatum(res));
  	}
  
- 	pfree(st->hs);
- 	pfree(st);
- 
  	SRF_RETURN_DONE(funcctx);
  }
--- 838,1137 ----
  		{
  			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 = tmpl->size;
! 	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->size)
  	{
! 		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 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 = hs1->size;
+ 	int         hcount2 = hs2->size;
+ 	char       *str1 = STRPTR(hs1);
+ 	char       *str2 = STRPTR(hs2);
+ 	HEntry     *ent1 = ARRPTR(hs1);
+ 	HEntry     *ent2 = ARRPTR(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 */
+ 		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)
+ 		   == CALCDATASIZE(hs->size, HSE_ENDPOS(ARRPTR(hs)[2*hs->size - 1])));
+ 
+ 	PG_FREE_IF_COPY(hs,0);
+ 	PG_RETURN_DATUM(hval);
+ }
Index: contrib/hstore/uninstall_hstore.sql
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/hstore/uninstall_hstore.sql,v
retrieving revision 1.8
diff -c -r1.8 uninstall_hstore.sql
*** contrib/hstore/uninstall_hstore.sql	25 Mar 2009 22:19:01 -0000	1.8
--- contrib/hstore/uninstall_hstore.sql	8 Jul 2009 18:39:00 -0000
***************
*** 5,36 ****
  
  DROP OPERATOR CLASS gist_hstore_ops USING gist CASCADE;
  DROP OPERATOR CLASS gin_hstore_ops USING gin CASCADE;
  
! DROP OPERATOR ? ( hstore, text );
! DROP OPERATOR ->( hstore, text );
! DROP OPERATOR ||( hstore, hstore );
! DROP OPERATOR @>( hstore, hstore );
! DROP OPERATOR <@( hstore, hstore );
! DROP OPERATOR @( hstore, hstore );
! DROP OPERATOR ~( hstore, hstore );
! DROP OPERATOR =>( text, text );
! 
  
  DROP FUNCTION fetchval(hstore,text);
  DROP FUNCTION isexists(hstore,text);
  DROP FUNCTION exist(hstore,text);
  DROP FUNCTION isdefined(hstore,text);
  DROP FUNCTION defined(hstore,text);
  DROP FUNCTION delete(hstore,text);
  DROP FUNCTION hs_concat(hstore,hstore);
  DROP FUNCTION hs_contains(hstore,hstore);
  DROP FUNCTION hs_contained(hstore,hstore);
  DROP FUNCTION tconvert(text,text);
  DROP FUNCTION akeys(hstore);
  DROP FUNCTION avals(hstore);
  DROP FUNCTION skeys(hstore);
  DROP FUNCTION svals(hstore);
  DROP FUNCTION each(hstore);
  DROP FUNCTION ghstore_compress(internal);
  DROP FUNCTION ghstore_decompress(internal);
  DROP FUNCTION ghstore_penalty(internal,internal,internal);
--- 5,70 ----
  
  DROP OPERATOR CLASS gist_hstore_ops USING gist CASCADE;
  DROP OPERATOR CLASS gin_hstore_ops USING gin CASCADE;
+ DROP OPERATOR CLASS hash_hstore_ops USING hash CASCADE;
+ DROP OPERATOR CLASS btree_hstore_ops USING btree CASCADE;
  
! DROP OPERATOR -  ( hstore, text );
! DROP OPERATOR -  ( hstore, text[] );
! DROP OPERATOR -  ( hstore, hstore );
! DROP OPERATOR ?  ( hstore, text );
! DROP OPERATOR ?& ( hstore, text[] );
! DROP OPERATOR ?| ( hstore, text[] );
! DROP OPERATOR -> ( hstore, text );
! DROP OPERATOR -> ( hstore, text[] );
! DROP OPERATOR || ( hstore, hstore );
! DROP OPERATOR @> ( hstore, hstore );
! DROP OPERATOR <@ ( hstore, hstore );
! DROP OPERATOR @  ( hstore, hstore );
! DROP OPERATOR ~  ( hstore, hstore );
! DROP OPERATOR => ( text, text );
! DROP OPERATOR => ( text[], text[] );
! DROP OPERATOR => ( hstore, text[] );
! DROP OPERATOR #= ( anyelement, hstore );
! DROP OPERATOR =  ( hstore, hstore );
! DROP OPERATOR <> ( hstore, hstore );
! DROP OPERATOR #<#  ( hstore, hstore );
! DROP OPERATOR #<=# ( hstore, hstore );
! DROP OPERATOR #>#  ( hstore, hstore );
! DROP OPERATOR #>=# ( hstore, hstore );
  
+ DROP FUNCTION hstore_eq(hstore,hstore);
+ DROP FUNCTION hstore_ne(hstore,hstore);
+ DROP FUNCTION hstore_gt(hstore,hstore);
+ DROP FUNCTION hstore_ge(hstore,hstore);
+ DROP FUNCTION hstore_lt(hstore,hstore);
+ DROP FUNCTION hstore_le(hstore,hstore);
+ DROP FUNCTION hstore_cmp(hstore,hstore);
+ DROP FUNCTION hstore_hash(hstore);
+ DROP FUNCTION slice_array(hstore,text[]);
+ DROP FUNCTION slice_hstore(hstore,text[]);
+ DROP FUNCTION hstore(text[],text[]);
+ DROP FUNCTION hstore(record);
  DROP FUNCTION fetchval(hstore,text);
  DROP FUNCTION isexists(hstore,text);
  DROP FUNCTION exist(hstore,text);
+ DROP FUNCTION exists_any(hstore,text[]);
+ DROP FUNCTION exists_all(hstore,text[]);
  DROP FUNCTION isdefined(hstore,text);
  DROP FUNCTION defined(hstore,text);
  DROP FUNCTION delete(hstore,text);
+ DROP FUNCTION delete(hstore,text[]);
+ DROP FUNCTION delete(hstore,hstore);
  DROP FUNCTION hs_concat(hstore,hstore);
  DROP FUNCTION hs_contains(hstore,hstore);
  DROP FUNCTION hs_contained(hstore,hstore);
  DROP FUNCTION tconvert(text,text);
+ DROP FUNCTION hstore(text,text);
  DROP FUNCTION akeys(hstore);
  DROP FUNCTION avals(hstore);
  DROP FUNCTION skeys(hstore);
  DROP FUNCTION svals(hstore);
  DROP FUNCTION each(hstore);
+ DROP FUNCTION populate_record(anyelement,hstore);
  DROP FUNCTION ghstore_compress(internal);
  DROP FUNCTION ghstore_decompress(internal);
  DROP FUNCTION ghstore_penalty(internal,internal,internal);
Index: contrib/hstore/expected/hstore.out
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/hstore/expected/hstore.out,v
retrieving revision 1.4
diff -c -r1.4 hstore.out
*** contrib/hstore/expected/hstore.out	14 Mar 2007 14:21:53 -0000	1.4
--- contrib/hstore/expected/hstore.out	8 Jul 2009 18:39:01 -0000
***************
*** 278,283 ****
--- 278,308 ----
   f
  (1 row)
  
+ -- -> array operator
+ select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['aa','c'];
+   ?column?  
+ ------------
+  {"NULL",d}
+ (1 row)
+ 
+ select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['c','aa'];
+   ?column?  
+ ------------
+  {d,"NULL"}
+ (1 row)
+ 
+ select 'aa=>NULL, c=>d , b=>16'::hstore -> ARRAY['aa','c',null];
+    ?column?    
+ ---------------
+  {NULL,d,NULL}
+ (1 row)
+ 
+ select 'aa=>1, c=>3, b=>2, d=>4'::hstore -> ARRAY[['b','d'],['aa','c']];
+    ?column?    
+ ---------------
+  {{2,4},{1,3}}
+ (1 row)
+ 
  -- exists/defined
  select exist('a=>NULL, b=>qq', 'a');
   exist 
***************
*** 327,332 ****
--- 352,441 ----
   t
  (1 row)
  
+ select hstore 'a=>NULL, b=>qq' ? 'a';
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ? 'b';
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ? 'c';
+  ?column? 
+ ----------
+  f
+ (1 row)
+ 
+ select hstore 'a=>"NULL", b=>qq' ? 'a';
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?| ARRAY['a','b'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?| ARRAY['b','a'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','a'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','d'];
+  ?column? 
+ ----------
+  f
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?| '{}'::text[];
+  ?column? 
+ ----------
+  f
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?& ARRAY['a','b'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?& ARRAY['b','a'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','a'];
+  ?column? 
+ ----------
+  f
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','d'];
+  ?column? 
+ ----------
+  f
+ (1 row)
+ 
+ select hstore 'a=>NULL, b=>qq' ?& '{}'::text[];
+  ?column? 
+ ----------
+  f
+ (1 row)
+ 
  -- delete 
  select delete('a=>1 , b=>2, c=>3'::hstore, 'a');
         delete       
***************
*** 358,363 ****
--- 467,624 ----
   "a"=>"1", "b"=>"2", "c"=>"3"
  (1 row)
  
+ select 'a=>1 , b=>2, c=>3'::hstore - 'a'::text;
+       ?column?      
+ --------------------
+  "b"=>"2", "c"=>"3"
+ (1 row)
+ 
+ select 'a=>null , b=>2, c=>3'::hstore - 'a'::text;
+       ?column?      
+ --------------------
+  "b"=>"2", "c"=>"3"
+ (1 row)
+ 
+ select 'a=>1 , b=>2, c=>3'::hstore - 'b'::text;
+       ?column?      
+ --------------------
+  "a"=>"1", "c"=>"3"
+ (1 row)
+ 
+ select 'a=>1 , b=>2, c=>3'::hstore - 'c'::text;
+       ?column?      
+ --------------------
+  "a"=>"1", "b"=>"2"
+ (1 row)
+ 
+ select 'a=>1 , b=>2, c=>3'::hstore - 'd'::text;
+            ?column?           
+ ------------------------------
+  "a"=>"1", "b"=>"2", "c"=>"3"
+ (1 row)
+ 
+ -- delete (array)
+ select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','e']);
+             delete            
+ ------------------------------
+  "a"=>"1", "b"=>"2", "c"=>"3"
+ (1 row)
+ 
+ select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','b']);
+        delete       
+ --------------------
+  "a"=>"1", "c"=>"3"
+ (1 row)
+ 
+ select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['a','c']);
+   delete  
+ ----------
+  "b"=>"2"
+ (1 row)
+ 
+ select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY[['b'],['c'],['a']]);
+  delete 
+ --------
+  
+ (1 row)
+ 
+ select delete('a=>1 , b=>2, c=>3'::hstore, '{}'::text[]);
+             delete            
+ ------------------------------
+  "a"=>"1", "b"=>"2", "c"=>"3"
+ (1 row)
+ 
+ select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','e'];
+            ?column?           
+ ------------------------------
+  "a"=>"1", "b"=>"2", "c"=>"3"
+ (1 row)
+ 
+ select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','b'];
+       ?column?      
+ --------------------
+  "a"=>"1", "c"=>"3"
+ (1 row)
+ 
+ select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c'];
+  ?column? 
+ ----------
+  "b"=>"2"
+ (1 row)
+ 
+ select 'a=>1 , b=>2, c=>3'::hstore - ARRAY[['b'],['c'],['a']];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ select 'a=>1 , b=>2, c=>3'::hstore - '{}'::text[];
+            ?column?           
+ ------------------------------
+  "a"=>"1", "b"=>"2", "c"=>"3"
+ (1 row)
+ 
+ -- delete (hstore)
+ select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>4, b=>2'::hstore);
+        delete        
+ ---------------------
+  "c"=>"3", "aa"=>"1"
+ (1 row)
+ 
+ select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>NULL, c=>3'::hstore);
+        delete        
+ ---------------------
+  "b"=>"2", "aa"=>"1"
+ (1 row)
+ 
+ select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>1, b=>2, c=>3'::hstore);
+  delete 
+ --------
+  
+ (1 row)
+ 
+ select delete('aa=>1 , b=>2, c=>3'::hstore, 'b=>2'::hstore);
+        delete        
+ ---------------------
+  "c"=>"3", "aa"=>"1"
+ (1 row)
+ 
+ select delete('aa=>1 , b=>2, c=>3'::hstore, ''::hstore);
+             delete             
+ -------------------------------
+  "b"=>"2", "c"=>"3", "aa"=>"1"
+ (1 row)
+ 
+ select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>4, b=>2'::hstore;
+       ?column?       
+ ---------------------
+  "c"=>"3", "aa"=>"1"
+ (1 row)
+ 
+ select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>NULL, c=>3'::hstore;
+       ?column?       
+ ---------------------
+  "b"=>"2", "aa"=>"1"
+ (1 row)
+ 
+ select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>1, b=>2, c=>3'::hstore;
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ select 'aa=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore;
+       ?column?       
+ ---------------------
+  "c"=>"3", "aa"=>"1"
+ (1 row)
+ 
+ select 'aa=>1 , b=>2, c=>3'::hstore - ''::hstore;
+            ?column?            
+ -------------------------------
+  "b"=>"2", "c"=>"3", "aa"=>"1"
+ (1 row)
+ 
  -- ||
  select 'aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f';
                   ?column?                  
***************
*** 414,419 ****
--- 675,825 ----
   "a"=>"g", "b"=>NULL
  (1 row)
  
+ -- => arrays
+ select ARRAY['a','b','asd'] => ARRAY['g','h','i'];
+             ?column?            
+ --------------------------------
+  "a"=>"g", "b"=>"h", "asd"=>"i"
+ (1 row)
+ 
+ select ARRAY['a','b','asd'] => ARRAY['g','h',NULL];
+             ?column?             
+ ---------------------------------
+  "a"=>"g", "b"=>"h", "asd"=>NULL
+ (1 row)
+ 
+ select ARRAY['z','y','x'] => ARRAY['1','2','3'];
+            ?column?           
+ ------------------------------
+  "x"=>"3", "y"=>"2", "z"=>"1"
+ (1 row)
+ 
+ select ARRAY['aaa','bb','c','d'] => ARRAY[null::text,null,null,null];
+                    ?column?                    
+ -----------------------------------------------
+  "c"=>NULL, "d"=>NULL, "bb"=>NULL, "aaa"=>NULL
+ (1 row)
+ 
+ select ARRAY['aaa','bb','c','d'] => null;
+                    ?column?                    
+ -----------------------------------------------
+  "c"=>NULL, "d"=>NULL, "bb"=>NULL, "aaa"=>NULL
+ (1 row)
+ 
+ select hstore 'aa=>1, b=>2, c=>3' => ARRAY['g','h','i'];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ select hstore 'aa=>1, b=>2, c=>3' => ARRAY['c','b'];
+       ?column?      
+ --------------------
+  "b"=>"2", "c"=>"3"
+ (1 row)
+ 
+ select hstore 'aa=>1, b=>2, c=>3' => ARRAY['aa','b'];
+       ?column?       
+ ---------------------
+  "b"=>"2", "aa"=>"1"
+ (1 row)
+ 
+ select hstore 'aa=>1, b=>2, c=>3' => ARRAY['c','b','aa'];
+            ?column?            
+ -------------------------------
+  "b"=>"2", "c"=>"3", "aa"=>"1"
+ (1 row)
+ 
+ -- records
+ select hstore(v) from (values (1, 'foo', 1.2, 3::float8)) v(a,b,c,d);
+                      hstore                     
+ ------------------------------------------------
+  "f1"=>"1", "f2"=>"foo", "f3"=>"1.2", "f4"=>"3"
+ (1 row)
+ 
+ create table testhstore1 (a integer, b text, c numeric, d float8);
+ insert into testhstore1 values (1, 'foo', 1.2, 3::float8);
+ select hstore(v) from testhstore1 v;
+                    hstore                   
+ --------------------------------------------
+  "a"=>"1", "b"=>"foo", "c"=>"1.2", "d"=>"3"
+ (1 row)
+ 
+ select hstore(null::testhstore1);
+                    hstore                   
+ --------------------------------------------
+  "a"=>NULL, "b"=>NULL, "c"=>NULL, "d"=>NULL
+ (1 row)
+ 
+ select populate_record(v, ('c' => '3.45')) from testhstore1 v;
+  populate_record 
+ -----------------
+  (1,foo,3.45,3)
+ (1 row)
+ 
+ select populate_record(v, ('d' => '3.45')) from testhstore1 v;
+  populate_record  
+ ------------------
+  (1,foo,1.2,3.45)
+ (1 row)
+ 
+ select populate_record(v, ('c' => null)) from testhstore1 v;
+  populate_record 
+ -----------------
+  (1,foo,,3)
+ (1 row)
+ 
+ select populate_record(v, ('b' => 'foo') || ('a' => '123')) from testhstore1 v;
+  populate_record 
+ -----------------
+  (123,foo,1.2,3)
+ (1 row)
+ 
+ select populate_record(null::testhstore1, ('c' => '3.45') || ('a' => '123')) from testhstore1 v;
+  populate_record 
+ -----------------
+  (123,,3.45,)
+ (1 row)
+ 
+ select v #= ('c' => '3.45') from testhstore1 v;
+     ?column?    
+ ----------------
+  (1,foo,3.45,3)
+ (1 row)
+ 
+ select v #= ('d' => '3.45') from testhstore1 v;
+      ?column?     
+ ------------------
+  (1,foo,1.2,3.45)
+ (1 row)
+ 
+ select v #= ('c' => null) from testhstore1 v;
+   ?column?  
+ ------------
+  (1,foo,,3)
+ (1 row)
+ 
+ select v #= (('b' => 'foo') || ('a' => '123')) from testhstore1 v;
+     ?column?     
+ -----------------
+  (123,foo,1.2,3)
+ (1 row)
+ 
+ select null::testhstore1 #= (('c' => '3.45') || ('a' => '123')) from testhstore1 v;
+    ?column?   
+ --------------
+  (123,,3.45,)
+ (1 row)
+ 
+ select v #= h from testhstore1 v, (values (hstore 'a=>123',1),('b=>foo,c=>3.21',2),('a=>null',3),('e=>blah',4)) x(h,i) order by i;
+     ?column?     
+ -----------------
+  (123,foo,1.2,3)
+  (1,foo,3.21,3)
+  (,foo,1.2,3)
+  (1,foo,1.2,3)
+ (4 rows)
+ 
  -- keys/values
  select akeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
      akeys     
***************
*** 440,448 ****
  (1 row)
  
  select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL');
!    avals    
! ------------
!  {g,1,l,""}
  (1 row)
  
  select avals('""=>1');
--- 846,854 ----
  (1 row)
  
  select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL');
!     avals     
! --------------
!  {g,1,l,NULL}
  (1 row)
  
  select avals('""=>1');
***************
*** 583,588 ****
--- 989,1006 ----
     194
  (1 row)
  
+ select count(*) from testhstore where h ?| ARRAY['public','disabled'];
+  count 
+ -------
+    337
+ (1 row)
+ 
+ select count(*) from testhstore where h ?& ARRAY['public','disabled'];
+  count 
+ -------
+     42
+ (1 row)
+ 
  create index hidx on testhstore using gist(h);
  set enable_seqscan=off;
  select count(*) from testhstore where h @> 'wait=>NULL';
***************
*** 609,614 ****
--- 1027,1044 ----
     194
  (1 row)
  
+ select count(*) from testhstore where h ?| ARRAY['public','disabled'];
+  count 
+ -------
+    337
+ (1 row)
+ 
+ select count(*) from testhstore where h ?& ARRAY['public','disabled'];
+  count 
+ -------
+     42
+ (1 row)
+ 
  drop index hidx;
  create index hidx on testhstore using gin (h);
  set enable_seqscan=off;
***************
*** 636,641 ****
--- 1066,1083 ----
     194
  (1 row)
  
+ select count(*) from testhstore where h ?| ARRAY['public','disabled'];
+  count 
+ -------
+    337
+ (1 row)
+ 
+ select count(*) from testhstore where h ?& ARRAY['public','disabled'];
+  count 
+ -------
+     42
+ (1 row)
+ 
  select count(*) from (select (each(h)).key from testhstore) as wow ;
   count 
  -------
***************
*** 669,671 ****
--- 1111,1163 ----
   abstract  |   161
  (22 rows)
  
+ -- btree opclass transitivity
+ set enable_seqscan=on;
+ create table testhstore2 as select * from testhstore limit 50;
+ create function bool_imp(boolean,boolean) returns boolean language sql as $f$ select (not $1) or $2; $f$;
+ create operator => (leftarg = boolean, rightarg = boolean, procedure = bool_imp);
+ select every( ((h1.h #># h2.h AND h2.h #># h3.h) => (h1.h #># h3.h)) AND ((h1.h #>=# h2.h AND h2.h #>=# h3.h) => (h1.h #>=# h3.h)) AND ((h1.h #<# h2.h AND h2.h #<# h3.h) => (h1.h #<# h3.h)) AND ((h1.h #<=# h2.h AND h2.h #<=# h3.h) => (h1.h #<=# h3.h)) AND ((h1.h = h2.h AND h2.h = h3.h) => (h1.h = h3.h)) ) from testhstore2 h1, testhstore2 h2, testhstore2 h3;
+  every 
+ -------
+  t
+ (1 row)
+ 
+ -- sort/hash
+ select count(distinct h) from testhstore;
+  count 
+ -------
+    885
+ (1 row)
+ 
+ set enable_hashagg = false;
+ select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2;
+  count 
+ -------
+    885
+ (1 row)
+ 
+ set enable_hashagg = true;
+ set enable_sort = false;
+ select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2;
+  count 
+ -------
+    885
+ (1 row)
+ 
+ set enable_sort = true;
+ -- btree
+ drop index hidx;
+ create index hidx on testhstore using btree (h);
+ set enable_seqscan=off;
+ select count(*) from testhstore where h #># 'p=>1';
+  count 
+ -------
+    125
+ (1 row)
+ 
+ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t';
+  count 
+ -------
+      1
+ (1 row)
+ 
Index: contrib/hstore/sql/hstore.sql
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/hstore/sql/hstore.sql,v
retrieving revision 1.4
diff -c -r1.4 hstore.sql
*** contrib/hstore/sql/hstore.sql	14 Mar 2007 14:21:53 -0000	1.4
--- contrib/hstore/sql/hstore.sql	8 Jul 2009 18:39:01 -0000
***************
*** 65,70 ****
--- 65,77 ----
  select ('aa=>NULL, c=>d , b=>16'::hstore->'aa') is null;
  select ('aa=>"NULL", c=>d , b=>16'::hstore->'aa') is null;
  
+ -- -> array operator
+ 
+ select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['aa','c'];
+ select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['c','aa'];
+ select 'aa=>NULL, c=>d , b=>16'::hstore -> ARRAY['aa','c',null];
+ select 'aa=>1, c=>3, b=>2, d=>4'::hstore -> ARRAY[['b','d'],['aa','c']];
+ 
  -- exists/defined
  
  select exist('a=>NULL, b=>qq', 'a');
***************
*** 75,80 ****
--- 82,101 ----
  select defined('a=>NULL, b=>qq', 'b');
  select defined('a=>NULL, b=>qq', 'c');
  select defined('a=>"NULL", b=>qq', 'a');
+ select hstore 'a=>NULL, b=>qq' ? 'a';
+ select hstore 'a=>NULL, b=>qq' ? 'b';
+ select hstore 'a=>NULL, b=>qq' ? 'c';
+ select hstore 'a=>"NULL", b=>qq' ? 'a';
+ select hstore 'a=>NULL, b=>qq' ?| ARRAY['a','b'];
+ select hstore 'a=>NULL, b=>qq' ?| ARRAY['b','a'];
+ select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','a'];
+ select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','d'];
+ select hstore 'a=>NULL, b=>qq' ?| '{}'::text[];
+ select hstore 'a=>NULL, b=>qq' ?& ARRAY['a','b'];
+ select hstore 'a=>NULL, b=>qq' ?& ARRAY['b','a'];
+ select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','a'];
+ select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','d'];
+ select hstore 'a=>NULL, b=>qq' ?& '{}'::text[];
  
  -- delete 
  
***************
*** 83,88 ****
--- 104,140 ----
  select delete('a=>1 , b=>2, c=>3'::hstore, 'b');
  select delete('a=>1 , b=>2, c=>3'::hstore, 'c');
  select delete('a=>1 , b=>2, c=>3'::hstore, 'd');
+ select 'a=>1 , b=>2, c=>3'::hstore - 'a'::text;
+ select 'a=>null , b=>2, c=>3'::hstore - 'a'::text;
+ select 'a=>1 , b=>2, c=>3'::hstore - 'b'::text;
+ select 'a=>1 , b=>2, c=>3'::hstore - 'c'::text;
+ select 'a=>1 , b=>2, c=>3'::hstore - 'd'::text;
+ 
+ -- delete (array)
+ 
+ select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','e']);
+ select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','b']);
+ select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['a','c']);
+ select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY[['b'],['c'],['a']]);
+ select delete('a=>1 , b=>2, c=>3'::hstore, '{}'::text[]);
+ select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','e'];
+ select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','b'];
+ select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c'];
+ select 'a=>1 , b=>2, c=>3'::hstore - ARRAY[['b'],['c'],['a']];
+ select 'a=>1 , b=>2, c=>3'::hstore - '{}'::text[];
+ 
+ -- delete (hstore)
+ 
+ select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>4, b=>2'::hstore);
+ select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>NULL, c=>3'::hstore);
+ select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>1, b=>2, c=>3'::hstore);
+ select delete('aa=>1 , b=>2, c=>3'::hstore, 'b=>2'::hstore);
+ select delete('aa=>1 , b=>2, c=>3'::hstore, ''::hstore);
+ select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>4, b=>2'::hstore;
+ select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>NULL, c=>3'::hstore;
+ select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>1, b=>2, c=>3'::hstore;
+ select 'aa=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore;
+ select 'aa=>1 , b=>2, c=>3'::hstore - ''::hstore;
  
  -- ||
  select 'aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f';
***************
*** 97,102 ****
--- 149,183 ----
  select 'a=>g, b=>c'::hstore || ( 'b'=>'NULL' );
  select 'a=>g, b=>c'::hstore || ( 'b'=>NULL );
  
+ -- => arrays
+ select ARRAY['a','b','asd'] => ARRAY['g','h','i'];
+ select ARRAY['a','b','asd'] => ARRAY['g','h',NULL];
+ select ARRAY['z','y','x'] => ARRAY['1','2','3'];
+ select ARRAY['aaa','bb','c','d'] => ARRAY[null::text,null,null,null];
+ select ARRAY['aaa','bb','c','d'] => null;
+ select hstore 'aa=>1, b=>2, c=>3' => ARRAY['g','h','i'];
+ select hstore 'aa=>1, b=>2, c=>3' => ARRAY['c','b'];
+ select hstore 'aa=>1, b=>2, c=>3' => ARRAY['aa','b'];
+ select hstore 'aa=>1, b=>2, c=>3' => ARRAY['c','b','aa'];
+ 
+ -- records
+ select hstore(v) from (values (1, 'foo', 1.2, 3::float8)) v(a,b,c,d);
+ create table testhstore1 (a integer, b text, c numeric, d float8);
+ insert into testhstore1 values (1, 'foo', 1.2, 3::float8);
+ select hstore(v) from testhstore1 v;
+ select hstore(null::testhstore1);
+ select populate_record(v, ('c' => '3.45')) from testhstore1 v;
+ select populate_record(v, ('d' => '3.45')) from testhstore1 v;
+ select populate_record(v, ('c' => null)) from testhstore1 v;
+ select populate_record(v, ('b' => 'foo') || ('a' => '123')) from testhstore1 v;
+ select populate_record(null::testhstore1, ('c' => '3.45') || ('a' => '123')) from testhstore1 v;
+ select v #= ('c' => '3.45') from testhstore1 v;
+ select v #= ('d' => '3.45') from testhstore1 v;
+ select v #= ('c' => null) from testhstore1 v;
+ select v #= (('b' => 'foo') || ('a' => '123')) from testhstore1 v;
+ select null::testhstore1 #= (('c' => '3.45') || ('a' => '123')) from testhstore1 v;
+ select v #= h from testhstore1 v, (values (hstore 'a=>123',1),('b=>foo,c=>3.21',2),('a=>null',3),('e=>blah',4)) x(h,i) order by i;
+ 
  -- keys/values
  select akeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
  select akeys('""=>1');
***************
*** 132,137 ****
--- 213,220 ----
  select count(*) from testhstore where h @> 'wait=>CC';
  select count(*) from testhstore where h @> 'wait=>CC, public=>t';
  select count(*) from testhstore where h ? 'public';
+ select count(*) from testhstore where h ?| ARRAY['public','disabled'];
+ select count(*) from testhstore where h ?& ARRAY['public','disabled'];
  
  create index hidx on testhstore using gist(h);
  set enable_seqscan=off;
***************
*** 140,145 ****
--- 223,230 ----
  select count(*) from testhstore where h @> 'wait=>CC';
  select count(*) from testhstore where h @> 'wait=>CC, public=>t';
  select count(*) from testhstore where h ? 'public';
+ select count(*) from testhstore where h ?| ARRAY['public','disabled'];
+ select count(*) from testhstore where h ?& ARRAY['public','disabled'];
  
  drop index hidx;
  create index hidx on testhstore using gin (h);
***************
*** 149,154 ****
--- 234,265 ----
  select count(*) from testhstore where h @> 'wait=>CC';
  select count(*) from testhstore where h @> 'wait=>CC, public=>t';
  select count(*) from testhstore where h ? 'public';
+ select count(*) from testhstore where h ?| ARRAY['public','disabled'];
+ select count(*) from testhstore where h ?& ARRAY['public','disabled'];
  
  select count(*) from (select (each(h)).key from testhstore) as wow ;
  select key, count(*) from (select (each(h)).key from testhstore) as wow group by key order by count desc, key;
+ 
+ -- btree opclass transitivity
+ set enable_seqscan=on;
+ create table testhstore2 as select * from testhstore limit 50;
+ create function bool_imp(boolean,boolean) returns boolean language sql as $f$ select (not $1) or $2; $f$;
+ create operator => (leftarg = boolean, rightarg = boolean, procedure = bool_imp);
+ select every( ((h1.h #># h2.h AND h2.h #># h3.h) => (h1.h #># h3.h)) AND ((h1.h #>=# h2.h AND h2.h #>=# h3.h) => (h1.h #>=# h3.h)) AND ((h1.h #<# h2.h AND h2.h #<# h3.h) => (h1.h #<# h3.h)) AND ((h1.h #<=# h2.h AND h2.h #<=# h3.h) => (h1.h #<=# h3.h)) AND ((h1.h = h2.h AND h2.h = h3.h) => (h1.h = h3.h)) ) from testhstore2 h1, testhstore2 h2, testhstore2 h3;
+ 
+ -- sort/hash
+ select count(distinct h) from testhstore;
+ set enable_hashagg = false;
+ select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2;
+ set enable_hashagg = true;
+ set enable_sort = false;
+ select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2;
+ set enable_sort = true;
+ 
+ -- btree
+ drop index hidx;
+ create index hidx on testhstore using btree (h);
+ set enable_seqscan=off;
+ 
+ select count(*) from testhstore where h #># 'p=>1';
+ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t';
Index: doc/src/sgml/hstore.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/hstore.sgml,v
retrieving revision 1.3
diff -c -r1.3 hstore.sgml
*** doc/src/sgml/hstore.sgml	15 Mar 2009 22:05:17 -0000	1.3
--- doc/src/sgml/hstore.sgml	8 Jul 2009 18:39:03 -0000
***************
*** 15,23 ****
   </para>
  
   <para>
!   In the current implementation, neither the key nor the value
!   string can exceed 65535 bytes in length; an error will be thrown if this
!   limit is exceeded. These maximum lengths may change in future releases.
   </para>
  
   <sect2>
--- 15,23 ----
   </para>
  
   <para>
!   In the current implementation, there are no limits on the length of keys
!   and values, other than the 1GB limit that applies to all variable-length
!   types (including the hstore value itself).
   </para>
  
   <sect2>
***************
*** 88,93 ****
--- 88,100 ----
       </row>
  
       <row>
+       <entry><type>hstore</> <literal>-&gt;</> <type>text[]</></entry>
+       <entry>get values for keys (null if not present)</entry>
+       <entry><literal>'a=&gt;x, b=&gt;y, c=&gt;z'::hstore -&gt; ARRAY['c','a']</literal></entry>
+       <entry><literal>{"z","x"}</literal></entry>
+      </row>
+ 
+      <row>
        <entry><type>text</> <literal>=&gt;</> <type>text</></entry>
        <entry>make single-item <type>hstore</></entry>
        <entry><literal>'a' =&gt; 'b'</literal></entry>
***************
*** 95,100 ****
--- 102,121 ----
       </row>
  
       <row>
+       <entry><type>text[]</> <literal>=&gt;</> <type>text[]</></entry>
+       <entry>construct an <type>hstore</> value from separate key/value arrays</entry>
+       <entry><literal>ARRAY['a','b'] =&gt; ARRAY['1','2']</literal></entry>
+       <entry><literal>"a"=&gt;"1","b"=&gt;"2"</literal></entry>
+      </row>
+ 
+      <row>
+       <entry><type>hstore</> <literal>=&gt;</> <type>text[]</></entry>
+       <entry>construct an <type>hstore</> value as a slice of an existing <type>hstore</></entry>
+       <entry><literal>'a=&gt;1,b=&gt;2,c=&gt;3'::hstore =&gt; ARRAY['b','c']</literal></entry>
+       <entry><literal>"b"=&gt;"2", "c"=&gt;"3"</literal></entry>
+      </row>
+ 
+      <row>
        <entry><type>hstore</> <literal>||</> <type>hstore</></entry>
        <entry>concatenation</entry>
        <entry><literal>'a=&gt;b, c=&gt;d'::hstore || 'c=&gt;x, d=&gt;q'::hstore</literal></entry>
***************
*** 109,114 ****
--- 130,149 ----
       </row>
  
       <row>
+       <entry><type>hstore</> <literal>?&</> <type>text[]</></entry>
+       <entry>does <type>hstore</> contain all specified keys?</entry>
+       <entry><literal>'a=&gt;1,b=&gt;2'::hstore ?& ARRAY['a','b']</literal></entry>
+       <entry><literal>t</literal></entry>
+      </row>
+ 
+      <row>
+       <entry><type>hstore</> <literal>?|</> <type>text[]</></entry>
+       <entry>does <type>hstore</> contain any of the specified keys?</entry>
+       <entry><literal>'a=&gt;1,b=&gt;2'::hstore ? ARRAY['b','c']</literal></entry>
+       <entry><literal>t</literal></entry>
+      </row>
+ 
+      <row>
        <entry><type>hstore</> <literal>@&gt;</> <type>hstore</></entry>
        <entry>does left operand contain right?</entry>
        <entry><literal>'a=&gt;b, b=&gt;1, c=&gt;NULL'::hstore @&gt; 'b=&gt;1'</literal></entry>
***************
*** 122,127 ****
--- 157,190 ----
        <entry><literal>f</literal></entry>
       </row>
  
+      <row>
+       <entry><type>hstore</> <literal>-</> <type>text</></entry>
+       <entry>delete key from left operand</entry>
+       <entry><literal>'a=&gt;1, b=&gt;2, c=&gt;3'::hstore - 'b'::text</literal></entry>
+       <entry><literal>"a"=&gt;"1", "c"=&gt;"3"</literal></entry>
+      </row>
+ 
+      <row>
+       <entry><type>hstore</> <literal>-</> <type>text[]</></entry>
+       <entry>delete keys from left operand</entry>
+       <entry><literal>'a=&gt;1, b=&gt;2, c=&gt;3'::hstore - ARRAY['a','b']</literal></entry>
+       <entry><literal>"c"=&gt;"3"</literal></entry>
+      </row>
+ 
+      <row>
+       <entry><type>hstore</> <literal>-</> <type>hstore</></entry>
+       <entry>delete matching key/value pairs from left operand</entry>
+       <entry><literal>'a=&gt;1, b=&gt;2, c=&gt;3'::hstore - 'a=&gt;4, b=&gt;2'::hstore</literal></entry>
+       <entry><literal>"a"=&gt;"1", "c"=&gt;"3"</literal></entry>
+      </row>
+ 
+      <row>
+       <entry><type>record</> <literal>#=</> <type>hstore</></entry>
+       <entry>modify fields in record according to values from hstore</entry>
+       <entry><literal>see below under <function>populate_record()</></literal></entry>
+       <entry><literal></literal></entry>
+      </row>
+ 
      </tbody>
     </tgroup>
    </table>
***************
*** 150,155 ****
--- 213,226 ----
  
      <tbody>
       <row>
+       <entry><function>hstore(record)</function></entry>
+       <entry><type>hstore</type></entry>
+       <entry>construct an <type>hstore</> from a record or row</entry>
+       <entry><literal>hstore(ROW(1,2))</literal></entry>
+       <entry><literal>f1=&gt;1,f2=&gt;2</literal></entry>
+      </row>
+ 
+      <row>
        <entry><function>akeys(hstore)</function></entry>
        <entry><type>text[]</type></entry>
        <entry>get <type>hstore</>'s keys as array</entry>
***************
*** 227,248 ****
        <entry><literal>"a"=>"1"</literal></entry>
       </row>
  
      </tbody>
     </tgroup>
    </table>
   </sect2>
  
   <sect2>
    <title>Indexes</title>
  
    <para>
!    <type>hstore</> has index support for <literal>@&gt;</> and <literal>?</>
!    operators.  You can use either GiST or GIN index types.  For example:
    </para>
    <programlisting>
! CREATE INDEX hidx ON testhstore USING GIST(h);
  
! CREATE INDEX hidx ON testhstore USING GIN(h);
    </programlisting>
   </sect2>
  
--- 298,351 ----
        <entry><literal>"a"=>"1"</literal></entry>
       </row>
  
+      <row>
+       <entry><function>delete(hstore,text[])</function></entry>
+       <entry><type>hstore</type></entry>
+       <entry>delete any item matching any of the keys</entry>
+       <entry><literal>delete('a=&gt;1,b=&gt;2,c=&gt;3',ARRAY['a','b'])</literal></entry>
+       <entry><literal>"c"=>"3"</literal></entry>
+      </row>
+ 
+      <row>
+       <entry><function>delete(hstore,hstore)</function></entry>
+       <entry><type>hstore</type></entry>
+       <entry>delete any key/value pair exactly matching in the second argument</entry>
+       <entry><literal>delete('a=&gt;1,b=&gt;2','a=&gt;4,b=&gt;2'::hstore)</literal></entry>
+       <entry><literal>"a"=>"1"</literal></entry>
+      </row>
+ 
+      <row>
+       <entry><function>populate_record(record,hstore)</function></entry>
+       <entry><type>record</type></entry>
+       <entry>delete any key/value pair exactly matching in the second argument</entry>
+       <entry><literal>delete('a=&gt;1,b=&gt;2','a=&gt;4,b=&gt;2'::hstore)</literal></entry>
+       <entry><literal>"a"=>"1"</literal></entry>
+      </row>
+ 
      </tbody>
     </tgroup>
    </table>
+ 
+   <para>
+     NOTE: the function <function>populate_record</function> actually
+     uses the pseudo-type <type>anyelement</>, not <type>record</>, but
+     it will reject non-record types with an error.
+   </para>
+      
   </sect2>
  
   <sect2>
    <title>Indexes</title>
  
    <para>
!    <type>hstore</> has index support for <literal>@&gt;</>, <literal>?</>,
!    <literal>?&</> and <literal>?|</> operators.  You can use either
!    GiST or GIN index types.  For example:
    </para>
    <programlisting>
! CREATE INDEX hidx ON testhstore USING GIST (h);
  
! CREATE INDEX hidx ON testhstore USING GIN (h);
    </programlisting>
   </sect2>
  
***************
*** 321,326 ****
--- 424,433 ----
    <para>
     Teodor Sigaev <email>teodor@sigaev.ru</email>, Moscow, Delta-Soft Ltd., Russia
    </para>
+ 
+   <para>
+    With additional enhancements by Andrew Gierth <email>andrew@tao11.riddles.org.uk</email>, United Kingdom
+   </para>
   </sect2>
  
  </sect1>