]> granicus.if.org Git - postgresql/commitdiff
Assorted improvements in contrib/hstore.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 30 Sep 2009 19:50:22 +0000 (19:50 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 30 Sep 2009 19:50:22 +0000 (19:50 +0000)
Remove the 64K limit on the lengths of keys and values within an hstore.
(This changes the on-disk format, but the old format can still be read.)
Add support for btree/hash opclasses for hstore --- this is not so much
for actual indexing purposes as to allow use of GROUP BY, DISTINCT, etc.
Add various other new functions and operators.

Andrew Gierth

12 files changed:
contrib/hstore/Makefile
contrib/hstore/expected/hstore.out
contrib/hstore/hstore.h
contrib/hstore/hstore.sql.in
contrib/hstore/hstore_compat.c [new file with mode: 0644]
contrib/hstore/hstore_gin.c
contrib/hstore/hstore_gist.c
contrib/hstore/hstore_io.c
contrib/hstore/hstore_op.c
contrib/hstore/sql/hstore.sql
contrib/hstore/uninstall_hstore.sql
doc/src/sgml/hstore.sgml

index adb014d0d12bc1287dd1f8718ac72c8734bc27a3..bb69d7080550c8cb5ae302b5d1ab85db0232d772 100644 (file)
@@ -1,11 +1,12 @@
-# $PostgreSQL: pgsql/contrib/hstore/Makefile,v 1.6 2007/11/10 23:59:51 momjian Exp $
+# $PostgreSQL: pgsql/contrib/hstore/Makefile,v 1.7 2009/09/30 19:50:22 tgl Exp $
 
 subdir = contrib/hstore
 top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 
 MODULE_big = hstore
-OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o crc32.o
+OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \
+       crc32.o
 
 DATA_built = hstore.sql
 DATA = uninstall_hstore.sql
index eee23d3a05abc786a268abcf790391d54b22541f..34db7f571e30f0538272cfb0fcc8295bca9803a1 100644 (file)
@@ -278,6 +278,31 @@ select ('aa=>"NULL", c=>d , b=>16'::hstore->'aa') is null;
  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,6 +352,90 @@ select defined('a=>"NULL", b=>qq', 'a');
  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,6 +467,193 @@ select delete('a=>1 , b=>2, c=>3'::hstore, 'd');
  "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)
+
+select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b'::text)
+         = pg_column_size('a=>1, b=>2'::hstore);
+ ?column? 
+----------
+ t
+(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)
+
+select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c'])
+         = pg_column_size('b=>2'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
+select pg_column_size('a=>1 , b=>2, c=>3'::hstore - '{}'::text[])
+         = pg_column_size('a=>1, b=>2, c=>3'::hstore);
+ ?column? 
+----------
+ t
+(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 pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore)
+         = pg_column_size('a=>1, c=>3'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
+select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ''::hstore)
+         = pg_column_size('a=>1, b=>2, c=>3'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
 -- ||
 select 'aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f';
                  ?column?                  
@@ -389,6 +685,33 @@ select ''::hstore || 'cq=>l, b=>g, fg=>f';
  "b"=>"g", "cq"=>"l", "fg"=>"f"
 (1 row)
 
+select pg_column_size(''::hstore || ''::hstore) = pg_column_size(''::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
+select pg_column_size('aa=>1'::hstore || 'b=>2'::hstore)
+         = pg_column_size('aa=>1, b=>2'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
+select pg_column_size('aa=>1, b=>2'::hstore || ''::hstore)
+         = pg_column_size('aa=>1, b=>2'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
+select pg_column_size(''::hstore || 'aa=>1, b=>2'::hstore)
+         = pg_column_size('aa=>1, b=>2'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
 -- =>
 select 'a=>g, b=>c'::hstore || ( 'asd'=>'gf' );
             ?column?             
@@ -414,6 +737,367 @@ select 'a=>g, b=>c'::hstore || ( 'b'=>NULL );
  "a"=>"g", "b"=>NULL
 (1 row)
 
+select ('a=>g, b=>c'::hstore || ( NULL=>'b' )) is null;
+ ?column? 
+----------
+ t
+(1 row)
+
+select pg_column_size(('b'=>'gf'))
+         = pg_column_size('b=>gf'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
+select pg_column_size('a=>g, b=>c'::hstore || ('b'=>'gf'))
+         = pg_column_size('a=>g, b=>gf'::hstore);
+ ?column? 
+----------
+ t
+(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)
+
+select quote_literal('{}'::text[] => '{}'::text[]);
+ quote_literal 
+---------------
+ ''
+(1 row)
+
+select quote_literal('{}'::text[] => null);
+ quote_literal 
+---------------
+ ''
+(1 row)
+
+select ARRAY['a'] => '{}'::text[];  -- error
+ERROR:  arrays must have same bounds
+select '{}'::text[] => ARRAY['a'];  -- error
+ERROR:  arrays must have same bounds
+select pg_column_size(ARRAY['a','b','asd'] => ARRAY['g','h','i'])
+         = pg_column_size('a=>g, b=>h, asd=>i'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
+select pg_column_size(hstore 'aa=>1, b=>2, c=>3' => ARRAY['c','b'])
+         = pg_column_size('b=>2, c=>3'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
+select pg_column_size(hstore 'aa=>1, b=>2, c=>3' => ARRAY['c','b','aa'])
+         = pg_column_size('aa=>1, b=>2, c=>3'::hstore);
+ ?column? 
+----------
+ t
+(1 row)
+
+-- array input
+select '{}'::text[]::hstore;
+ hstore 
+--------
+(1 row)
+
+select ARRAY['a','g','b','h','asd']::hstore;
+ERROR:  array must have even number of elements
+select ARRAY['a','g','b','h','asd','i']::hstore;
+             array              
+--------------------------------
+ "a"=>"g", "b"=>"h", "asd"=>"i"
+(1 row)
+
+select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
+             array              
+--------------------------------
+ "a"=>"g", "b"=>"h", "asd"=>"i"
+(1 row)
+
+select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
+ERROR:  array must have two columns
+select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
+ERROR:  wrong number of array subscripts
+select hstore('{}'::text[]);
+ hstore 
+--------
+(1 row)
+
+select hstore(ARRAY['a','g','b','h','asd']);
+ERROR:  array must have even number of elements
+select hstore(ARRAY['a','g','b','h','asd','i']);
+             hstore             
+--------------------------------
+ "a"=>"g", "b"=>"h", "asd"=>"i"
+(1 row)
+
+select hstore(ARRAY[['a','g'],['b','h'],['asd','i']]);
+             hstore             
+--------------------------------
+ "a"=>"g", "b"=>"h", "asd"=>"i"
+(1 row)
+
+select hstore(ARRAY[['a','g','b'],['h','asd','i']]);
+ERROR:  array must have two columns
+select hstore(ARRAY[[['a','g'],['b','h'],['asd','i']]]);
+ERROR:  wrong number of array subscripts
+select hstore('[0:5]={a,g,b,h,asd,i}'::text[]);
+             hstore             
+--------------------------------
+ "a"=>"g", "b"=>"h", "asd"=>"i"
+(1 row)
+
+select hstore('[0:2][1:2]={{a,g},{b,h},{asd,i}}'::text[]);
+             hstore             
+--------------------------------
+ "a"=>"g", "b"=>"h", "asd"=>"i"
+(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 domain hstestdom1 as integer not null default 0;
+create table testhstore0 (a integer, b text, c numeric, d float8);
+create table testhstore1 (a integer, b text, c numeric, d float8, e hstestdom1);
+insert into testhstore0 values (1, 'foo', 1.2, 3::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", "e"=>"0"
+(1 row)
+
+select hstore(null::testhstore0);
+                   hstore                   
+--------------------------------------------
+ "a"=>NULL, "b"=>NULL, "c"=>NULL, "d"=>NULL
+(1 row)
+
+select hstore(null::testhstore1);
+                        hstore                         
+-------------------------------------------------------
+ "a"=>NULL, "b"=>NULL, "c"=>NULL, "d"=>NULL, "e"=>NULL
+(1 row)
+
+select pg_column_size(hstore(v))
+         = pg_column_size('a=>1, b=>"foo", c=>"1.2", d=>"3", e=>"0"'::hstore)
+  from testhstore1 v;
+ ?column? 
+----------
+ t
+(1 row)
+
+select populate_record(v, ('c' => '3.45')) from testhstore1 v;
+ populate_record  
+------------------
+ (1,foo,3.45,3,0)
+(1 row)
+
+select populate_record(v, ('d' => '3.45')) from testhstore1 v;
+  populate_record   
+--------------------
+ (1,foo,1.2,3.45,0)
+(1 row)
+
+select populate_record(v, ('e' => '123')) from testhstore1 v;
+  populate_record  
+-------------------
+ (1,foo,1.2,3,123)
+(1 row)
+
+select populate_record(v, ('e' => null)) from testhstore1 v;
+ERROR:  domain hstestdom1 does not allow null values
+select populate_record(v, ('c' => null)) from testhstore1 v;
+ populate_record 
+-----------------
+ (1,foo,,3,0)
+(1 row)
+
+select populate_record(v, ('b' => 'foo') || ('a' => '123')) from testhstore1 v;
+  populate_record  
+-------------------
+ (123,foo,1.2,3,0)
+(1 row)
+
+select populate_record(v, ('b' => 'foo') || ('e' => null)) from testhstore0 v;
+ populate_record 
+-----------------
+ (1,foo,1.2,3)
+(1 row)
+
+select populate_record(v, ('b' => 'foo') || ('e' => null)) from testhstore1 v;
+ERROR:  domain hstestdom1 does not allow null values
+select populate_record(v, '') from testhstore0 v;
+ populate_record 
+-----------------
+ (1,foo,1.2,3)
+(1 row)
+
+select populate_record(v, '') from testhstore1 v;
+ populate_record 
+-----------------
+ (1,foo,1.2,3,0)
+(1 row)
+
+select populate_record(null::testhstore1, ('c' => '3.45') || ('a' => '123'));
+ERROR:  domain hstestdom1 does not allow null values
+select populate_record(null::testhstore1, ('c' => '3.45') || ('e' => '123'));
+ populate_record 
+-----------------
+ (,,3.45,,123)
+(1 row)
+
+select populate_record(null::testhstore0, '');
+ populate_record 
+-----------------
+ (,,,)
+(1 row)
+
+select populate_record(null::testhstore1, '');
+ERROR:  domain hstestdom1 does not allow null values
+select v #= ('c' => '3.45') from testhstore1 v;
+     ?column?     
+------------------
+ (1,foo,3.45,3,0)
+(1 row)
+
+select v #= ('d' => '3.45') from testhstore1 v;
+      ?column?      
+--------------------
+ (1,foo,1.2,3.45,0)
+(1 row)
+
+select v #= ('e' => '123') from testhstore1 v;
+     ?column?      
+-------------------
+ (1,foo,1.2,3,123)
+(1 row)
+
+select v #= ('c' => null) from testhstore1 v;
+   ?column?   
+--------------
+ (1,foo,,3,0)
+(1 row)
+
+select v #= ('e' => null) from testhstore0 v;
+   ?column?    
+---------------
+ (1,foo,1.2,3)
+(1 row)
+
+select v #= ('e' => null) from testhstore1 v;
+ERROR:  domain hstestdom1 does not allow null values
+select v #= (('b' => 'foo') || ('a' => '123')) from testhstore1 v;
+     ?column?      
+-------------------
+ (123,foo,1.2,3,0)
+(1 row)
+
+select v #= (('b' => 'foo') || ('e' => '123')) from testhstore1 v;
+     ?column?      
+-------------------
+ (1,foo,1.2,3,123)
+(1 row)
+
+select v #= hstore '' from testhstore0 v;
+   ?column?    
+---------------
+ (1,foo,1.2,3)
+(1 row)
+
+select v #= hstore '' from testhstore1 v;
+    ?column?     
+-----------------
+ (1,foo,1.2,3,0)
+(1 row)
+
+select null::testhstore1 #= (('c' => '3.45') || ('a' => '123'));
+ERROR:  domain hstestdom1 does not allow null values
+select null::testhstore1 #= (('c' => '3.45') || ('e' => '123'));
+   ?column?    
+---------------
+ (,,3.45,,123)
+(1 row)
+
+select null::testhstore0 #= hstore '';
+ ?column? 
+----------
+ (,,,)
+(1 row)
+
+select null::testhstore1 #= hstore '';
+ERROR:  domain hstestdom1 does not allow null values
+select v #= h from testhstore1 v, (values (hstore 'a=>123',1),('b=>foo,c=>3.21',2),('a=>null',3),('e=>123',4),('f=>blah',5)) x(h,i) order by i;
+     ?column?      
+-------------------
+ (123,foo,1.2,3,0)
+ (1,foo,3.21,3,0)
+ (,foo,1.2,3,0)
+ (1,foo,1.2,3,123)
+ (1,foo,1.2,3,0)
+(5 rows)
+
 -- keys/values
 select akeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
     akeys     
@@ -440,9 +1124,9 @@ select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
 (1 row)
 
 select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL');
-   avals    
-------------
- {g,1,l,""}
+    avals     
+--------------
+ {g,1,l,NULL}
 (1 row)
 
 select avals('""=>1');
@@ -457,6 +1141,30 @@ select avals('');
  {}
 (1 row)
 
+select hstore_to_array('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore);
+     hstore_to_array     
+-------------------------
+ {b,g,aa,1,cq,l,fg,NULL}
+(1 row)
+
+select %% 'aa=>1, cq=>l, b=>g, fg=>NULL';
+        ?column?         
+-------------------------
+ {b,g,aa,1,cq,l,fg,NULL}
+(1 row)
+
+select hstore_to_matrix('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore);
+        hstore_to_matrix         
+---------------------------------
+ {{b,g},{aa,1},{cq,l},{fg,NULL}}
+(1 row)
+
+select %# 'aa=>1, cq=>l, b=>g, fg=>NULL';
+            ?column?             
+---------------------------------
+ {{b,g},{aa,1},{cq,l},{fg,NULL}}
+(1 row)
+
 select * from skeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
  skeys 
 -------
@@ -583,6 +1291,18 @@ select count(*) from testhstore where h ? 'public';
    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,6 +1329,18 @@ select count(*) from testhstore where h ? 'public';
    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,6 +1368,18 @@ select count(*) from testhstore where h ? 'public';
    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,3 +1413,48 @@ select key, count(*) from (select (each(h)).key from testhstore) as wow group by
  abstract  |   161
 (22 rows)
 
+-- 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)
+
+select distinct * from (values (hstore '' || ''),('')) v(h);
+ h 
+---
+(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 e8ea58b567272cfe068b16fa9d21e3980faec0bc..495ac1afc9b986730504d0188bf98b2b2ac18706 100644 (file)
 /*
- * $PostgreSQL: pgsql/contrib/hstore/hstore.h,v 1.8 2009/06/11 14:48:51 momjian Exp $
+ * $PostgreSQL: pgsql/contrib/hstore/hstore.h,v 1.9 2009/09/30 19:50:22 tgl Exp $
  */
 #ifndef __HSTORE_H__
 #define __HSTORE_H__
 
 #include "fmgr.h"
+#include "utils/array.h"
 
 
+/*
+ * HEntry: there is one of these for each key _and_ value in an hstore
+ *
+ * the position offset points to the _end_ so that we can get the length
+ * by subtraction from the previous entry.  the ISFIRST flag lets us tell
+ * whether there is a previous entry.
+ */
 typedef struct
 {
-       uint16          keylen;
-       uint16          vallen;
-       uint32
-                               valisnull:1,
-                               pos:31;
+       uint32          entry;
 } 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
+#define HENTRY_ISFIRST 0x80000000
+#define HENTRY_ISNULL  0x40000000
+#define HENTRY_POSMASK 0x3FFFFFFF
 
+/* note possible multiple evaluations, also access to prior array element */
+#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]))
+
+/*
+ * determined by the size of "endpos" (ie HENTRY_POSMASK), 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;
-       char            data[1];
+       uint32          size_;                  /* flags and number of items in hstore */
+       /* array of HEntry follows */
 } 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 ) )
+/*
+ * it's not possible to get more than 2^28 items into an hstore,
+ * so we reserve the top few bits of the size field. See hstore_compat.c
+ * for one reason why.  Some bits are left for future use here.
+ */
+#define HS_FLAG_NEWVERSION 0x80000000
+
+#define HS_COUNT(hsp_) ((hsp_)->size_ & 0x0FFFFFFF)
+#define HS_SETCOUNT(hsp_,c_) ((hsp_)->size_ = (c_) | HS_FLAG_NEWVERSION)
+
+
+#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) + HS_COUNT((HStore*)(x)) * 2) )
+
+/* note multiple/non evaluations */
+#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. Exception: the in-place upgrade in hstore_compat.c
+ * messes with entries directly.
+ */
+
+/*
+ * 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 {                                                                                                                    \
+               int     buflen = (ptr_) - (buf_);                                                               \
+               if ((count_))                                                                                           \
+                       ARRPTR(hsp_)[0].entry |= HENTRY_ISFIRST;                                \
+               if ((count_) != HS_COUNT((hsp_)))                                                       \
+               {                                                                                                                       \
+                       HS_SETCOUNT((hsp_),(count_));                                                   \
+                       memmove(STRPTR(hsp_), (buf_), buflen);                                  \
+               }                                                                                                                       \
+               SET_VARSIZE((hsp_), CALCDATASIZE((count_), buflen));            \
+       } while (0)
+
+/* ensure the varlena size of an existing hstore is correct */
+#define HS_FIXSIZE(hsp_,count_)                                                                                        \
+       do {                                                                                                                            \
+               int bl = (count_) ? HSE_ENDPOS(ARRPTR(hsp_)[2*(count_)-1]) : 0; \
+               SET_VARSIZE((hsp_), CALCDATASIZE((count_),bl));                                 \
+       } while (0)
+
+/* DatumGetHStoreP includes support for reading old-format hstore values */
+extern HStore *hstoreUpgrade(Datum orig);
 
+#define DatumGetHStoreP(d) hstoreUpgrade(d)
 
-#define PG_GETARG_HS(x) ((HStore*)PG_DETOAST_DATUM(PG_GETARG_DATUM(x)))
+#define PG_GETARG_HS(x) DatumGetHStoreP(PG_GETARG_DATUM(x))
 
+
+/*
+ * Pairs is a "decompressed" representation of one key/value pair.
+ * The two strings are not necessarily null-terminated.
+ */
 typedef struct
 {
        char       *key;
        char       *val;
-       uint16          keylen;
-       uint16          vallen;
-       bool            isnull;
-       bool            needfree;
+       size_t          keylen;
+       size_t          vallen;
+       bool            isnull;                 /* value is null? */
+       bool            needfree;               /* need to pfree the value? */
 } Pairs;
 
-int                    comparePairs(const void *a, const void *b);
-int                    uniquePairs(Pairs *a, int4 l, int4 *buflen);
+extern int     hstoreUniquePairs(Pairs *a, int4 l, int4 *buflen);
+extern HStore *hstorePairs(Pairs *pairs, int4 pcount, int4 buflen);
+
+extern size_t hstoreCheckKeyLen(size_t len);
+extern size_t hstoreCheckValLen(size_t len);
 
-size_t         hstoreCheckKeyLen(size_t len);
-size_t         hstoreCheckValLen(size_t len);
+extern int     hstoreFindKey(HStore *hs, int *lowbound, char *key, int keylen);
+extern Pairs *hstoreArrayToPairs(ArrayType *a, int *npairs);
 
 #define HStoreContainsStrategyNumber   7
 #define HStoreExistsStrategyNumber             9
+#define HStoreExistsAnyStrategyNumber  10
+#define HStoreExistsAllStrategyNumber  11
+#define HStoreOldContainsStrategyNumber        13              /* backwards compatibility */
+
+/*
+ * defining HSTORE_POLLUTE_NAMESPACE=0 will prevent use of old function names;
+ * 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); } \
+       extern int no_such_variable
+#else
+#define HSTORE_POLLUTE(newname_,oldname_) \
+       extern int no_such_variable
+#endif
 
 #endif   /* __HSTORE_H__ */
index 29a78ed0529cb14f27ba73cb608d92a8514c262f..75c4367ccc0d3c159d6477fb881d5cf2e3575d1c 100644 (file)
@@ -1,4 +1,4 @@
-/* $PostgreSQL: pgsql/contrib/hstore/hstore.sql.in,v 1.11 2009/06/11 18:30:03 tgl Exp $ */
+/* $PostgreSQL: pgsql/contrib/hstore/hstore.sql.in,v 1.12 2009/09/30 19:50:22 tgl Exp $ */
 
 -- Adjust this setting to control where the objects get created.
 SET search_path = public;
@@ -8,23 +8,40 @@ CREATE TYPE hstore;
 CREATE OR REPLACE FUNCTION hstore_in(cstring)
 RETURNS hstore
 AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT;
+LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION hstore_out(hstore)
 RETURNS cstring
 AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT;
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION hstore_recv(internal)
+RETURNS hstore
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION hstore_send(hstore)
+RETURNS bytea
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
 
 CREATE TYPE hstore (
         INTERNALLENGTH = -1,
         INPUT = hstore_in,
         OUTPUT = hstore_out,
+        RECEIVE = hstore_recv,
+        SEND = hstore_send,
         STORAGE = extended
 );
 
+CREATE OR REPLACE FUNCTION hstore_version_diag(hstore)
+RETURNS integer
+AS 'MODULE_PATHNAME','hstore_version_diag'
+LANGUAGE C STRICT IMMUTABLE;
+
 CREATE OR REPLACE FUNCTION fetchval(hstore,text)
 RETURNS text
-AS 'MODULE_PATHNAME'
+AS 'MODULE_PATHNAME','hstore_fetchval'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR -> (
@@ -33,14 +50,36 @@ CREATE OPERATOR -> (
        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','exists'
+AS 'MODULE_PATHNAME','hstore_exists'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION exist(hstore,text)
 RETURNS bool
-AS 'MODULE_PATHNAME','exists'
+AS 'MODULE_PATHNAME','hstore_exists'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR ? (
@@ -51,24 +90,78 @@ CREATE OPERATOR ? (
        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','defined'
+AS 'MODULE_PATHNAME','hstore_defined'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION defined(hstore,text)
 RETURNS bool
-AS 'MODULE_PATHNAME','defined'
+AS 'MODULE_PATHNAME','hstore_defined'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION delete(hstore,text)
 RETURNS hstore
-AS 'MODULE_PATHNAME','delete'
+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'
+AS 'MODULE_PATHNAME','hstore_concat'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR || (
@@ -79,12 +172,12 @@ CREATE OPERATOR || (
 
 CREATE OR REPLACE FUNCTION hs_contains(hstore,hstore)
 RETURNS bool
-AS 'MODULE_PATHNAME'
+AS 'MODULE_PATHNAME','hstore_contains'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION hs_contained(hstore,hstore)
 RETURNS bool
-AS 'MODULE_PATHNAME'
+AS 'MODULE_PATHNAME','hstore_contained'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR @> (
@@ -126,57 +219,237 @@ CREATE OPERATOR ~ (
 
 CREATE OR REPLACE FUNCTION tconvert(text,text)
 RETURNS hstore
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE; -- not STRICT
+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 = tconvert
+       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 FUNCTION hstore(text[])
+RETURNS hstore
+AS 'MODULE_PATHNAME', 'hstore_from_array'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE CAST (text[] AS hstore)
+  WITH FUNCTION hstore(text[]);
+
+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 hstore_to_array(hstore)
+RETURNS text[]
+AS 'MODULE_PATHNAME','hstore_to_array'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR %% (
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_to_array
+);
+
+CREATE OR REPLACE FUNCTION hstore_to_matrix(hstore)
+RETURNS text[]
+AS 'MODULE_PATHNAME','hstore_to_matrix'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR %# (
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_to_matrix
 );
 
 CREATE OR REPLACE FUNCTION akeys(hstore)
-RETURNS _text
-AS 'MODULE_PATHNAME'
+RETURNS text[]
+AS 'MODULE_PATHNAME','hstore_akeys'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION avals(hstore)
-RETURNS _text
-AS 'MODULE_PATHNAME'
+RETURNS text[]
+AS 'MODULE_PATHNAME','hstore_avals'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION skeys(hstore)
 RETURNS setof text
-AS 'MODULE_PATHNAME'
+AS 'MODULE_PATHNAME','hstore_skeys'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION svals(hstore)
 RETURNS setof text
-AS 'MODULE_PATHNAME'
+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'
+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 support
 
--- define the GiST support methods
+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);
+
+-- hash support
+
+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);
+
+-- GiST support
 
 CREATE TYPE ghstore;
 
 CREATE OR REPLACE FUNCTION ghstore_in(cstring)
 RETURNS ghstore
 AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT;
+LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION ghstore_out(ghstore)
 RETURNS cstring
 AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT;
+LANGUAGE C STRICT IMMUTABLE;
 
 CREATE TYPE ghstore (
         INTERNALLENGTH = -1,
@@ -219,12 +492,13 @@ RETURNS bool
 AS 'MODULE_PATHNAME'
 LANGUAGE C IMMUTABLE STRICT;
 
--- register the opclass for indexing (not as default)
 CREATE OPERATOR CLASS gist_hstore_ops
 DEFAULT FOR TYPE hstore USING gist
 AS
-               OPERATOR        7       @> ,
-               OPERATOR        9       ?(hstore,text) ,
+       OPERATOR        7       @> ,
+       OPERATOR        9       ?(hstore,text) ,
+       OPERATOR        10      ?|(hstore,text[]) ,
+       OPERATOR        11      ?&(hstore,text[]) ,
         --OPERATOR        8       <@ ,
         OPERATOR        13      @ ,
         --OPERATOR        14      ~ ,
@@ -237,7 +511,7 @@ AS
         FUNCTION        7       ghstore_same (internal, internal, internal),
         STORAGE         ghstore;
 
--- define the GIN support methods
+-- GIN support
 
 CREATE OR REPLACE FUNCTION gin_extract_hstore(internal, internal)
 RETURNS internal
@@ -257,10 +531,12 @@ LANGUAGE C IMMUTABLE STRICT;
 CREATE OPERATOR CLASS gin_hstore_ops
 DEFAULT FOR TYPE hstore USING gin
 AS
-       OPERATOR        7       @> ,
+       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;
+       STORAGE         text;
diff --git a/contrib/hstore/hstore_compat.c b/contrib/hstore/hstore_compat.c
new file mode 100644 (file)
index 0000000..e2c2b55
--- /dev/null
@@ -0,0 +1,376 @@
+/*
+ * $PostgreSQL: pgsql/contrib/hstore/hstore_compat.c,v 1.1 2009/09/30 19:50:22 tgl Exp $
+ *
+ * Notes on old/new hstore format disambiguation.
+ *
+ * There are three formats to consider:
+ * 1) old contrib/hstore (referred to as hstore-old)
+ * 2) prerelease pgfoundry hstore
+ * 3) new contrib/hstore
+ *
+ * (2) and (3) are identical except for the HS_FLAG_NEWVERSION
+ * bit, which is set in (3) but not (2).
+ *
+ * Values that are already in format (3), or which are
+ * unambiguously in format (2), are handled by the first
+ * "return immediately" test in hstoreUpgrade().
+ *
+ * To stress a point: we ONLY get here with possibly-ambiguous
+ * values if we're doing some sort of in-place migration from an
+ * old prerelease pgfoundry hstore-new; and we explicitly don't
+ * support that without fixing up any potentially padded values
+ * first. Most of the code here is serious overkill, but the
+ * performance penalty isn't serious (especially compared to the
+ * palloc() that we have to do anyway) and the belt-and-braces
+ * validity checks provide some reassurance. (If for some reason
+ * we get a value that would have worked on the old code, but
+ * which would be botched by the conversion code, the validity
+ * checks will fail it first so we get an error rather than bad
+ * data.)
+ *
+ * Note also that empty hstores are the same in (2) and (3), so
+ * there are some special-case paths for them.
+ *
+ * We tell the difference between formats (2) and (3) as follows (but
+ * note that there are some edge cases where we can't tell; see
+ * comments in hstoreUpgrade):
+ *
+ * First, since there must be at least one entry, we look at
+ * how the bits line up. The new format looks like:
+ *
+ * 10kkkkkkkkkkkkkkkkkkkkkkkkkkkkkk  (k..k = keylen)
+ * 0nvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv  (v..v = keylen+vallen)
+ *
+ * The old format looks like one of these, depending on endianness
+ * and bitfield layout: (k..k = keylen, v..v = vallen, p..p = pos,
+ * n = isnull)
+ *
+ * kkkkkkkkkkkkkkkkvvvvvvvvvvvvvvvv
+ * nppppppppppppppppppppppppppppppp
+ *
+ * kkkkkkkkkkkkkkkkvvvvvvvvvvvvvvvv
+ * pppppppppppppppppppppppppppppppn
+ *
+ * vvvvvvvvvvvvvvvvkkkkkkkkkkkkkkkk
+ * nppppppppppppppppppppppppppppppp
+ *
+ * vvvvvvvvvvvvvvvvkkkkkkkkkkkkkkkk
+ * pppppppppppppppppppppppppppppppn   (usual i386 format)
+ *
+ * If the entry is in old format, for the first entry "pos" must be 0.
+ * We can obviously see that either keylen or vallen must be >32768
+ * for there to be any ambiguity (which is why lengths less than that
+ * are fasttracked in hstore.h) Since "pos"==0, the "v" field in the
+ * new-format interpretation can only be 0 or 1, which constrains all
+ * but three bits of the old-format's k and v fields. But in addition
+ * to all of this, the data length implied by the keylen and vallen
+ * must fit in the varlena size. So the only ambiguous edge case for
+ * hstores with only one entry occurs between a new-format entry with
+ * an excess (~32k) of padding, and an old-format entry. But we know
+ * which format to use in that case based on how we were compiled, so
+ * no actual data corruption can occur.
+ *
+ * If there is more than one entry, the requirement that keys do not
+ * decrease in length, and that positions increase contiguously, and
+ * that the end of the data not be beyond the end of the varlena
+ * itself, disambiguates in almost all other cases. There is a small
+ * set of ambiguous cases which could occur if the old-format value
+ * has a large excess of padding and just the right pattern of key
+ * sizes, but these are also handled based on how we were compiled.
+ *
+ * The otherwise undocumented function hstore_version_diag is provided
+ * for testing purposes.
+ */
+#include "postgres.h"
+
+#include "funcapi.h"
+
+#include "hstore.h"
+
+/*
+ * This is the structure used for entries in the old contrib/hstore
+ * implementation. Notice that this is the same size as the new entry
+ * (two 32-bit words per key/value pair) and that the header is the
+ * same, so the old and new versions of ARRPTR, STRPTR, CALCDATASIZE
+ * etc. are compatible.
+ *
+ * If the above statement isn't true on some bizarre platform, we're
+ * a bit hosed (see Assert in hstoreValidOldFormat).
+ */
+typedef struct
+{
+       uint16          keylen;
+       uint16          vallen;
+       uint32
+                               valisnull:1,
+                               pos:31;
+} HOldEntry;
+
+static int hstoreValidNewFormat(HStore *hs);
+static int hstoreValidOldFormat(HStore *hs);
+
+
+/*
+ * Validity test for a new-format hstore.
+ *  0 = not valid
+ *  1 = valid but with "slop" in the length
+ *  2 = exactly valid
+ */
+static int
+hstoreValidNewFormat(HStore *hs)
+{
+       int count = HS_COUNT(hs);
+       HEntry *entries = ARRPTR(hs);
+       int buflen = (count) ? HSE_ENDPOS(entries[2*(count)-1]) : 0;
+       int vsize = CALCDATASIZE(count,buflen);
+       int i;
+
+       if (hs->size_ & HS_FLAG_NEWVERSION)
+               return 2;
+
+       if (count == 0)
+               return 2;
+
+       if (!HSE_ISFIRST(entries[0]))
+               return 0;
+
+       if (vsize > VARSIZE(hs))
+               return 0;
+
+       /* entry position must be nondecreasing */
+
+       for (i = 1; i < 2*count; ++i)
+       {
+               if (HSE_ISFIRST(entries[i])
+                       || (HSE_ENDPOS(entries[i]) < HSE_ENDPOS(entries[i-1])))
+                       return 0;
+       }
+
+       /* key length must be nondecreasing and keys must not be null */
+
+       for (i = 1; i < count; ++i)
+       {
+               if (HS_KEYLEN(entries,i) < HS_KEYLEN(entries,i-1))
+                       return 0;
+               if (HSE_ISNULL(entries[2*i]))
+                       return 0;
+       }
+
+       if (vsize != VARSIZE(hs))
+               return 1;
+
+       return 2;
+}
+
+/*
+ * Validity test for an old-format hstore.
+ *  0 = not valid
+ *  1 = valid but with "slop" in the length
+ *  2 = exactly valid
+ */
+static int
+hstoreValidOldFormat(HStore *hs)
+{
+       int count = hs->size_;
+       HOldEntry *entries = (HOldEntry *) ARRPTR(hs);
+       int vsize;
+       int lastpos = 0;
+       int i;
+
+       if (hs->size_ & HS_FLAG_NEWVERSION)
+               return 0;
+
+       Assert(sizeof(HOldEntry) == sizeof(HEntry));
+
+       if (count == 0)
+               return 2;
+
+       if (count > 0xFFFFFFF)
+               return 0;
+
+       if (CALCDATASIZE(count,0) > VARSIZE(hs))
+               return 0;
+
+       if (entries[0].pos != 0)
+               return 0;
+
+       /* key length must be nondecreasing */
+
+       for (i = 1; i < count; ++i)
+       {
+               if (entries[i].keylen < entries[i-1].keylen)
+                       return 0;
+       }
+
+       /*
+        * entry position must be strictly increasing, except for the
+        * first entry (which can be ""=>"" and thus zero-length); and
+        * all entries must be properly contiguous
+        */
+
+       for (i = 0; i < count; ++i)
+       {
+               if (entries[i].pos != lastpos)
+                       return 0;
+               lastpos += (entries[i].keylen
+                                       + ((entries[i].valisnull) ? 0 : entries[i].vallen));
+       }
+
+       vsize = CALCDATASIZE(count,lastpos);
+
+       if (vsize > VARSIZE(hs))
+               return 0;
+
+       if (vsize != VARSIZE(hs))
+               return 1;
+
+       return 2;
+}
+
+
+/*
+ * hstoreUpgrade: PG_DETOAST_DATUM plus support for conversion of old hstores
+ */
+HStore *
+hstoreUpgrade(Datum orig)
+{
+       HStore     *hs = (HStore *) PG_DETOAST_DATUM(orig);
+       int                     valid_new;
+       int                     valid_old;
+       bool            writable;
+
+       /* Return immediately if no conversion needed */
+       if ((hs->size_ & HS_FLAG_NEWVERSION) ||
+               hs->size_ == 0 ||
+               (VARSIZE(hs) < 32768 && HSE_ISFIRST((ARRPTR(hs)[0]))))
+               return hs;
+
+       valid_new = hstoreValidNewFormat(hs);
+       valid_old = hstoreValidOldFormat(hs);
+       /* Do we have a writable copy? */
+       writable = ((void *) hs != (void *) DatumGetPointer(orig));
+
+       if (!valid_old || hs->size_ == 0)
+       {
+               if (valid_new)
+               {
+                       /*
+                        * force the "new version" flag and the correct varlena
+                        * length, but only if we have a writable copy already
+                        * (which we almost always will, since short new-format
+                        * values won't come through here)
+                        */
+                       if (writable)
+                       {
+                               HS_SETCOUNT(hs,HS_COUNT(hs));
+                               HS_FIXSIZE(hs,HS_COUNT(hs));
+                       }
+                       return hs;
+               }
+               else
+               {
+                       elog(ERROR,"invalid hstore value found");
+               }
+       }
+
+       /*
+        * this is the tricky edge case. It is only possible in some
+        * quite extreme cases (the hstore must have had a lot
+        * of wasted padding space at the end).
+        * But the only way a "new" hstore value could get here is if
+        * we're upgrading in place from a pre-release version of
+        * hstore-new (NOT contrib/hstore), so we work off the following
+        * assumptions:
+        *  1. If you're moving from old contrib/hstore to hstore-new,
+        *     you're required to fix up any potential conflicts first,
+        *     e.g. by running ALTER TABLE ... USING col::text::hstore;
+        *     on all hstore columns before upgrading.
+        *  2. If you're moving from old contrib/hstore to new
+        *     contrib/hstore, then "new" values are impossible here
+        *  3. If you're moving from pre-release hstore-new to hstore-new,
+        *     then "old" values are impossible here
+        *  4. If you're moving from pre-release hstore-new to new
+        *     contrib/hstore, you're not doing so as an in-place upgrade,
+        *     so there is no issue
+        * So the upshot of all this is that we can treat all the edge
+        * cases as "new" if we're being built as hstore-new, and "old"
+        * if we're being built as contrib/hstore.
+        *
+        * XXX the WARNING can probably be downgraded to DEBUG1 once this
+        * has been beta-tested. But for now, it would be very useful to
+        * know if anyone can actually reach this case in a non-contrived
+        * setting.
+        */
+
+       if (valid_new)
+       {
+#if HSTORE_IS_HSTORE_NEW
+               elog(WARNING,"ambiguous hstore value resolved as hstore-new");
+
+               /*
+                * force the "new version" flag and the correct varlena
+                * length, but only if we have a writable copy already
+                * (which we almost always will, since short new-format
+                * values won't come through here)
+                */
+               if (writable)
+               {
+                       HS_SETCOUNT(hs,HS_COUNT(hs));
+                       HS_FIXSIZE(hs,HS_COUNT(hs));
+               }
+               return hs;
+#else
+               elog(WARNING,"ambiguous hstore value resolved as hstore-old");
+#endif
+       }
+
+       /*
+        * must have an old-style value. Overwrite it in place as a new-style
+        * one, making sure we have a writable copy first.
+        */
+
+       if (!writable)
+               hs = (HStore *) PG_DETOAST_DATUM_COPY(orig);
+
+       {
+               int count = hs->size_;
+               HEntry *new_entries = ARRPTR(hs);
+               HOldEntry *old_entries = (HOldEntry *) ARRPTR(hs);
+               int i;
+               
+               for (i = 0; i < count; ++i)
+               {
+                       uint32 pos = old_entries[i].pos;
+                       uint32 keylen = old_entries[i].keylen;
+                       uint32 vallen = old_entries[i].vallen;
+                       bool isnull = old_entries[i].valisnull;
+
+                       if (isnull)
+                               vallen = 0;
+
+                       new_entries[2*i].entry = (pos + keylen) & HENTRY_POSMASK;
+                       new_entries[2*i+1].entry = (((pos + keylen + vallen) & HENTRY_POSMASK)
+                                                                               | ((isnull) ? HENTRY_ISNULL : 0)); 
+               }
+
+               if (count)
+                       new_entries[0].entry |= HENTRY_ISFIRST;
+               HS_SETCOUNT(hs,count);
+               HS_FIXSIZE(hs,count);
+       }
+
+       return hs;
+}
+
+
+PG_FUNCTION_INFO_V1(hstore_version_diag);
+Datum          hstore_version_diag(PG_FUNCTION_ARGS);
+Datum
+hstore_version_diag(PG_FUNCTION_ARGS)
+{
+       HStore *hs = (HStore *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+       int valid_new = hstoreValidNewFormat(hs);
+       int valid_old = hstoreValidOldFormat(hs);
+
+       PG_RETURN_INT32(valid_old*10 + valid_new);
+}
index 9c9a83d128679c905e924a6534a90869ef9929ee..3bd9d718bb33e92679286db84ac9c08a90db76cd 100644 (file)
@@ -1,9 +1,10 @@
 /*
- * $PostgreSQL: pgsql/contrib/hstore/hstore_gin.c,v 1.6 2009/06/11 14:48:51 momjian Exp $
+ * $PostgreSQL: pgsql/contrib/hstore/hstore_gin.c,v 1.7 2009/09/30 19:50:22 tgl Exp $
  */
 #include "postgres.h"
 
 #include "access/gin.h"
+#include "catalog/pg_type.h"
 
 #include "hstore.h"
 
@@ -35,43 +36,36 @@ gin_extract_hstore(PG_FUNCTION_ARGS)
        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_COUNT(hs);
+       int        i;
 
-       *nentries = 2 * hs->size;
+       *nentries = 2 * count;
+       if (count)
+               entries = (Datum *) palloc(sizeof(Datum) * 2 * count);
 
-       if (hs->size > 0)
+       for (i = 0; i < count; ++i)
        {
-               HEntry     *ptr = ARRPTR(hs);
-               char       *words = STRPTR(hs);
-               int                     i = 0;
+               text       *item;
 
-               entries = (Datum *) palloc(sizeof(Datum) * 2 * hs->size);
+               item = makeitem(HS_KEY(hsent,ptr,i), HS_KEYLEN(hsent,i));
+               *VARDATA(item) = KEYFLAG;
+               entries[2*i] = PointerGetDatum(item);
 
-               while (ptr - ARRPTR(hs) < hs->size)
+               if (HS_VALISNULL(hsent,i))
                {
-                       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++;
+                       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_FREE_IF_COPY(hs, 0);
        PG_RETURN_POINTER(entries);
 }
 
@@ -85,8 +79,7 @@ gin_extract_hstore_query(PG_FUNCTION_ARGS)
 
        if (strategy == HStoreContainsStrategyNumber)
        {
-               PG_RETURN_DATUM(DirectFunctionCall2(
-                                                                                       gin_extract_hstore,
+               PG_RETURN_DATUM(DirectFunctionCall2(gin_extract_hstore,
                                                                                        PG_GETARG_DATUM(0),
                                                                                        PG_GETARG_DATUM(1)
                                                                                        ));
@@ -94,19 +87,50 @@ gin_extract_hstore_query(PG_FUNCTION_ARGS)
        else if (strategy == HStoreExistsStrategyNumber)
        {
                text       *item,
-                                  *q = PG_GETARG_TEXT_P(0);
+                                  *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(q), VARSIZE(q) - VARHDRSZ);
+               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,32 +145,45 @@ gin_consistent_hstore(PG_FUNCTION_ARGS)
 {
        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); */
+       /* 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;
 
+       *recheck = false;
+
        if (strategy == HStoreContainsStrategyNumber)
        {
                int                     i;
 
                /*
                 * Index lost information about correspondence of keys and values, so
-                * we need recheck
+                * we need recheck (pre-8.4 this is handled at SQL level)
                 */
                *recheck = true;
-               for (i = 0; res && i < 2 * query->size; i++)
+               for (i = 0; res && i < nkeys; i++)
                        if (check[i] == false)
                                res = false;
        }
        else if (strategy == HStoreExistsStrategyNumber)
        {
                /* Existence of key is guaranteed */
-               *recheck = false;
                res = true;
        }
+       else if (strategy == HStoreExistsAnyStrategyNumber)
+       {
+               /* Existence of key is guaranteed */
+               res = true;
+       }
+       else if (strategy == HStoreExistsAllStrategyNumber)
+       {
+               int        i;
+
+               for (i = 0; res && i < nkeys; ++i)
+                       if (!check[i])
+                               res = false;
+       }
        else
                elog(ERROR, "Unsupported strategy number: %d", strategy);
 
index 0f6eac347c7646b78ae5978cde7704a6b47be8e4..b036fa932f227db24a919706ec4e0f005e045f08 100644 (file)
@@ -1,13 +1,14 @@
 /*
- * $PostgreSQL: pgsql/contrib/hstore/hstore_gist.c,v 1.10 2009/06/11 14:48:51 momjian Exp $
+ * $PostgreSQL: pgsql/contrib/hstore/hstore_gist.c,v 1.11 2009/09/30 19:50:22 tgl Exp $
  */
 #include "postgres.h"
 
 #include "access/gist.h"
 #include "access/itup.h"
 #include "access/skey.h"
-#include "crc32.h"
+#include "catalog/pg_type.h"
 
+#include "crc32.h"
 #include "hstore.h"
 
 /* bigint defines */
@@ -114,30 +115,27 @@ ghstore_compress(PG_FUNCTION_ARGS)
        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);
+               HStore     *val = DatumGetHStoreP(entry->key);
+               HEntry     *hsent = ARRPTR(val);
+               char       *ptr = STRPTR(val);
+               int        count = HS_COUNT(val);
+               int        i;
 
                SET_VARSIZE(res, CALCGTSIZE(0));
 
-               while (ptr - ARRPTR(val) < val->size)
+               for (i = 0; i < count; ++i)
                {
-                       int                     h;
+                       int     h;
 
-                       h = crc32_sz((char *) (words + ptr->pos), ptr->keylen);
+                       h = crc32_sz((char *) HS_KEY(hsent,ptr,i), HS_KEYLEN(hsent,i));
                        HASH(GETSIGN(res), h);
-                       if (!ptr->valisnull)
+                       if (!HS_VALISNULL(hsent,i))
                        {
-                               h = crc32_sz((char *) (words + ptr->pos + ptr->keylen), ptr->vallen);
+                               h = crc32_sz((char *) HS_VAL(hsent,ptr,i), HS_VALLEN(hsent,i));
                                HASH(GETSIGN(res), h);
                        }
-                       ptr++;
                }
 
-               if (val != toastedval)
-                       pfree(val);
-
                retval = (GISTENTRY *) palloc(sizeof(GISTENTRY));
                gistentryinit(*retval, PointerGetDatum(res),
                                          entry->rel, entry->page,
@@ -177,7 +175,7 @@ ghstore_decompress(PG_FUNCTION_ARGS)
        GISTENTRY  *retval;
        HStore     *key;
 
-       key = (HStore *) PG_DETOAST_DATUM(entry->key);
+       key = DatumGetHStoreP(entry->key);
 
        if (key != (HStore *) DatumGetPointer(entry->key))
        {
@@ -500,7 +498,6 @@ ghstore_picksplit(PG_FUNCTION_ARGS)
        }
 
        *right = *left = FirstOffsetNumber;
-       pfree(costvector);
 
        v->spl_ldatum = PointerGetDatum(datum_l);
        v->spl_rdatum = PointerGetDatum(datum_r);
@@ -514,7 +511,6 @@ ghstore_consistent(PG_FUNCTION_ARGS)
 {
        GISTTYPE   *entry = (GISTTYPE *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key);
        StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
-
        /* Oid          subtype = PG_GETARG_OID(3); */
        bool       *recheck = (bool *) PG_GETARG_POINTER(4);
        bool            res = true;
@@ -528,37 +524,85 @@ ghstore_consistent(PG_FUNCTION_ARGS)
 
        sign = GETSIGN(entry);
 
-       if (strategy == HStoreContainsStrategyNumber || strategy == 13 /* hack for old strats */ )
+       if (strategy == HStoreContainsStrategyNumber ||
+               strategy == HStoreOldContainsStrategyNumber)
        {
                HStore     *query = PG_GETARG_HS(1);
                HEntry     *qe = ARRPTR(query);
                char       *qv = STRPTR(query);
+               int        count = HS_COUNT(query);
+               int        i;
 
-               while (res && qe - ARRPTR(query) < query->size)
+               for (i = 0; res && i < count; ++i)
                {
-                       int                     crc = crc32_sz((char *) (qv + qe->pos), qe->keylen);
+                       int     crc = crc32_sz((char *) HS_KEY(qe,qv,i), HS_KEYLEN(qe,i));
 
                        if (GETBIT(sign, HASHVAL(crc)))
                        {
-                               if (!qe->valisnull)
+                               if (!HS_VALISNULL(qe,i))
                                {
-                                       crc = crc32_sz((char *) (qv + qe->pos + qe->keylen), qe->vallen);
+                                       crc = crc32_sz((char *) HS_VAL(qe,qv,i), HS_VALLEN(qe,i));
                                        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);
+               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 3b19702520e9dcaf62ba24a98d723049453a3481..a79cddef0af8e9dc6bc21dd8036d07677732c935 100644 (file)
@@ -1,14 +1,26 @@
 /*
- * $PostgreSQL: pgsql/contrib/hstore/hstore_io.c,v 1.11 2009/06/11 14:48:51 momjian Exp $
+ * $PostgreSQL: pgsql/contrib/hstore/hstore_io.c,v 1.12 2009/09/30 19:50:22 tgl Exp $
  */
 #include "postgres.h"
 
 #include <ctype.h>
 
+#include "access/heapam.h"
+#include "access/htup.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "libpq/pqformat.h"
+#include "utils/lsyscache.h"
+#include "utils/typcache.h"
+
 #include "hstore.h"
 
 PG_MODULE_MAGIC;
 
+/* old names for C functions */
+HSTORE_POLLUTE(hstore_from_text,tconvert);
+
+
 typedef struct
 {
        char       *begin;
@@ -263,7 +275,7 @@ parse_hstore(HSParser *state)
        }
 }
 
-int
+static int
 comparePairs(const void *a, const void *b)
 {
        if (((Pairs *) a)->keylen == ((Pairs *) b)->keylen)
@@ -286,8 +298,14 @@ comparePairs(const void *a, const void *b)
        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
-uniquePairs(Pairs *a, int4 l, int4 *buflen)
+hstoreUniquePairs(Pairs *a, int4 l, int4 *buflen)
 {
        Pairs      *ptr,
                           *res;
@@ -305,7 +323,8 @@ uniquePairs(Pairs *a, int4 l, int4 *buflen)
        res = a;
        while (ptr - a < l)
        {
-               if (ptr->keylen == res->keylen && strncmp(ptr->key, res->key, res->keylen) == 0)
+               if (ptr->keylen == res->keylen &&
+                       strncmp(ptr->key, res->key, res->keylen) == 0)
                {
                        if (ptr->needfree)
                        {
@@ -327,24 +346,6 @@ uniquePairs(Pairs *a, int4 l, int4 *buflen)
        return res + 1 - a;
 }
 
-static void
-freeHSParse(HSParser *state)
-{
-       int                     i;
-
-       if (state->word)
-               pfree(state->word);
-       for (i = 0; i < state->pcur; i++)
-               if (state->pairs[i].needfree)
-               {
-                       if (state->pairs[i].key)
-                               pfree(state->pairs[i].key);
-                       if (state->pairs[i].val)
-                               pfree(state->pairs[i].val);
-               }
-       pfree(state->pairs);
-}
-
 size_t
 hstoreCheckKeyLen(size_t len)
 {
@@ -366,65 +367,722 @@ hstoreCheckValLen(size_t len)
 }
 
 
+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);
+       HS_SETCOUNT(out, pcount);
+
+       if (pcount == 0)
+               return out;
+
+       entry = ARRPTR(out);
+       buf = ptr = STRPTR(out);
+
+       for (i = 0; i < pcount; i++)
+               HS_ADDITEM(entry,buf,ptr,pairs[i]);
+
+       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            len,
-                               buflen,
-                               i;
+       int4            buflen;
        HStore     *out;
-       HEntry     *entries;
-       char       *ptr;
 
        state.begin = PG_GETARG_CSTRING(0);
 
        parse_hstore(&state);
 
-       if (state.pcur == 0)
+       state.pcur = hstoreUniquePairs(state.pairs, state.pcur, &buflen);
+
+       out = hstorePairs(state.pairs, state.pcur, buflen);
+
+       PG_RETURN_POINTER(out);
+}
+
+
+PG_FUNCTION_INFO_V1(hstore_recv);
+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)
        {
-               freeHSParse(&state);
-               len = CALCDATASIZE(0, 0);
-               out = palloc(len);
-               SET_VARSIZE(out, len);
-               out->size = 0;
+               out = hstorePairs(NULL, 0, 0);
                PG_RETURN_POINTER(out);
        }
 
-       state.pcur = uniquePairs(state.pairs, state.pcur, &buflen);
+       pairs = palloc(pcount * sizeof(Pairs));
 
-       len = CALCDATASIZE(state.pcur, buflen);
-       out = palloc(len);
-       SET_VARSIZE(out, len);
-       out->size = state.pcur;
+       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);
+
+       /*
+        * must check >1 rather than != 1 because empty arrays have
+        * 0 dimensions, not 1
+        */
 
-       entries = ARRPTR(out);
-       ptr = STRPTR(out);
+       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 */
 
-       for (i = 0; i < out->size; i++)
+       if (PG_ARGISNULL(1))
+       {
+               value_array = NULL;
+               value_count = key_count;
+               value_datums = NULL;
+               value_nulls = NULL;
+       }
+       else
        {
-               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;
+               value_array = PG_GETARG_ARRAYTYPE_P(1);
+
+               Assert(ARR_ELEMTYPE(value_array) == TEXTOID);
+
+               if (ARR_NDIM(value_array) > 1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                        errmsg("wrong number of array subscripts")));
+
+               if ((ARR_NDIM(key_array) > 0 || ARR_NDIM(value_array) > 0) &&
+                       (ARR_NDIM(key_array) != ARR_NDIM(value_array) ||
+                        ARR_DIMS(key_array)[0] != ARR_DIMS(value_array)[0] ||
+                        ARR_LBOUND(key_array)[0] != ARR_LBOUND(value_array)[0]))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                        errmsg("arrays must have same bounds")));
+
+               deconstruct_array(value_array,
+                                                 TEXTOID, -1, false, 'i',
+                                                 &value_datums, &value_nulls, &value_count);
 
-               entries[i].valisnull = state.pairs[i].isnull;
-               if (entries[i].valisnull)
-                       entries[i].vallen = 4;          /* null */
+               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
                {
-                       entries[i].vallen = state.pairs[i].vallen;
-                       memcpy(ptr, state.pairs[i].val, state.pairs[i].vallen);
-                       ptr += entries[i].vallen;
+                       pairs[i].key = VARDATA_ANY(key_datums[i]);
+                       pairs[i].val = VARDATA_ANY(value_datums[i]);
+                       pairs[i].keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key_datums[i]));
+                       pairs[i].vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(value_datums[i]));
+                       pairs[i].isnull = false;
+                       pairs[i].needfree = false;
+               }
+       }
+
+       key_count = hstoreUniquePairs(pairs, key_count, &buflen);
+
+       out = hstorePairs(pairs, key_count, buflen);
+
+       PG_RETURN_POINTER(out);
+}
+
+
+PG_FUNCTION_INFO_V1(hstore_from_array);
+Datum          hstore_from_array(PG_FUNCTION_ARGS);
+Datum
+hstore_from_array(PG_FUNCTION_ARGS)
+{
+       ArrayType  *in_array = PG_GETARG_ARRAYTYPE_P(0);
+       int         ndims = ARR_NDIM(in_array);
+       int         count;
+       int4            buflen;
+       HStore     *out;
+       Pairs      *pairs;
+       Datum      *in_datums;
+       bool       *in_nulls;
+       int                 in_count;
+       int                 i;
+
+       Assert(ARR_ELEMTYPE(in_array) == TEXTOID);
+
+       switch (ndims)
+       {
+               case 0:
+                       out = hstorePairs(NULL, 0, 0);
+                       PG_RETURN_POINTER(out);
+
+               case 1:
+                       if ((ARR_DIMS(in_array)[0]) % 2)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                                errmsg("array must have even number of elements")));
+                       break;
+
+               case 2:
+                       if ((ARR_DIMS(in_array)[1]) != 2)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                                errmsg("array must have two columns")));
+                       break;
+
+               default:
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+                                        errmsg("wrong number of array subscripts")));
+       }                       
+
+       deconstruct_array(in_array,
+                                         TEXTOID, -1, false, 'i',
+                                         &in_datums, &in_nulls, &in_count);
+
+       count = in_count / 2;
+
+       pairs = palloc(count * sizeof(Pairs));
+
+       for (i = 0; i < count; ++i)
+       {
+               if (in_nulls[i*2])
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("null value not allowed for hstore key")));
+
+               if (in_nulls[i*2+1])
+               {
+                       pairs[i].key = VARDATA_ANY(in_datums[i*2]);
+                       pairs[i].val = NULL;
+                       pairs[i].keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(in_datums[i*2]));
+                       pairs[i].vallen = 4;
+                       pairs[i].isnull = true;
+                       pairs[i].needfree = false;
+               }
+               else
+               {
+                       pairs[i].key = VARDATA_ANY(in_datums[i*2]);
+                       pairs[i].val = VARDATA_ANY(in_datums[i*2+1]);
+                       pairs[i].keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(in_datums[i*2]));
+                       pairs[i].vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(in_datums[i*2+1]));
+                       pairs[i].isnull = false;
+                       pairs[i].needfree = false;
+               }
+       }
+
+       count = hstoreUniquePairs(pairs, count, &buflen);
+
+       out = hstorePairs(pairs, count, buflen);
+
+       PG_RETURN_POINTER(out);
+}
+
+/* most of hstore_from_record is shamelessly swiped from record_out */
+
+/*
+ * structure to cache metadata needed for record I/O
+ */
+typedef struct ColumnIOData
+{
+       Oid                     column_type;
+       Oid                     typiofunc;
+       Oid                     typioparam;
+       FmgrInfo        proc;
+} ColumnIOData;
+
+typedef struct RecordIOData
+{
+       Oid                     record_type;
+       int32           record_typmod;
+       int                     ncolumns;
+       ColumnIOData columns[1];        /* VARIABLE LENGTH ARRAY */
+} RecordIOData;
+
+PG_FUNCTION_INFO_V1(hstore_from_record);
+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;
        }
 
-       freeHSParse(&state);
+       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("first argument must be a rowtype")));
+
+       if (PG_ARGISNULL(0))
+       {
+               if (PG_ARGISNULL(1))
+                       PG_RETURN_NULL();
+
+               rec = NULL;
+
+               /*
+                * have no tuple to look at, so the only source of type info
+                * is the argtype. The lookup_rowtype_tupdesc call below will
+                * error out if we don't have a known composite type oid here.
+                */
+               tupType = argtype;
+               tupTypmod = -1;
+       }
+       else
+       {
+               rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+               if (PG_ARGISNULL(1))
+                       PG_RETURN_POINTER(rec);
+
+               /* Extract type info from the tuple itself */
+               tupType = HeapTupleHeaderGetTypeId(rec);
+               tupTypmod = HeapTupleHeaderGetTypMod(rec);
+       }
+
+       hs = PG_GETARG_HS(1);
+       entries = ARRPTR(hs);
+       ptr = STRPTR(hs);
+
+       /*
+        * if the input hstore is empty, we can only skip the rest if
+        * we were passed in a non-null record, since otherwise there
+        * may be issues with domain nulls.
+        */
+
+       if (HS_COUNT(hs) == 0 && rec)
+               PG_RETURN_POINTER(rec);
+
+       tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+       ncolumns = tupdesc->natts;
+
+       if (rec)
+       {
+               /* Build a temporary HeapTuple control structure */
+               tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+               ItemPointerSetInvalid(&(tuple.t_self));
+               tuple.t_tableOid = InvalidOid;
+               tuple.t_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)));
+               /*
+                * we can't just skip here if the key wasn't found since we
+                * might have a domain to deal with. If we were passed in a
+                * non-null record datum, we assume that the existing values
+                * are valid (if they're not, then it's not our fault), but if
+                * we were passed in a null, then every field which we don't
+                * populate needs to be run through the input function just in
+                * case it's a domain type.
+                */
+               if (idx < 0 && rec)
+                       continue;
+
+               /*
+                * Prepare to convert the column value from text
+                */
+               if (column_info->column_type != column_type)
+               {
+                       getTypeInputInfo(column_type,
+                                                        &column_info->typiofunc,
+                                                        &column_info->typioparam);
+                       fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+                                                 fcinfo->flinfo->fn_mcxt);
+                       column_info->column_type = column_type;
+               }
+
+               if (idx < 0 || HS_VALISNULL(entries,idx))
+               {
+                       /*
+                        * need InputFunctionCall to happen even for nulls, so
+                        * that domain checks are done
+                        */
+                       values[i] = InputFunctionCall(&column_info->proc, NULL,
+                                                                                 column_info->typioparam,
+                                                                                 tupdesc->attrs[i]->atttypmod);
+                       nulls[i] = true;
+               }
+               else
+               {
+                       vallen = HS_VALLEN(entries,idx);
+                       value = 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,40 +1104,50 @@ hstore_out(PG_FUNCTION_ARGS)
 {
        HStore     *in = PG_GETARG_HS(0);
        int                     buflen,
-                               i,
-                               nnulls = 0;
+                               i;
+       int        count = HS_COUNT(in);
        char       *out,
                           *ptr;
        char       *base = STRPTR(in);
        HEntry     *entries = ARRPTR(in);
 
-       if (in->size == 0)
+       if (count == 0)
        {
                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 = 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.
+        */
 
-       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 */ ;
+       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 < in->size; i++)
+
+       for (i = 0; i < count; i++)
        {
                *ptr++ = '"';
-               ptr = cpw(ptr, base + entries[i].pos, entries[i].keylen);
+               ptr = cpw(ptr, HS_KEY(entries,base,i), HS_KEYLEN(entries,i));
                *ptr++ = '"';
                *ptr++ = '=';
                *ptr++ = '>';
-               if (entries[i].valisnull)
+               if (HS_VALISNULL(entries,i))
                {
                        *ptr++ = 'N';
                        *ptr++ = 'U';
@@ -489,11 +1157,11 @@ hstore_out(PG_FUNCTION_ARGS)
                else
                {
                        *ptr++ = '"';
-                       ptr = cpw(ptr, base + entries[i].pos + entries[i].keylen, entries[i].vallen);
+                       ptr = cpw(ptr, HS_VAL(entries,base,i), HS_VALLEN(entries,i));
                        *ptr++ = '"';
                }
 
-               if (i + 1 != in->size)
+               if (i + 1 != count)
                {
                        *ptr++ = ',';
                        *ptr++ = ' ';
@@ -501,6 +1169,42 @@ hstore_out(PG_FUNCTION_ARGS)
        }
        *ptr = '\0';
 
-       PG_FREE_IF_COPY(in, 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 = HS_COUNT(in);
+       char       *base = STRPTR(in);
+       HEntry     *entries = ARRPTR(in);
+       StringInfoData buf;
+
+       pq_begintypsend(&buf);
+
+       pq_sendint(&buf, count, 4);
+
+       for (i = 0; i < count; i++)
+       {
+               int32 keylen = HS_KEYLEN(entries,i);
+               pq_sendint(&buf, keylen, 4);
+               pq_sendtext(&buf, HS_KEY(entries,base,i), keylen);
+               if (HS_VALISNULL(entries,i))
+               {
+                       pq_sendint(&buf, -1, 4);
+               }
+               else
+               {
+                       int32 vallen = HS_VALLEN(entries,i);
+                       pq_sendint(&buf, vallen, 4);
+                       pq_sendtext(&buf, HS_VAL(entries,base,i), vallen);
+               }
+       }
+
+       PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+}
index 8d471e30f1d3350e3972a2124ed0311646b79df9..2338bbf44bd03ae19f9f2646d7bcacec38d9c84c 100644 (file)
 /*
- * $PostgreSQL
+ * $PostgreSQL: pgsql/contrib/hstore/hstore_op.c,v 1.14 2009/09/30 19:50:22 tgl Exp $
  */
 #include "postgres.h"
 
+#include "access/hash.h"
+#include "access/heapam.h"
+#include "access/htup.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
-#include "utils/array.h"
 #include "utils/builtins.h"
 
 #include "hstore.h"
 
+/* old names for C functions */
+HSTORE_POLLUTE(hstore_fetchval,fetchval);
+HSTORE_POLLUTE(hstore_exists,exists);
+HSTORE_POLLUTE(hstore_defined,defined);
+HSTORE_POLLUTE(hstore_delete,delete);
+HSTORE_POLLUTE(hstore_concat,hs_concat);
+HSTORE_POLLUTE(hstore_contains,hs_contains);
+HSTORE_POLLUTE(hstore_contained,hs_contained);
+HSTORE_POLLUTE(hstore_akeys,akeys);
+HSTORE_POLLUTE(hstore_avals,avals);
+HSTORE_POLLUTE(hstore_skeys,skeys);
+HSTORE_POLLUTE(hstore_svals,svals);
+HSTORE_POLLUTE(hstore_each,each);
 
-static HEntry *
-findkey(HStore *hs, char *key, int keylen)
+
+/*
+ * 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     *StopLow = ARRPTR(hs);
-       HEntry     *StopHigh = StopLow + hs->size;
-       HEntry     *StopMiddle;
-       int                     difference;
+       HEntry     *entries = ARRPTR(hs);
+       int         stopLow = lowbound ? *lowbound : 0;
+       int         stopHigh = HS_COUNT(hs);
+       int         stopMiddle;
        char       *base = STRPTR(hs);
 
-       while (StopLow < StopHigh)
+       while (stopLow < stopHigh)
        {
-               StopMiddle = StopLow + (StopHigh - StopLow) / 2;
+               int difference;
 
-               if (StopMiddle->keylen == keylen)
-                       difference = strncmp(base + StopMiddle->pos, key, StopMiddle->keylen);
+               stopMiddle = stopLow + (stopHigh - stopLow) / 2;
+
+               if (HS_KEYLEN(entries,stopMiddle) == keylen)
+                       difference = strncmp(HS_KEY(entries,base,stopMiddle), key, keylen);
                else
-                       difference = (StopMiddle->keylen > keylen) ? 1 : -1;
+                       difference = (HS_KEYLEN(entries,stopMiddle) > keylen) ? 1 : -1;
 
                if (difference == 0)
-                       return StopMiddle;
+               {
+                       if (lowbound)
+                               *lowbound = stopMiddle + 1;
+                       return stopMiddle;
+               }
                else if (difference < 0)
-                       StopLow = StopMiddle + 1;
+                       stopLow = stopMiddle + 1;
                else
-                       StopHigh = StopMiddle;
+                       stopHigh = stopMiddle;
        }
 
-       return NULL;
+       if (lowbound)
+               *lowbound = stopLow;
+       return -1;
 }
 
-PG_FUNCTION_INFO_V1(fetchval);
-Datum          fetchval(PG_FUNCTION_ARGS);
+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
-fetchval(PG_FUNCTION_ARGS)
+hstore_fetchval(PG_FUNCTION_ARGS)
 {
        HStore     *hs = PG_GETARG_HS(0);
-       text       *key = PG_GETARG_TEXT_P(1);
-       HEntry     *entry;
+       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 ((entry = findkey(hs, VARDATA(key), VARSIZE(key) - VARHDRSZ)) == NULL || entry->valisnull)
-       {
-               PG_FREE_IF_COPY(hs, 0);
-               PG_FREE_IF_COPY(key, 1);
+       if (idx < 0 || HS_VALISNULL(entries,idx))
                PG_RETURN_NULL();
-       }
 
-       out = cstring_to_text_with_len(STRPTR(hs) + entry->pos + entry->keylen,
-                                                                  entry->vallen);
+       out = cstring_to_text_with_len(HS_VAL(entries,STRPTR(hs),idx),
+                                                                  HS_VALLEN(entries,idx));
 
-       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);
+
+PG_FUNCTION_INFO_V1(hstore_exists);
+Datum          hstore_exists(PG_FUNCTION_ARGS);
 Datum
-exists(PG_FUNCTION_ARGS)
+hstore_exists(PG_FUNCTION_ARGS)
 {
        HStore     *hs = PG_GETARG_HS(0);
-       text       *key = PG_GETARG_TEXT_P(1);
-       HEntry     *entry;
+       text       *key = PG_GETARG_TEXT_PP(1);
+    int         idx = hstoreFindKey(hs, NULL,
+                                                                       VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key));
 
-       entry = findkey(hs, VARDATA(key), VARSIZE(key) - VARHDRSZ);
+       PG_RETURN_BOOL(idx >= 0);
+}
 
-       PG_FREE_IF_COPY(hs, 0);
-       PG_FREE_IF_COPY(key, 1);
 
-       PG_RETURN_BOOL(entry);
+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(defined);
-Datum          defined(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(hstore_exists_all);
+Datum          hstore_exists_all(PG_FUNCTION_ARGS);
 Datum
-defined(PG_FUNCTION_ARGS)
+hstore_exists_all(PG_FUNCTION_ARGS)
 {
        HStore     *hs = PG_GETARG_HS(0);
-       text       *key = PG_GETARG_TEXT_P(1);
-       HEntry     *entry;
-       bool            res;
+       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;
+       }
 
-       entry = findkey(hs, VARDATA(key), VARSIZE(key) - VARHDRSZ);
+       PG_RETURN_BOOL(res);
+}
 
-       res = (entry && !entry->valisnull) ? true : false;
 
-       PG_FREE_IF_COPY(hs, 0);
-       PG_FREE_IF_COPY(key, 1);
+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(delete);
-Datum          delete(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(hstore_delete);
+Datum          hstore_delete(PG_FUNCTION_ARGS);
 Datum
-delete(PG_FUNCTION_ARGS)
+hstore_delete(PG_FUNCTION_ARGS)
 {
        HStore     *hs = PG_GETARG_HS(0);
-       text       *key = PG_GETARG_TEXT_P(1);
+       text       *key = PG_GETARG_TEXT_PP(1);
+       char       *keyptr = VARDATA_ANY(key);
+       int         keylen = VARSIZE_ANY_EXHDR(key);
        HStore     *out = palloc(VARSIZE(hs));
-       char       *ptrs,
+       char       *bufs,
+                      *bufd,
                           *ptrd;
        HEntry     *es,
                           *ed;
+       int         i;
+       int         count = HS_COUNT(hs);
+       int         outcount = 0;
 
        SET_VARSIZE(out, VARSIZE(hs));
-       out->size = hs->size;           /* temporary! */
+       HS_SETCOUNT(out, count);                /* temporary! */
 
-       ptrs = STRPTR(hs);
+       bufs = STRPTR(hs);
        es = ARRPTR(hs);
-       ptrd = STRPTR(out);
+       bufd = ptrd = STRPTR(out);
        ed = ARRPTR(out);
 
-       while (es - ARRPTR(hs) < hs->size)
+       for (i = 0; i < count; ++i)
        {
-               if (!(es->keylen == VARSIZE(key) - VARHDRSZ && strncmp(ptrs, VARDATA(key), es->keylen) == 0))
+               int len = HS_KEYLEN(es,i);
+               char *ptrs = HS_KEY(es,bufs,i);
+
+               if (!(len == keylen && strncmp(ptrs, keyptr, 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++;
+                       int vallen = HS_VALLEN(es,i);
+                       HS_COPYITEM(ed, bufd, ptrd, ptrs, len, vallen, HS_VALISNULL(es,i));
+                       ++outcount;
                }
-               ptrs += es->keylen + ((es->valisnull) ? 0 : es->vallen);
-               es++;
        }
 
-       if (ed - ARRPTR(out) != out->size)
+       HS_FINALIZE(out,outcount,bufd,ptrd);
+
+       PG_RETURN_POINTER(out);
+}
+
+
+PG_FUNCTION_INFO_V1(hstore_delete_array);
+Datum          hstore_delete_array(PG_FUNCTION_ARGS);
+Datum
+hstore_delete_array(PG_FUNCTION_ARGS)
+{
+       HStore     *hs = PG_GETARG_HS(0);
+       HStore     *out = palloc(VARSIZE(hs));
+       int         hs_count = HS_COUNT(hs);
+       char       *ps,
+                      *bufd,
+                          *pd;
+       HEntry     *es,
+                          *ed;
+       int         i,j;
+       int         outcount = 0;
+       ArrayType  *key_array = PG_GETARG_ARRAYTYPE_P(1);
+       int         nkeys;
+       Pairs      *key_pairs = hstoreArrayToPairs(key_array, &nkeys);
+
+       SET_VARSIZE(out, VARSIZE(hs));
+       HS_SETCOUNT(out, hs_count);             /* temporary! */
+
+       ps = STRPTR(hs);
+       es = ARRPTR(hs);
+       bufd = pd = STRPTR(out);
+       ed = ARRPTR(out);
+
+       if (nkeys == 0)
        {
-               int                     buflen = ptrd - STRPTR(out);
+               /* return a copy of the input, unchanged */
+               memcpy(out, hs, VARSIZE(hs));
+               HS_FIXSIZE(out, hs_count);
+               HS_SETCOUNT(out, hs_count);
+               PG_RETURN_POINTER(out);
+       }
+
+       /*
+        * this is in effect a merge between hs and key_pairs, both of
+        * which are already sorted by (keylen,key); we take keys from
+        * hs only
+        */
+
+       for (i = j = 0; i < hs_count; )
+       {
+               int     difference;
+               
+               if (j >= nkeys)
+                       difference = -1;
+               else
+               {
+                       int skeylen = HS_KEYLEN(es,i);
+                       if (skeylen == key_pairs[j].keylen)
+                               difference = strncmp(HS_KEY(es,ps,i),
+                                                                        key_pairs[j].key,
+                                                                        key_pairs[j].keylen);
+                       else
+                               difference = (skeylen > key_pairs[j].keylen) ? 1 : -1;
+               }
+
+               if (difference > 0)
+                       ++j;
+               else if (difference == 0)
+                       ++i, ++j;
+               else
+               {
+                       HS_COPYITEM(ed, bufd, pd,
+                                               HS_KEY(es,ps,i), HS_KEYLEN(es,i),
+                                               HS_VALLEN(es,i), HS_VALISNULL(es,i));
+                       ++outcount;
+                       ++i;
+               }
+       }
+
+       HS_FINALIZE(out,outcount,bufd,pd);
+
+       PG_RETURN_POINTER(out);
+}
+
 
-               ptrd = STRPTR(out);
+PG_FUNCTION_INFO_V1(hstore_delete_hstore);
+Datum          hstore_delete_hstore(PG_FUNCTION_ARGS);
+Datum
+hstore_delete_hstore(PG_FUNCTION_ARGS)
+{
+       HStore     *hs = PG_GETARG_HS(0);
+       HStore     *hs2 = PG_GETARG_HS(1);
+       HStore     *out = palloc(VARSIZE(hs));
+       int         hs_count = HS_COUNT(hs);
+       int         hs2_count = HS_COUNT(hs2);
+       char       *ps,
+                      *ps2,
+                      *bufd,
+                          *pd;
+       HEntry     *es,
+                      *es2,
+                          *ed;
+       int         i,j;
+       int         outcount = 0;
 
-               out->size = ed - ARRPTR(out);
+       SET_VARSIZE(out, VARSIZE(hs));
+       HS_SETCOUNT(out, hs_count);             /* temporary! */
 
-               memmove(STRPTR(out), ptrd, buflen);
-               SET_VARSIZE(out, CALCDATASIZE(out->size, buflen));
+       ps = STRPTR(hs);
+       es = ARRPTR(hs);
+       ps2 = STRPTR(hs2);
+       es2 = ARRPTR(hs2);
+       bufd = pd = STRPTR(out);
+       ed = ARRPTR(out);
+
+       if (hs2_count == 0)
+       {
+               /* return a copy of the input, unchanged */
+               memcpy(out, hs, VARSIZE(hs));
+               HS_FIXSIZE(out, hs_count);
+               HS_SETCOUNT(out, hs_count);
+               PG_RETURN_POINTER(out);
        }
 
+       /*
+        * this is in effect a merge between hs and hs2, both of
+        * which are already sorted by (keylen,key); we take keys from
+        * hs only; for equal keys, we take the value from hs unless the
+        * values are equal
+        */
+
+       for (i = j = 0; i < hs_count; )
+       {
+               int     difference;
+               
+               if (j >= hs2_count)
+                       difference = -1;
+               else
+               {
+                       int skeylen = HS_KEYLEN(es,i);
+                       int s2keylen = HS_KEYLEN(es2,j);
+                       if (skeylen == s2keylen)
+                               difference = strncmp(HS_KEY(es,ps,i),
+                                                                        HS_KEY(es2,ps2,j),
+                                                                        skeylen);
+                       else
+                               difference = (skeylen > s2keylen) ? 1 : -1;
+               }
+
+               if (difference > 0)
+                       ++j;
+               else if (difference == 0)
+               {
+                       int svallen = HS_VALLEN(es,i);
+                       int snullval = HS_VALISNULL(es,i);
+                       if (snullval != HS_VALISNULL(es2,j)
+                               || (!snullval
+                                       && (svallen != HS_VALLEN(es2,j)
+                                               || strncmp(HS_VAL(es,ps,i), HS_VAL(es2,ps2,j), svallen) != 0)))
+                       {
+                               HS_COPYITEM(ed, bufd, pd,
+                                                       HS_KEY(es,ps,i), HS_KEYLEN(es,i),
+                                                       svallen, snullval);
+                               ++outcount;
+                       }
+                       ++i, ++j;
+               }
+               else
+               {
+                       HS_COPYITEM(ed, bufd, pd,
+                                               HS_KEY(es,ps,i), HS_KEYLEN(es,i),
+                                               HS_VALLEN(es,i), HS_VALISNULL(es,i));
+                       ++outcount;
+                       ++i;
+               }
+       }
 
-       PG_FREE_IF_COPY(hs, 0);
-       PG_FREE_IF_COPY(key, 1);
+       HS_FINALIZE(out,outcount,bufd,pd);
 
        PG_RETURN_POINTER(out);
 }
 
-PG_FUNCTION_INFO_V1(hs_concat);
-Datum          hs_concat(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(hstore_concat);
+Datum          hstore_concat(PG_FUNCTION_ARGS);
 Datum
-hs_concat(PG_FUNCTION_ARGS)
+hstore_concat(PG_FUNCTION_ARGS)
 {
        HStore     *s1 = PG_GETARG_HS(0);
        HStore     *s2 = PG_GETARG_HS(1);
        HStore     *out = palloc(VARSIZE(s1) + VARSIZE(s2));
        char       *ps1,
                           *ps2,
+                      *bufd,
                           *pd;
        HEntry     *es1,
                           *es2,
                           *ed;
+       int         s1idx;
+       int         s2idx;
+       int         s1count = HS_COUNT(s1);
+       int         s2count = HS_COUNT(s2);
+       int         outcount = 0;
 
-       SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2));
-       out->size = s1->size + s2->size;
+       SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2) - HSHRDSIZE);
+       HS_SETCOUNT(out, s1count + s2count);
+
+       if (s1count == 0)
+       {
+               /* return a copy of the input, unchanged */
+               memcpy(out, s2, VARSIZE(s2));
+               HS_FIXSIZE(out, s2count);
+               HS_SETCOUNT(out, s2count);
+               PG_RETURN_POINTER(out);
+       }
+
+       if (s2count == 0)
+       {
+               /* return a copy of the input, unchanged */
+               memcpy(out, s1, VARSIZE(s1));
+               HS_FIXSIZE(out, s1count);
+               HS_SETCOUNT(out, s1count);
+               PG_RETURN_POINTER(out);
+       }
 
        ps1 = STRPTR(s1);
        ps2 = STRPTR(s2);
-       pd = STRPTR(out);
+       bufd = 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;
+       /*
+        * 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
+        */
 
-               if (es1->keylen == es2->keylen)
-                       difference = strncmp(ps1, ps2, es1->keylen);
+       for (s1idx = s2idx = 0; s1idx < s1count || s2idx < s2count; ++outcount)
+       {
+               int     difference;
+               
+               if (s1idx >= s1count)
+                       difference = 1;
+               else if (s2idx >= s2count)
+                       difference = -1;
                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++;
+                       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;
                }
-               else if (difference > 0)
+
+               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++;
+                       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
                {
-                       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++;
+                       HS_COPYITEM(ed, bufd, pd,
+                                               HS_KEY(es1,ps1,s1idx), HS_KEYLEN(es1,s1idx),
+                                               HS_VALLEN(es1,s1idx), HS_VALISNULL(es1,s1idx));
+                       ++s1idx;
                }
        }
 
-       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++;
+       HS_FINALIZE(out,outcount,bufd,pd);
 
-               ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
-               es1++;
-       }
+       PG_RETURN_POINTER(out);
+}
 
