Assorted improvements in contrib/hstore.

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
This commit is contained in:
Tom Lane 2009-09-30 19:50:22 +00:00
parent 1d43e5314e
commit 172eacba43
12 changed files with 4026 additions and 586 deletions

View 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

View 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)

View File

@ -1,59 +1,197 @@
/*
* $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 PG_GETARG_HS(x) ((HStore*)PG_DETOAST_DATUM(PG_GETARG_DATUM(x)))
#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) 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);
size_t hstoreCheckKeyLen(size_t len);
size_t hstoreCheckValLen(size_t len);
extern size_t hstoreCheckKeyLen(size_t len);
extern 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__ */

View 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
);
-- define the GiST support methods
-- btree support
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;

View File

@ -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);
}

View 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);

View 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);

View 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;
entries = ARRPTR(out);
ptr = STRPTR(out);
for (i = 0; i < out->size; i++)
for (i = 0; i < pcount; ++i)
{
entries[i].keylen = state.pairs[i].keylen;
entries[i].pos = ptr - STRPTR(out);
memcpy(ptr, state.pairs[i].key, state.pairs[i].keylen);
ptr += entries[i].keylen;
int rawlen = pq_getmsgint(buf, 4);
int len;
entries[i].valisnull = state.pairs[i].isnull;
if (entries[i].valisnull)
entries[i].vallen = 4; /* null */
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
{
entries[i].vallen = state.pairs[i].vallen;
memcpy(ptr, state.pairs[i].val, state.pairs[i].vallen);
ptr += entries[i].vallen;
pairs[i].val = pq_getmsgtext(buf, rawlen, &len);
pairs[i].vallen = hstoreCheckValLen(len);
pairs[i].isnull = false;
}
}
freeHSParse(&state);
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
*/
if (ARR_NDIM(key_array) > 1)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("wrong number of array subscripts")));
deconstruct_array(key_array,
TEXTOID, -1, false, 'i',
&key_datums, &key_nulls, &key_count);
/* value_array might be NULL */
if (PG_ARGISNULL(1))
{
value_array = NULL;
value_count = key_count;
value_datums = NULL;
value_nulls = NULL;
}
else
{
value_array = PG_GETARG_ARRAYTYPE_P(1);
Assert(ARR_ELEMTYPE(value_array) == TEXTOID);
if (ARR_NDIM(value_array) > 1)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("wrong number of array subscripts")));
if ((ARR_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);
Assert(key_count == value_count);
}
pairs = palloc(key_count * sizeof(Pairs));
for (i = 0; i < key_count; ++i)
{
if (key_nulls[i])
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value not allowed for hstore key")));
if (!value_nulls || value_nulls[i])
{
pairs[i].key = VARDATA_ANY(key_datums[i]);
pairs[i].val = NULL;
pairs[i].keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key_datums[i]));
pairs[i].vallen = 4;
pairs[i].isnull = true;
pairs[i].needfree = false;
}
else
{
pairs[i].key = VARDATA_ANY(key_datums[i]);
pairs[i].val = VARDATA_ANY(value_datums[i]);
pairs[i].keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key_datums[i]));
pairs[i].vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(value_datums[i]));
pairs[i].isnull = false;
pairs[i].needfree = false;
}
}
key_count = hstoreUniquePairs(pairs, key_count, &buflen);
out = hstorePairs(pairs, key_count, buflen);
PG_RETURN_POINTER(out);
}
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;
}
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;
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 */ ;
/*
* this loop overestimates due to pessimistic assumptions about
* escaping, so very large hstore values can't be output. this
* could be fixed, but many other data types probably have the
* same issue. This replaced code that used the original varlena
* size for calculations, which was wrong in some subtle ways.
*/
for (i = 0; i < count; i++)
{
/* include "" and => and comma-space */
buflen += 6 + 2 * HS_KEYLEN(entries,i);
/* include "" only if nonnull */
buflen += 2 + (HS_VALISNULL(entries,i)
? 2
: 2 * HS_VALLEN(entries,i));
}
out = ptr = palloc(buflen);
for (i = 0; i < 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));
}

File diff suppressed because it is too large Load Diff

View 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';

View 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;

View 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>
@ -11,13 +11,8 @@
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>
@ -56,8 +49,20 @@
as an ordinary data value.
</para>
<note>
<para>
Currently, double quotes are always used to surround key and value
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>
Double quotes are always used to surround key and value
strings on output, even when this is not strictly necessary.
</para>
@ -87,6 +92,13 @@
<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>
@ -94,6 +106,20 @@
<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>
@ -108,6 +134,20 @@
<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>
@ -122,6 +162,48 @@
<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>
@ -149,6 +231,23 @@
</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>