-       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++;
+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);
        }
 
-       if (ed - ARRPTR(out) != out->size)
-       {
-               int                     buflen = pd - STRPTR(out);
+       out_datums = palloc(sizeof(Datum) * key_count);
+       out_nulls = palloc(sizeof(bool) * key_count);
 
-               pd = STRPTR(out);
+       for (i = 0; i < key_count; ++i)
+       {
+               text       *key = (text*) DatumGetPointer(key_datums[i]);
+               int        idx;
 
-               out->size = ed - ARRPTR(out);
+               if (key_nulls[i])
+                       idx = -1;
+               else
+                       idx = hstoreFindKey(hs, NULL, VARDATA(key), VARSIZE(key) - VARHDRSZ);
 
-               memmove(STRPTR(out), pd, buflen);
-               SET_VARSIZE(out, CALCDATASIZE(out->size, buflen));
+               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;
+               }
        }
 
-       PG_FREE_IF_COPY(s1, 0);
-       PG_FREE_IF_COPY(s2, 1);
+       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(out);
+       PG_RETURN_POINTER(aout);
 }
 
-PG_FUNCTION_INFO_V1(tconvert);
-Datum          tconvert(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(hstore_slice_to_hstore);
+Datum          hstore_slice_to_hstore(PG_FUNCTION_ARGS);
 Datum
-tconvert(PG_FUNCTION_ARGS)
+hstore_slice_to_hstore(PG_FUNCTION_ARGS)
 {
-       text       *key;
-       text       *val = NULL;
-       int                     len;
-       HStore     *out;
+       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);
+       }
 
-       if (PG_ARGISNULL(0))
-               PG_RETURN_NULL();
+       out_pairs = palloc(sizeof(Pairs) * nkeys);
+       bufsiz = 0;
 
-       key = PG_GETARG_TEXT_P(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.
+        */
 
-       if (PG_ARGISNULL(1))
-               len = CALCDATASIZE(1, VARSIZE(key));
-       else
+       for (i = 0; i < nkeys; ++i)
        {
-               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;
+               int idx = hstoreFindKey(hs, &lastidx,
+                                                               key_pairs[i].key, key_pairs[i].keylen);
 
-       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;
+               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;
+               }
        }
-       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);
-       }
+       /*
+        * we don't use uniquePairs here because we know that the
+        * pairs list is already sorted and uniq'ed.
+        */
 
-       PG_FREE_IF_COPY(key, 0);
+       out = hstorePairs(out_pairs, out_count, bufsiz);
 
        PG_RETURN_POINTER(out);
 }
 
-PG_FUNCTION_INFO_V1(akeys);
-Datum          akeys(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(hstore_akeys);
+Datum          hstore_akeys(PG_FUNCTION_ARGS);
 Datum
-akeys(PG_FUNCTION_ARGS)
+hstore_akeys(PG_FUNCTION_ARGS)
 {
        HStore     *hs = PG_GETARG_HS(0);
        Datum      *d;
        ArrayType  *a;
-       HEntry     *ptr = ARRPTR(hs);
+       HEntry     *entries = ARRPTR(hs);
        char       *base = STRPTR(hs);
+       int         count = HS_COUNT(hs);
+       int         i;
 
-       d = (Datum *) palloc(sizeof(Datum) * (hs->size + 1));
-       while (ptr - ARRPTR(hs) < hs->size)
+       if (count == 0)
        {
-               text       *item;
-
-               item = cstring_to_text_with_len(base + ptr->pos, ptr->keylen);
-               d[ptr - ARRPTR(hs)] = PointerGetDatum(item);
-               ptr++;
+               a = construct_empty_array(TEXTOID);
+               PG_RETURN_POINTER(a);
        }
 
-       a = construct_array(d,
-                                               hs->size,
-                                               TEXTOID,
-                                               -1,
-                                               false,
-                                               'i');
+       d = (Datum *) palloc(sizeof(Datum) * count);
 
-       ptr = ARRPTR(hs);
-       while (ptr - ARRPTR(hs) < hs->size)
+       for (i = 0; i < count; ++i)
        {
-               pfree(DatumGetPointer(d[ptr - ARRPTR(hs)]));
-               ptr++;
+               text *item = cstring_to_text_with_len(HS_KEY(entries,base,i),
+                                                                                         HS_KEYLEN(entries,i));
+               d[i] = PointerGetDatum(item);
        }
 
-       pfree(d);
-       PG_FREE_IF_COPY(hs, 0);
+       a = construct_array(d, count,
+                                               TEXTOID, -1, false,     'i');
 
        PG_RETURN_POINTER(a);
 }
 
-PG_FUNCTION_INFO_V1(avals);
-Datum          avals(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(hstore_avals);
+Datum          hstore_avals(PG_FUNCTION_ARGS);
 Datum
-avals(PG_FUNCTION_ARGS)
+hstore_avals(PG_FUNCTION_ARGS)
 {
        HStore     *hs = PG_GETARG_HS(0);
        Datum      *d;
+       bool       *nulls;
        ArrayType  *a;
-       HEntry     *ptr = ARRPTR(hs);
+       HEntry     *entries = ARRPTR(hs);
        char       *base = STRPTR(hs);
+       int         count = HS_COUNT(hs);
+       int         lb = 1;
+       int         i;
 
-       d = (Datum *) palloc(sizeof(Datum) * (hs->size + 1));
-       while (ptr - ARRPTR(hs) < hs->size)
+       if (count == 0)
        {
-               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_empty_array(TEXTOID);
+               PG_RETURN_POINTER(a);
        }
 
-       a = construct_array(d,
-                                               hs->size,
-                                               TEXTOID,
-                                               -1,
-                                               false,
-                                               'i');
+       d = (Datum *) palloc(sizeof(Datum) * count);
+       nulls = (bool *) palloc(sizeof(bool) * count);
 
-       ptr = ARRPTR(hs);
-       while (ptr - ARRPTR(hs) < hs->size)
+       for (i = 0; i < count; ++i)
        {
-               pfree(DatumGetPointer(d[ptr - ARRPTR(hs)]));
-               ptr++;
+               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;
+               }
        }
 
-       pfree(d);
-       PG_FREE_IF_COPY(hs, 0);
+       a = construct_md_array(d, nulls, 1, &count, &lb,
+                                                  TEXTOID, -1, false,  'i');
 
        PG_RETURN_POINTER(a);
 }
 
-typedef struct
+
+static ArrayType *
+hstore_to_array_internal(HStore *hs, int ndims)
+{
+       HEntry     *entries = ARRPTR(hs);
+       char       *base = STRPTR(hs);
+       int         count = HS_COUNT(hs);
+       int         out_size[2] = { 0, 2 };
+       int         lb[2] = { 1, 1 };
+       Datum      *out_datums;
+       bool       *out_nulls;
+       int         i;
+
+       Assert(ndims < 3);
+
+       if (count == 0 || ndims == 0)
+               return construct_empty_array(TEXTOID);
+
+       out_size[0] = count * 2 / ndims;
+       out_datums = palloc(sizeof(Datum) * count * 2);
+       out_nulls = palloc(sizeof(bool) * count * 2);
+
+       for (i = 0; i < count; ++i)
+       {
+               text *key = cstring_to_text_with_len(HS_KEY(entries,base,i),
+                                                                                        HS_KEYLEN(entries,i));
+               out_datums[i*2] = PointerGetDatum(key);
+               out_nulls[i*2] = false;
+
+               if (HS_VALISNULL(entries,i))
+               {
+                       out_datums[i*2+1] = (Datum) 0;
+                       out_nulls[i*2+1] = true;
+               }
+               else
+               {
+                       text *item = cstring_to_text_with_len(HS_VAL(entries,base,i),
+                                                                                                 HS_VALLEN(entries,i));
+                       out_datums[i*2+1] = PointerGetDatum(item);
+                       out_nulls[i*2+1] = false;
+               }
+       }
+
+       return construct_md_array(out_datums, out_nulls,
+                                                         ndims, out_size, lb,
+                                                         TEXTOID, -1, false, 'i');
+}
+
+PG_FUNCTION_INFO_V1(hstore_to_array);
+Datum          hstore_to_array(PG_FUNCTION_ARGS);
+Datum
+hstore_to_array(PG_FUNCTION_ARGS)
+{
+       HStore     *hs = PG_GETARG_HS(0);
+       ArrayType  *out = hstore_to_array_internal(hs, 1);
+
+       PG_RETURN_POINTER(out);
+}
+
+PG_FUNCTION_INFO_V1(hstore_to_matrix);
+Datum          hstore_to_matrix(PG_FUNCTION_ARGS);
+Datum
+hstore_to_matrix(PG_FUNCTION_ARGS)
 {
-       HStore     *hs;
-       int                     i;
-} AKStore;
+       HStore     *hs = PG_GETARG_HS(0);
+       ArrayType  *out = hstore_to_array_internal(hs, 2);
+
+       PG_RETURN_POINTER(out);
+}
+
+/*
+ * Common initialization function for the various set-returning
+ * funcs. fcinfo is only passed if the function is to return a
+ * composite; it will be used to look up the return tupledesc.
+ * we stash a copy of the hstore in the multi-call context in
+ * case it was originally toasted. (At least I assume that's why;
+ * there was no explanatory comment in the original code. --AG)
+ */
 
 static void
-setup_firstcall(FuncCallContext *funcctx, HStore *hs)
+setup_firstcall(FuncCallContext *funcctx, HStore * hs,
+                               FunctionCallInfoData *fcinfo)
 {
        MemoryContext oldcontext;
-       AKStore    *st;
+       HStore     *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));
+       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(skeys);
-Datum          skeys(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(hstore_skeys);
+Datum          hstore_skeys(PG_FUNCTION_ARGS);
 Datum
-skeys(PG_FUNCTION_ARGS)
+hstore_skeys(PG_FUNCTION_ARGS)
 {
        FuncCallContext *funcctx;
-       AKStore    *st;
+       HStore    *hs;
+       int        i;
 
        if (SRF_IS_FIRSTCALL())
        {
-               HStore     *hs = PG_GETARG_HS(0);
-
+               hs = PG_GETARG_HS(0);
                funcctx = SRF_FIRSTCALL_INIT();
-               setup_firstcall(funcctx, hs);
-               PG_FREE_IF_COPY(hs, 0);
+               setup_firstcall(funcctx, hs, NULL);
        }
 
        funcctx = SRF_PERCALL_SETUP();
-       st = (AKStore *) funcctx->user_fctx;
+       hs = (HStore *) funcctx->user_fctx;
+       i = funcctx->call_cntr;
 
-       if (st->i < st->hs->size)
+       if (i < HS_COUNT(hs))
        {
-               HEntry     *ptr = &(ARRPTR(st->hs)[st->i]);
+               HEntry     *entries = ARRPTR(hs);
                text       *item;
 
-               item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos,
-                                                                               ptr->keylen);
-               st->i++;
+               item = cstring_to_text_with_len(HS_KEY(entries,STRPTR(hs),i),
+                                                                               HS_KEYLEN(entries,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);
+
+PG_FUNCTION_INFO_V1(hstore_svals);
+Datum          hstore_svals(PG_FUNCTION_ARGS);
 Datum
-svals(PG_FUNCTION_ARGS)
+hstore_svals(PG_FUNCTION_ARGS)
 {
        FuncCallContext *funcctx;
-       AKStore    *st;
+       HStore    *hs;
+       int        i;
 
        if (SRF_IS_FIRSTCALL())
        {
-               HStore     *hs = PG_GETARG_HS(0);
-
+               hs = PG_GETARG_HS(0);
                funcctx = SRF_FIRSTCALL_INIT();
-               setup_firstcall(funcctx, hs);
-               PG_FREE_IF_COPY(hs, 0);
+               setup_firstcall(funcctx, hs, NULL);
        }
 
        funcctx = SRF_PERCALL_SETUP();
-       st = (AKStore *) funcctx->user_fctx;
+       hs = (HStore *) funcctx->user_fctx;
+       i = funcctx->call_cntr;
 
-       if (st->i < st->hs->size)
+       if (i < HS_COUNT(hs))
        {
-               HEntry     *ptr = &(ARRPTR(st->hs)[st->i]);
+               HEntry     *entries = ARRPTR(hs);
 
-               if (ptr->valisnull)
+               if (HS_VALISNULL(entries,i))
                {
                        ReturnSetInfo *rsi;
 
-                       st->i++;
+                       /* ugly ugly ugly. why no macro for this? */
                        (funcctx)->call_cntr++;
                        rsi = (ReturnSetInfo *) fcinfo->resultinfo;
                        rsi->isDone = ExprMultipleResult;
@@ -502,144 +939,306 @@ svals(PG_FUNCTION_ARGS)
                {
                        text       *item;
 
-                       item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos + ptr->keylen,
-                                                                                       ptr->vallen);
-                       st->i++;
+                       item = cstring_to_text_with_len(HS_VAL(entries,STRPTR(hs),i),
+                                                                                       HS_VALLEN(entries,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);
+
+PG_FUNCTION_INFO_V1(hstore_contains);
+Datum          hstore_contains(PG_FUNCTION_ARGS);
 Datum
-hs_contains(PG_FUNCTION_ARGS)
+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       *vv = STRPTR(val);
-       char       *tv = STRPTR(tmpl);
-
-       while (res && te - ARRPTR(tmpl) < tmpl->size)
+       char       *tstr = STRPTR(tmpl);
+       HEntry     *ve = ARRPTR(val);
+       char       *vstr = STRPTR(val);
+       int         tcount = HS_COUNT(tmpl);
+       int         lastidx = 0;
+       int         i;
+
+       /*
+        * we exploit the fact that keys in "tmpl" are in strictly
+        * increasing order to narrow the hstoreFindKey search; each search
+        * can start one entry past the previous "found" entry, or at the
+        * lower bound of the search
+        */
+
+       for (i = 0; res && i < tcount; ++i)
        {
-               HEntry     *entry = findkey(val, tv + te->pos, te->keylen);
+               int idx = hstoreFindKey(val, &lastidx,
+                                                               HS_KEY(te,tstr,i), HS_KEYLEN(te,i));
 
-               if (entry)
+               if (idx >= 0)
                {
-                       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))
+                       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;
-               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);
+
+PG_FUNCTION_INFO_V1(hstore_contained);
+Datum          hstore_contained(PG_FUNCTION_ARGS);
 Datum
-hs_contained(PG_FUNCTION_ARGS)
+hstore_contained(PG_FUNCTION_ARGS)
 {
-       PG_RETURN_DATUM(DirectFunctionCall2(
-                                                                               hs_contains,
+       PG_RETURN_DATUM(DirectFunctionCall2(hstore_contains,
                                                                                PG_GETARG_DATUM(1),
                                                                                PG_GETARG_DATUM(0)
                                                                                ));
 }
 
-PG_FUNCTION_INFO_V1(each);
-Datum          each(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(hstore_each);
+Datum          hstore_each(PG_FUNCTION_ARGS);
 Datum
-each(PG_FUNCTION_ARGS)
+hstore_each(PG_FUNCTION_ARGS)
 {
        FuncCallContext *funcctx;
-       AKStore    *st;
+       HStore     *hs;
+       int         i;
 
        if (SRF_IS_FIRSTCALL())
        {
-               TupleDesc       tupdesc;
-               MemoryContext oldcontext;
-               HStore     *hs = PG_GETARG_HS(0);
-
+               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);
+               setup_firstcall(funcctx, hs, fcinfo);
        }
 
        funcctx = SRF_PERCALL_SETUP();
-       st = (AKStore *) funcctx->user_fctx;
+       hs = (HStore *) funcctx->user_fctx;
+       i = funcctx->call_cntr;
 
-       if (st->i < st->hs->size)
+       if (i < HS_COUNT(hs))
        {
-               HEntry     *ptr = &(ARRPTR(st->hs)[st->i]);
+               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(STRPTR(st->hs) + ptr->pos, ptr->keylen);
+               item = cstring_to_text_with_len(HS_KEY(entries,ptr,i),
+                                                                               HS_KEYLEN(entries,i));
                dvalues[0] = PointerGetDatum(item);
 
-               if (ptr->valisnull)
+               if (HS_VALISNULL(entries,i))
                {
                        dvalues[1] = (Datum) 0;
                        nulls[1] = true;
                }
                else
                {
-                       item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos + ptr->keylen,
-                                                                                       ptr->vallen);
+                       item = cstring_to_text_with_len(HS_VAL(entries,ptr,i),
+                                                                                       HS_VALLEN(entries,i));
                        dvalues[1] = PointerGetDatum(item);
                }
-               st->i++;
 
-               tuple = heap_form_tuple(funcctx->attinmeta->tupdesc, dvalues, nulls);
+               tuple = heap_form_tuple(funcctx->tuple_desc, 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);
 }
+
+
+/*
+ * btree sort order for hstores isn't intended to be useful; we really only
+ * care about equality versus non-equality.  we compare the entire string
+ * buffer first, then the entry pos array.
+ */
+
+PG_FUNCTION_INFO_V1(hstore_cmp);
+Datum          hstore_cmp(PG_FUNCTION_ARGS);
+Datum
+hstore_cmp(PG_FUNCTION_ARGS)
+{
+       HStore     *hs1 = PG_GETARG_HS(0);
+       HStore     *hs2 = PG_GETARG_HS(1);
+       int         hcount1 = HS_COUNT(hs1);
+       int         hcount2 = HS_COUNT(hs2);
+       int         res = 0;
+
+       if (hcount1 == 0 || hcount2 == 0)
+       {
+               /*
+                * if either operand is empty, and the other is nonempty, the
+                * nonempty one is larger. If both are empty they are equal.
+                */
+               if (hcount1 > 0)
+                       res = 1;
+               else if (hcount2 > 0)
+                       res = -1;
+       }
+       else
+       {
+               /* here we know both operands are nonempty */
+               char       *str1 = STRPTR(hs1);
+               char       *str2 = STRPTR(hs2);
+               HEntry     *ent1 = ARRPTR(hs1);
+               HEntry     *ent2 = ARRPTR(hs2);
+               size_t      len1 = HSE_ENDPOS(ent1[2*hcount1 - 1]);
+               size_t      len2 = HSE_ENDPOS(ent2[2*hcount2 - 1]);
+
+               res = memcmp(str1, str2, Min(len1,len2));
+
+               if (res == 0)
+               {
+                       if (len1 > len2)
+                               res = 1;
+                       else if (len1 < len2)
+                               res = -1;
+                       else if (hcount1 > hcount2)
+                               res = 1;
+                       else if (hcount2 > hcount1)
+                               res = -1;
+                       else
+                       {
+                               int count = hcount1 * 2;
+                               int i;
+
+                               for (i = 0; i < count; ++i)
+                                       if (HSE_ENDPOS(ent1[i]) != HSE_ENDPOS(ent2[i]) ||
+                                               HSE_ISNULL(ent1[i]) != HSE_ISNULL(ent2[i]))
+                                               break;
+                               if (i < count)
+                               {
+                                       if (HSE_ENDPOS(ent1[i]) < HSE_ENDPOS(ent2[i]))
+                                               res = -1;
+                                       else if (HSE_ENDPOS(ent1[i]) > HSE_ENDPOS(ent2[i]))
+                                               res = 1;
+                                       else if (HSE_ISNULL(ent1[i]))
+                                               res = 1;
+                                       else if (HSE_ISNULL(ent2[i]))
+                                               res = -1;
+                               }
+                       }
+               }
+               else
+               {
+                       res = (res > 0) ? 1 : -1;
+               }
+       }
+
+       /*
+        * this is a btree support function; this is one of the few
+        * places where memory needs to be explicitly freed.
+        */
+       PG_FREE_IF_COPY(hs1,0);
+       PG_FREE_IF_COPY(hs2,1);
+       PG_RETURN_INT32(res);
+}
+
+
+PG_FUNCTION_INFO_V1(hstore_eq);
+Datum          hstore_eq(PG_FUNCTION_ARGS);
+Datum
+hstore_eq(PG_FUNCTION_ARGS)
+{
+       int     res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
+                                                                                                       PG_GETARG_DATUM(0),
+                                                                                                       PG_GETARG_DATUM(1)));
+       PG_RETURN_BOOL(res == 0);
+}
+
+PG_FUNCTION_INFO_V1(hstore_ne);
+Datum          hstore_ne(PG_FUNCTION_ARGS);
+Datum
+hstore_ne(PG_FUNCTION_ARGS)
+{
+       int     res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
+                                                                                                       PG_GETARG_DATUM(0),
+                                                                                                       PG_GETARG_DATUM(1)));
+       PG_RETURN_BOOL(res != 0);
+}
+
+PG_FUNCTION_INFO_V1(hstore_gt);
+Datum          hstore_gt(PG_FUNCTION_ARGS);
+Datum
+hstore_gt(PG_FUNCTION_ARGS)
+{
+       int     res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
+                                                                                                       PG_GETARG_DATUM(0),
+                                                                                                       PG_GETARG_DATUM(1)));
+       PG_RETURN_BOOL(res > 0);
+}
+
+PG_FUNCTION_INFO_V1(hstore_ge);
+Datum          hstore_ge(PG_FUNCTION_ARGS);
+Datum
+hstore_ge(PG_FUNCTION_ARGS)
+{
+       int     res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
+                                                                                                       PG_GETARG_DATUM(0),
+                                                                                                       PG_GETARG_DATUM(1)));
+       PG_RETURN_BOOL(res >= 0);
+}
+
+PG_FUNCTION_INFO_V1(hstore_lt);
+Datum          hstore_lt(PG_FUNCTION_ARGS);
+Datum
+hstore_lt(PG_FUNCTION_ARGS)
+{
+       int     res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
+                                                                                                       PG_GETARG_DATUM(0),
+                                                                                                       PG_GETARG_DATUM(1)));
+       PG_RETURN_BOOL(res < 0);
+}
+
+PG_FUNCTION_INFO_V1(hstore_le);
+Datum          hstore_le(PG_FUNCTION_ARGS);
+Datum
+hstore_le(PG_FUNCTION_ARGS)
+{
+       int     res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
+                                                                                                       PG_GETARG_DATUM(0),
+                                                                                                       PG_GETARG_DATUM(1)));
+       PG_RETURN_BOOL(res <= 0);
+}
+
+
+PG_FUNCTION_INFO_V1(hstore_hash);
+Datum          hstore_hash(PG_FUNCTION_ARGS);
+Datum
+hstore_hash(PG_FUNCTION_ARGS)
+{
+       HStore     *hs = PG_GETARG_HS(0);
+       Datum       hval = hash_any((unsigned char *)VARDATA(hs),
+                                                               VARSIZE(hs) - VARHDRSZ);
+
+       /*
+        * this is the only place in the code that cares whether the
+        * overall varlena size exactly matches the true data size;
+        * this assertion should be maintained by all the other code,
+        * but we make it explicit here.
+        */
+       Assert(VARSIZE(hs) ==
+                  CALCDATASIZE(HS_COUNT(hs),
+                                               HSE_ENDPOS(ARRPTR(hs)[2*HS_COUNT(hs) - 1])));
+
+       PG_FREE_IF_COPY(hs,0);
+       PG_RETURN_DATUM(hval);
+}
index 9da6cd13dff9fd587e0864726279cbab721c68ac..a88ff1dab90534744dfef202cf2c31b89c43071b 100644 (file)
@@ -65,6 +65,13 @@ select ('aa=>b, c=>d , b=>16'::hstore->'gg') is null;
 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,6 +82,20 @@ select defined('a=>NULL, b=>qq', 'a');
 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,6 +104,47 @@ select delete('a=>null , b=>2, c=>3'::hstore, 'a');
 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;
+select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b'::text)
+         = pg_column_size('a=>1, b=>2'::hstore);
+
+-- 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[];
+select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c'])
+         = pg_column_size('b=>2'::hstore);
+select pg_column_size('a=>1 , b=>2, c=>3'::hstore - '{}'::text[])
+         = pg_column_size('a=>1, b=>2, c=>3'::hstore);
+
+-- 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 pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore)
+         = pg_column_size('a=>1, c=>3'::hstore);
+select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ''::hstore)
+         = pg_column_size('a=>1, b=>2, c=>3'::hstore);
 
 -- ||
 select 'aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f';
@@ -90,12 +152,104 @@ select 'aa=>1 , b=>2, cq=>3'::hstore || 'aq=>l';
 select 'aa=>1 , b=>2, cq=>3'::hstore || 'aa=>l';
 select 'aa=>1 , b=>2, cq=>3'::hstore || '';
 select ''::hstore || 'cq=>l, b=>g, fg=>f';
+select pg_column_size(''::hstore || ''::hstore) = pg_column_size(''::hstore);
+select pg_column_size('aa=>1'::hstore || 'b=>2'::hstore)
+         = pg_column_size('aa=>1, b=>2'::hstore);
+select pg_column_size('aa=>1, b=>2'::hstore || ''::hstore)
+         = pg_column_size('aa=>1, b=>2'::hstore);
+select pg_column_size(''::hstore || 'aa=>1, b=>2'::hstore)
+         = pg_column_size('aa=>1, b=>2'::hstore);
 
 -- =>
 select 'a=>g, b=>c'::hstore || ( 'asd'=>'gf' );
 select 'a=>g, b=>c'::hstore || ( 'b'=>'gf' );
 select 'a=>g, b=>c'::hstore || ( 'b'=>'NULL' );
 select 'a=>g, b=>c'::hstore || ( 'b'=>NULL );
+select ('a=>g, b=>c'::hstore || ( NULL=>'b' )) is null;
+select pg_column_size(('b'=>'gf'))
+         = pg_column_size('b=>gf'::hstore);
+select pg_column_size('a=>g, b=>c'::hstore || ('b'=>'gf'))
+         = pg_column_size('a=>g, b=>gf'::hstore);
+
+-- => 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'];
+select quote_literal('{}'::text[] => '{}'::text[]);
+select quote_literal('{}'::text[] => null);
+select ARRAY['a'] => '{}'::text[];  -- error
+select '{}'::text[] => ARRAY['a'];  -- error
+select pg_column_size(ARRAY['a','b','asd'] => ARRAY['g','h','i'])
+         = pg_column_size('a=>g, b=>h, asd=>i'::hstore);
+select pg_column_size(hstore 'aa=>1, b=>2, c=>3' => ARRAY['c','b'])
+         = pg_column_size('b=>2, c=>3'::hstore);
+select pg_column_size(hstore 'aa=>1, b=>2, c=>3' => ARRAY['c','b','aa'])
+         = pg_column_size('aa=>1, b=>2, c=>3'::hstore);
+
+-- array input
+select '{}'::text[]::hstore;
+select ARRAY['a','g','b','h','asd']::hstore;
+select ARRAY['a','g','b','h','asd','i']::hstore;
+select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
+select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
+select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
+select hstore('{}'::text[]);
+select hstore(ARRAY['a','g','b','h','asd']);
+select hstore(ARRAY['a','g','b','h','asd','i']);
+select hstore(ARRAY[['a','g'],['b','h'],['asd','i']]);
+select hstore(ARRAY[['a','g','b'],['h','asd','i']]);
+select hstore(ARRAY[[['a','g'],['b','h'],['asd','i']]]);
+select hstore('[0:5]={a,g,b,h,asd,i}'::text[]);
+select hstore('[0:2][1:2]={{a,g},{b,h},{asd,i}}'::text[]);
+
+-- records
+select hstore(v) from (values (1, 'foo', 1.2, 3::float8)) v(a,b,c,d);
+create domain hstestdom1 as integer not null default 0;
+create table testhstore0 (a integer, b text, c numeric, d float8);
+create table testhstore1 (a integer, b text, c numeric, d float8, e hstestdom1);
+insert into testhstore0 values (1, 'foo', 1.2, 3::float8);
+insert into testhstore1 values (1, 'foo', 1.2, 3::float8);
+select hstore(v) from testhstore1 v;
+select hstore(null::testhstore0);
+select hstore(null::testhstore1);
+select pg_column_size(hstore(v))
+         = pg_column_size('a=>1, b=>"foo", c=>"1.2", d=>"3", e=>"0"'::hstore)
+  from testhstore1 v;
+select populate_record(v, ('c' => '3.45')) from testhstore1 v;
+select populate_record(v, ('d' => '3.45')) from testhstore1 v;
+select populate_record(v, ('e' => '123')) from testhstore1 v;
+select populate_record(v, ('e' => null)) 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(v, ('b' => 'foo') || ('e' => null)) from testhstore0 v;
+select populate_record(v, ('b' => 'foo') || ('e' => null)) from testhstore1 v;
+select populate_record(v, '') from testhstore0 v;
+select populate_record(v, '') from testhstore1 v;
+select populate_record(null::testhstore1, ('c' => '3.45') || ('a' => '123'));
+select populate_record(null::testhstore1, ('c' => '3.45') || ('e' => '123'));
+select populate_record(null::testhstore0, '');
+select populate_record(null::testhstore1, '');
+select v #= ('c' => '3.45') from testhstore1 v;
+select v #= ('d' => '3.45') from testhstore1 v;
+select v #= ('e' => '123') from testhstore1 v;
+select v #= ('c' => null) from testhstore1 v;
+select v #= ('e' => null) from testhstore0 v;
+select v #= ('e' => null) from testhstore1 v;
+select v #= (('b' => 'foo') || ('a' => '123')) from testhstore1 v;
+select v #= (('b' => 'foo') || ('e' => '123')) from testhstore1 v;
+select v #= hstore '' from testhstore0 v;
+select v #= hstore '' from testhstore1 v;
+select null::testhstore1 #= (('c' => '3.45') || ('a' => '123'));
+select null::testhstore1 #= (('c' => '3.45') || ('e' => '123'));
+select null::testhstore0 #= hstore '';
+select null::testhstore1 #= hstore '';
+select v #= h from testhstore1 v, (values (hstore 'a=>123',1),('b=>foo,c=>3.21',2),('a=>null',3),('e=>123',4),('f=>blah',5)) x(h,i) order by i;
 
 -- keys/values
 select akeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
@@ -106,6 +260,12 @@ select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL');
 select avals('""=>1');
 select avals('');
 
+select hstore_to_array('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore);
+select %% 'aa=>1, cq=>l, b=>g, fg=>NULL';
+
+select hstore_to_matrix('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore);
+select %# 'aa=>1, cq=>l, b=>g, fg=>NULL';
+
 select * from skeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
 select * from skeys('""=>1');
 select * from skeys('');
@@ -132,6 +292,8 @@ select count(*) from testhstore where h @> 'wait=>NULL';
 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,6 +302,8 @@ select count(*) from testhstore where h @> 'wait=>NULL';
 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,6 +313,26 @@ select count(*) from testhstore where h @> 'wait=>NULL';
 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;
+
+-- 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;
+select distinct * from (values (hstore '' || ''),('')) v(h);
+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 17782d5c058f4048d518cc94d7e9ff57e0f89553..9162475ad1678a1c7c4656113257be66fa915a76 100644 (file)
@@ -1,36 +1,77 @@
-/* $PostgreSQL: pgsql/contrib/hstore/uninstall_hstore.sql,v 1.8 2009/03/25 22:19:01 tgl Exp $ */
+/* $PostgreSQL: pgsql/contrib/hstore/uninstall_hstore.sql,v 1.9 2009/09/30 19:50:22 tgl Exp $ */
 
 -- Adjust this setting to control where the objects get dropped.
 SET search_path = public;
 
 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, hstore );
-DROP OPERATOR <@( hstore, hstore );
-DROP OPERATOR @( hstore, hstore );
-DROP OPERATOR ~( hstore, hstore );
-DROP OPERATOR =>( text, text );
+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 %% ( NONE, hstore );
+DROP OPERATOR %# ( NONE, 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 CAST (text[] AS 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 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 hstore(text[],text[]);
+DROP FUNCTION hstore_to_array(hstore);
+DROP FUNCTION hstore_to_matrix(hstore);
+DROP FUNCTION hstore(record);
+DROP FUNCTION hstore(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);
@@ -41,6 +82,7 @@ DROP FUNCTION ghstore_consistent(internal,internal,int,oid,internal);
 DROP FUNCTION gin_consistent_hstore(internal, int2, internal, int4, internal, internal);
 DROP FUNCTION gin_extract_hstore(internal, internal);
 DROP FUNCTION gin_extract_hstore_query(internal, internal, smallint, internal, internal);
+DROP FUNCTION hstore_version_diag(hstore);
 
 DROP TYPE hstore CASCADE;
 DROP TYPE ghstore CASCADE;
index 48664b2b25b0143d849b62277101d408a0dfb3bc..78a2eb57ca510431a619177976b78a148e4e906d 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/hstore.sgml,v 1.3 2009/03/15 22:05:17 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/hstore.sgml,v 1.4 2009/09/30 19:50:22 tgl Exp $ -->
 
 <sect1 id="hstore">
  <title>hstore</title>
   This module implements a data type <type>hstore</> for storing sets of
   (key,value) pairs within a single <productname>PostgreSQL</> data field.
   This can be useful in various scenarios, such as rows with many attributes
-  that are rarely examined, or semi-structured data.
- </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.
+  that are rarely examined, or semi-structured data.  Keys and values are
+  arbitrary text strings.
  </para>
 
  <sect2>
@@ -39,9 +34,7 @@
    <literal>=&gt;</> sign is ignored.  Use double quotes if a key or
    value includes whitespace, comma, <literal>=</> or <literal>&gt;</>.
    To include a double quote or a backslash in a key or value, precede
-   it with another backslash.  (Keep in mind that depending on the
-   setting of <varname>standard_conforming_strings</>, you may need to
-   double backslashes in SQL literal strings.)
+   it with another backslash.
   </para>
 
   <para>
    as an ordinary data value.
   </para>
 
+  <note>
+  <para>
+   Keep in mind that the above format, when used to input hstore values,
+   applies <emphasis>before</> any required quoting or escaping. If you
+   are passing an hstore literal via a parameter, then no additional
+   processing is needed. If you are passing it as a quoted literal
+   constant, then any single-quote characters and (depending on the
+   setting of <varname>standard_conforming_strings</>) backslash characters
+   need to be escaped correctly. See <xref linkend="sql-syntax-strings">.
+  </para>
+  </note>
+
   <para>
-   Currently, double quotes are always used to surround key and value
+   Double quotes are always used to surround key and value
    strings on output, even when this is not strictly necessary.
   </para>
 
       <entry><literal>x</literal></entry>
      </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>
      </row>
 
+     <row>
+      <entry><type>text[]</> <literal>=&gt;</> <type>text[]</></entry>
+      <entry>construct an <type>hstore</> value from separate key and 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>extract a subset of an <type>hstore</> value</entry>
+      <entry><literal>'a=&gt;1,b=&gt;2,c=&gt;3'::hstore =&gt; ARRAY['b','c','x']</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>t</literal></entry>
      </row>
 
+     <row>
+      <entry><type>hstore</> <literal>?&amp;</> <type>text[]</></entry>
+      <entry>does <type>hstore</> contain all specified keys?</entry>
+      <entry><literal>'a=&gt;1,b=&gt;2'::hstore ?&amp; 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>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>replace fields in record with matching values from hstore</entry>
+      <entry>see Examples section</entry>
+      <entry></entry>
+     </row>
+
+     <row>
+      <entry><literal>%%</> <type>hstore</></entry>
+      <entry>convert hstore to array of alternating keys and values</entry>
+      <entry><literal>%% 'a=&gt;foo, b=&gt;bar'::hstore</literal></entry>
+      <entry><literal>{a,foo,b,bar}</literal></entry>
+     </row>
+
+     <row>
+      <entry><literal>%#</> <type>hstore</></entry>
+      <entry>convert hstore to two-dimensional key/value array</entry>
+      <entry><literal>%# 'a=&gt;foo, b=&gt;bar'::hstore</literal></entry>
+      <entry><literal>{{a,foo},{b,bar}}</literal></entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
     </thead>
 
     <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>hstore(text[])</function></entry>
+      <entry><type>hstore</type></entry>
+      <entry>construct an <type>hstore</> from an array, which may be either
+       a key/value array, or a two-dimensional array</entry>
+      <entry><literal>hstore(ARRAY['a','1','b','2']) || hstore(ARRAY[['c','3'],['d','4']])</literal></entry>
+      <entry><literal>a=&gt;1, b=&gt;2, c=&gt;3, d=&gt;4</literal></entry>
+     </row>
+
      <row>
       <entry><function>akeys(hstore)</function></entry>
       <entry><type>text[]</type></entry>
@@ -189,6 +288,23 @@ b
 </programlisting></entry>
      </row>
 
+     <row>
+      <entry><function>hstore_to_array(hstore)</function></entry>
+      <entry><type>text[]</type></entry>
+      <entry>get <type>hstore</>'s keys and values as an array of alternating
+       keys and values</entry>
+      <entry><literal>hstore_to_array('a=&gt;1,b=&gt;2')</literal></entry>
+      <entry><literal>{a,1,b,2}</literal></entry>
+     </row>
+
+     <row>
+      <entry><function>hstore_to_matrix(hstore)</function></entry>
+      <entry><type>text[]</type></entry>
+      <entry>get <type>hstore</>'s keys and values as a two-dimensional array</entry>
+      <entry><literal>hstore_to_matrix('a=&gt;1,b=&gt;2')</literal></entry>
+      <entry><literal>{{a,1},{b,2}}</literal></entry>
+     </row>
+
      <row>
       <entry><function>each(hstore)</function></entry>
       <entry><type>setof (key text, value text)</type></entry>
@@ -227,22 +343,71 @@ b
       <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 with an exact match 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>replace fields in record with matching values from hstore</entry>
+      <entry>see Examples section</entry>
+      <entry></entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
+
+  <note>
+   <para>
+    The function <function>populate_record</function> is actually declared
+    with <type>anyelement</>, not <type>record</>, as its first argument;
+    but it will reject non-record types with a runtime error.
+   </para>
+  </note>
  </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:
+   <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 GIST (h);
 
-CREATE INDEX hidx ON testhstore USING GIN(h);
+CREATE INDEX hidx ON testhstore USING GIN (h);
+  </programlisting>
+
+  <para>
+   Additionally, <type>hstore</> has index support for the <literal>=</>
+   operator using the <type>btree</> or <type>hash</> index types. This
+   allows <type>hstore</> columns to be declared UNIQUE, or used with
+   GROUP BY, ORDER BY or DISTINCT. The sort ordering for <type>hstore</>
+   values is not intended to be particularly useful; it merely brings
+   exactly equal values together.
+   If an index is needed to support <literal>=</> comparisons it can be
+   created as follows:
+  </para>
+  <programlisting>
+CREATE INDEX hidx ON testhstore USING BTREE (h);
+
+CREATE INDEX hidx ON testhstore USING HASH (h);
   </programlisting>
  </sect2>
 
@@ -262,6 +427,48 @@ UPDATE tab SET h = h || ('c' => '3');
   <programlisting>
 UPDATE tab SET h = delete(h, 'k1');
   </programlisting>
+
+  <para>
+   Convert a record to an hstore:
+  </para>
+  <programlisting>
+CREATE TABLE test (col1 integer, col2 text, col3 text);
+INSERT INTO test VALUES (123, 'foo', 'bar');
+
+SELECT hstore(t) FROM test AS t;
+                   hstore                    
+---------------------------------------------
+ "col1"=>"123", "col2"=>"foo", "col3"=>"bar"
+(1 row)
+  </programlisting>
+
+  <para>
+   Convert an hstore to a predefined record type:
+  </para>
+  <programlisting>
+CREATE TABLE test (col1 integer, col2 text, col3 text);
+
+SELECT * FROM populate_record(null::test,
+                              '"col1"=>"456", "col2"=>"zzz"');
+ col1 | col2 | col3 
+------+------+------
+  456 | zzz  | 
+(1 row)
+  </programlisting>
+
+  <para>
+   Modify an existing record using the values from an hstore:
+  </para>
+  <programlisting>
+CREATE TABLE test (col1 integer, col2 text, col3 text);
+INSERT INTO test VALUES (123, 'foo', 'bar');
+
+SELECT (r).* FROM (SELECT t #= '"col3"=>"baz"' AS r FROM test t) s;
+ col1 | col2 | col3 
+------+------+------
+  123 | foo  | baz
+(1 row)
+  </programlisting>
  </sect2>
 
  <sect2>
@@ -311,6 +518,45 @@ SELECT key, count(*) FROM
   </programlisting>
  </sect2>
 
+ <sect2>
+  <title>Compatibility</title>
+
+  <para>
+   <emphasis>When upgrading from older versions, always load the new
+   version of this module into the database before restoring an old
+   dump. Otherwise, many new features will be unavailable.</emphasis>
+  </para>
+
+  <para>
+   As of PostgreSQL 8.5, <type>hstore</> uses a different internal
+   representation than previous versions. This presents no obstacle for
+   dump/restore upgrades since the text representation (used in the dump) is
+   unchanged.
+  </para>
+
+  <para>
+   In the event of doing a binary upgrade, upward
+   compatibility is maintained by having the new code recognize
+   old-format data. This will entail a slight performance penalty when
+   processing data that has not yet been modified by the new code. It is
+   possible to force an upgrade of all values in a table column
+   by doing an UPDATE statement as follows:
+  </para>
+  <programlisting>
+UPDATE tablename SET hstorecol = hstorecol || '';
+  </programlisting>
+
+  <para>
+   Another way to do it is:
+  <programlisting>
+ALTER TABLE tablename ALTER hstorecol TYPE hstore USING hstorecol || '';
+  </programlisting>
+   The <command>ALTER TABLE</> method requires an exclusive lock on the table,
+   but does not result in bloating the table with old row versions.
+  </para>
+
+ </sect2>
+
  <sect2>
   <title>Authors</title>
 
@@ -321,6 +567,10 @@ SELECT key, count(*) FROM
   <para>
    Teodor Sigaev <email>teodor@sigaev.ru</email>, Moscow, Delta-Soft Ltd., Russia
   </para>
+
+  <para>
+   Additional enhancements by Andrew Gierth <email>andrew@tao11.riddles.org.uk</email>, United Kingdom
+  </para>
  </sect2>
 
 </sect1>