Commit 172eacba authored by Tom Lane's avatar Tom Lane

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
parent 1d43e531
# $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
......
......@@ -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
......@@ -441,8 +1125,8 @@ select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL');
avals
------------
{g,1,l,""}
--------------
{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)
/*
* $PostgreSQL: pgsql/contrib/hstore/hstore.h,v 1.8 2009/06/11 14:48:51 momjian Exp $
* $PostgreSQL: pgsql/contrib/hstore/hstore.h,v 1.9 2009/09/30 19:50:22 tgl Exp $
*/
#ifndef __HSTORE_H__
#define __HSTORE_H__
#include "fmgr.h"
#include "utils/array.h"
/*
* HEntry: there is one of these for each key _and_ value in an hstore
*
* the position offset points to the _end_ so that we can get the length
* by subtraction from the previous entry. the ISFIRST flag lets us tell
* whether there is a previous entry.
*/
typedef struct
{
uint16 keylen;
uint16 vallen;
uint32
valisnull:1,
pos:31;
uint32 entry;
} HEntry;
/* these are determined by the sizes of the keylen and vallen fields */
/* in struct HEntry and struct Pairs */
#define HSTORE_MAX_KEY_LEN 65535
#define HSTORE_MAX_VALUE_LEN 65535
#define HENTRY_ISFIRST 0x80000000
#define HENTRY_ISNULL 0x40000000
#define HENTRY_POSMASK 0x3FFFFFFF
/* note possible multiple evaluations, also access to prior array element */
#define HSE_ISFIRST(he_) (((he_).entry & HENTRY_ISFIRST) != 0)
#define HSE_ISNULL(he_) (((he_).entry & HENTRY_ISNULL) != 0)
#define HSE_ENDPOS(he_) ((he_).entry & HENTRY_POSMASK)
#define HSE_OFF(he_) (HSE_ISFIRST(he_) ? 0 : HSE_ENDPOS((&(he_))[-1]))
#define HSE_LEN(he_) (HSE_ISFIRST(he_) \
? HSE_ENDPOS(he_) \
: HSE_ENDPOS(he_) - HSE_ENDPOS((&(he_))[-1]))
/*
* determined by the size of "endpos" (ie HENTRY_POSMASK), though this is a
* bit academic since currently varlenas (and hence both the input and the
* whole hstore) have the same limit
*/
#define HSTORE_MAX_KEY_LEN 0x3FFFFFFF
#define HSTORE_MAX_VALUE_LEN 0x3FFFFFFF
typedef struct
{
int32 vl_len_; /* varlena header (do not touch directly!) */
int4 size;
char data[1];
uint32 size_; /* flags and number of items in hstore */
/* array of HEntry follows */
} HStore;
#define HSHRDSIZE (VARHDRSZ + sizeof(int4))
#define CALCDATASIZE(x, lenstr) ( (x) * sizeof(HEntry) + HSHRDSIZE + (lenstr) )
#define ARRPTR(x) ( (HEntry*) ( (char*)(x) + HSHRDSIZE ) )
#define STRPTR(x) ( (char*)(x) + HSHRDSIZE + ( sizeof(HEntry) * ((HStore*)x)->size ) )
/*
* it's not possible to get more than 2^28 items into an hstore,
* so we reserve the top few bits of the size field. See hstore_compat.c
* for one reason why. Some bits are left for future use here.
*/
#define HS_FLAG_NEWVERSION 0x80000000
#define HS_COUNT(hsp_) ((hsp_)->size_ & 0x0FFFFFFF)
#define HS_SETCOUNT(hsp_,c_) ((hsp_)->size_ = (c_) | HS_FLAG_NEWVERSION)
#define HSHRDSIZE (sizeof(HStore))
#define CALCDATASIZE(x, lenstr) ( (x) * 2 * sizeof(HEntry) + HSHRDSIZE + (lenstr) )
/* note multiple evaluations of x */
#define ARRPTR(x) ( (HEntry*) ( (HStore*)(x) + 1 ) )
#define STRPTR(x) ( (char*)(ARRPTR(x) + HS_COUNT((HStore*)(x)) * 2) )
/* note multiple/non evaluations */
#define HS_KEY(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)]))
#define HS_VAL(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)+1]))
#define HS_KEYLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)]))
#define HS_VALLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)+1]))
#define HS_VALISNULL(arr_,i_) (HSE_ISNULL((arr_)[2*(i_)+1]))
/*
* currently, these following macros are the _only_ places that rely
* on internal knowledge of HEntry. Everything else should be using
* the above macros. Exception: the in-place upgrade in hstore_compat.c
* messes with entries directly.
*/
/*
* copy one key/value pair (which must be contiguous starting at
* sptr_) into an under-construction hstore; dent_ is an HEntry*,
* dbuf_ is the destination's string buffer, dptr_ is the current
* position in the destination. lots of modification and multiple
* evaluation here.
*/
#define HS_COPYITEM(dent_,dbuf_,dptr_,sptr_,klen_,vlen_,vnull_) \
do { \
memcpy((dptr_), (sptr_), (klen_)+(vlen_)); \
(dptr_) += (klen_)+(vlen_); \
(dent_)++->entry = ((dptr_) - (dbuf_) - (vlen_)) & HENTRY_POSMASK; \
(dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK) \
| ((vnull_) ? HENTRY_ISNULL : 0)); \
} while(0)
/*
* add one key/item pair, from a Pairs structure, into an
* under-construction hstore
*/
#define HS_ADDITEM(dent_,dbuf_,dptr_,pair_) \
do { \
memcpy((dptr_), (pair_).key, (pair_).keylen); \
(dptr_) += (pair_).keylen; \
(dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK; \
if ((pair_).isnull) \
(dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK) \
| HENTRY_ISNULL); \
else \
{ \
memcpy((dptr_), (pair_).val, (pair_).vallen); \
(dptr_) += (pair_).vallen; \
(dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK; \
} \
} while (0)
/* finalize a newly-constructed hstore */
#define HS_FINALIZE(hsp_,count_,buf_,ptr_) \
do { \
int buflen = (ptr_) - (buf_); \
if ((count_)) \
ARRPTR(hsp_)[0].entry |= HENTRY_ISFIRST; \
if ((count_) != HS_COUNT((hsp_))) \
{ \
HS_SETCOUNT((hsp_),(count_)); \
memmove(STRPTR(hsp_), (buf_), buflen); \
} \
SET_VARSIZE((hsp_), CALCDATASIZE((count_), buflen)); \
} while (0)
/* ensure the varlena size of an existing hstore is correct */
#define HS_FIXSIZE(hsp_,count_) \
do { \
int bl = (count_) ? HSE_ENDPOS(ARRPTR(hsp_)[2*(count_)-1]) : 0; \
SET_VARSIZE((hsp_), CALCDATASIZE((count_),bl)); \
} while (0)
/* DatumGetHStoreP includes support for reading old-format hstore values */
extern HStore *hstoreUpgrade(Datum orig);
#define DatumGetHStoreP(d) hstoreUpgrade(d)
#define PG_GETARG_HS(x) ((HStore*)PG_DETOAST_DATUM(PG_GETARG_DATUM(x)))
#define PG_GETARG_HS(x) DatumGetHStoreP(PG_GETARG_DATUM(x))
/*
* Pairs is a "decompressed" representation of one key/value pair.
* The two strings are not necessarily null-terminated.
*/
typedef struct
{
char *key;
char *val;
uint16 keylen;
uint16 vallen;
bool isnull;
bool needfree;
size_t keylen;
size_t vallen;
bool isnull; /* value is null? */
bool needfree; /* need to pfree the value? */
} Pairs;
int comparePairs(const void *a, const void *b);
int uniquePairs(Pairs *a, int4 l, int4 *buflen);
extern int hstoreUniquePairs(Pairs *a, int4 l, int4 *buflen);
extern HStore *hstorePairs(Pairs *pairs, int4 pcount, int4 buflen);
extern size_t hstoreCheckKeyLen(size_t len);
extern size_t hstoreCheckValLen(size_t len);
size_t hstoreCheckKeyLen(size_t len);
size_t hstoreCheckValLen(size_t len);
extern int hstoreFindKey(HStore *hs, int *lowbound, char *key, int keylen);
extern Pairs *hstoreArrayToPairs(ArrayType *a, int *npairs);
#define HStoreContainsStrategyNumber 7
#define HStoreExistsStrategyNumber 9
#define HStoreExistsAnyStrategyNumber 10
#define HStoreExistsAllStrategyNumber 11
#define HStoreOldContainsStrategyNumber 13 /* backwards compatibility */
/*
* defining HSTORE_POLLUTE_NAMESPACE=0 will prevent use of old function names;
* for now, we default to on for the benefit of people restoring old dumps
*/
#ifndef HSTORE_POLLUTE_NAMESPACE
#define HSTORE_POLLUTE_NAMESPACE 1
#endif
#if HSTORE_POLLUTE_NAMESPACE
#define HSTORE_POLLUTE(newname_,oldname_) \
PG_FUNCTION_INFO_V1(oldname_); \
Datum oldname_(PG_FUNCTION_ARGS); \
Datum newname_(PG_FUNCTION_ARGS); \
Datum oldname_(PG_FUNCTION_ARGS) { return newname_(fcinfo); } \
extern int no_such_variable
#else
#define HSTORE_POLLUTE(newname_,oldname_) \
extern int no_such_variable
#endif
#endif /* __HSTORE_H__ */
/* $PostgreSQL: pgsql/contrib/hstore/hstore.sql.in,v 1.11 2009/06/11 18:30:03 tgl Exp $ */
/* $PostgreSQL: pgsql/contrib/hstore/hstore.sql.in,v 1.12 2009/09/30 19:50:22 tgl Exp $ */
-- Adjust this setting to control where the objects get created.
SET search_path = public;
......@@ -8,23 +8,40 @@ CREATE TYPE hstore;
CREATE OR REPLACE FUNCTION hstore_in(cstring)
RETURNS hstore
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION hstore_out(hstore)
RETURNS cstring
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION hstore_recv(internal)
RETURNS hstore
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION hstore_send(hstore)
RETURNS bytea
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT IMMUTABLE;
CREATE TYPE hstore (
INTERNALLENGTH = -1,
INPUT = hstore_in,
OUTPUT = hstore_out,
RECEIVE = hstore_recv,
SEND = hstore_send,
STORAGE = extended
);
CREATE OR REPLACE FUNCTION hstore_version_diag(hstore)
RETURNS integer
AS 'MODULE_PATHNAME','hstore_version_diag'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION fetchval(hstore,text)
RETURNS text
AS 'MODULE_PATHNAME'
AS 'MODULE_PATHNAME','hstore_fetchval'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR -> (
......@@ -33,14 +50,36 @@ CREATE OPERATOR -> (
PROCEDURE = fetchval
);
CREATE OR REPLACE FUNCTION slice_array(hstore,text[])
RETURNS text[]
AS 'MODULE_PATHNAME','hstore_slice_to_array'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR -> (
LEFTARG = hstore,
RIGHTARG = text[],
PROCEDURE = slice_array
);
CREATE OR REPLACE FUNCTION slice_hstore(hstore,text[])
RETURNS hstore
AS 'MODULE_PATHNAME','hstore_slice_to_hstore'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR => (
LEFTARG = hstore,
RIGHTARG = text[],
PROCEDURE = slice_hstore
);
CREATE OR REPLACE FUNCTION isexists(hstore,text)
RETURNS bool
AS 'MODULE_PATHNAME','exists'
AS 'MODULE_PATHNAME','hstore_exists'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION exist(hstore,text)
RETURNS bool
AS 'MODULE_PATHNAME','exists'
AS 'MODULE_PATHNAME','hstore_exists'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR ? (
......@@ -51,24 +90,78 @@ CREATE OPERATOR ? (
JOIN = contjoinsel
);
CREATE OR REPLACE FUNCTION exists_any(hstore,text[])
RETURNS bool
AS 'MODULE_PATHNAME','hstore_exists_any'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR ?| (
LEFTARG = hstore,
RIGHTARG = text[],
PROCEDURE = exists_any,
RESTRICT = contsel,
JOIN = contjoinsel
);
CREATE OR REPLACE FUNCTION exists_all(hstore,text[])
RETURNS bool
AS 'MODULE_PATHNAME','hstore_exists_all'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR ?& (
LEFTARG = hstore,
RIGHTARG = text[],
PROCEDURE = exists_all,
RESTRICT = contsel,
JOIN = contjoinsel
);
CREATE OR REPLACE FUNCTION isdefined(hstore,text)
RETURNS bool
AS 'MODULE_PATHNAME','defined'
AS 'MODULE_PATHNAME','hstore_defined'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION defined(hstore,text)
RETURNS bool
AS 'MODULE_PATHNAME','defined'
AS 'MODULE_PATHNAME','hstore_defined'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION delete(hstore,text)
RETURNS hstore
AS 'MODULE_PATHNAME','delete'
AS 'MODULE_PATHNAME','hstore_delete'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION delete(hstore,text[])
RETURNS hstore
AS 'MODULE_PATHNAME','hstore_delete_array'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION delete(hstore,hstore)
RETURNS hstore
AS 'MODULE_PATHNAME','hstore_delete_hstore'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR - (
LEFTARG = hstore,
RIGHTARG = text,
PROCEDURE = delete
);
CREATE OPERATOR - (
LEFTARG = hstore,
RIGHTARG = text[],
PROCEDURE = delete
);
CREATE OPERATOR - (
LEFTARG = hstore,
RIGHTARG = hstore,
PROCEDURE = delete
);
CREATE OR REPLACE FUNCTION hs_concat(hstore,hstore)
RETURNS hstore
AS 'MODULE_PATHNAME'
AS 'MODULE_PATHNAME','hstore_concat'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR || (
......@@ -79,12 +172,12 @@ CREATE OPERATOR || (
CREATE OR REPLACE FUNCTION hs_contains(hstore,hstore)
RETURNS bool
AS 'MODULE_PATHNAME'
AS 'MODULE_PATHNAME','hstore_contains'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION hs_contained(hstore,hstore)
RETURNS bool
AS 'MODULE_PATHNAME'
AS 'MODULE_PATHNAME','hstore_contained'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR @> (
......@@ -126,57 +219,237 @@ CREATE OPERATOR ~ (
CREATE OR REPLACE FUNCTION tconvert(text,text)
RETURNS hstore
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE; -- not STRICT
AS 'MODULE_PATHNAME','hstore_from_text'
LANGUAGE C IMMUTABLE; -- not STRICT; needs to allow (key,NULL)
CREATE OR REPLACE FUNCTION hstore(text,text)
RETURNS hstore
AS 'MODULE_PATHNAME','hstore_from_text'
LANGUAGE C IMMUTABLE; -- not STRICT; needs to allow (key,NULL)
CREATE OPERATOR => (
LEFTARG = text,
RIGHTARG = text,
PROCEDURE = tconvert
PROCEDURE = hstore
);
CREATE OR REPLACE FUNCTION hstore(text[],text[])
RETURNS hstore
AS 'MODULE_PATHNAME', 'hstore_from_arrays'
LANGUAGE C IMMUTABLE; -- not STRICT; allows (keys,null)
CREATE OPERATOR => (
LEFTARG = text[],
RIGHTARG = text[],
PROCEDURE = hstore
);
CREATE FUNCTION hstore(text[])
RETURNS hstore
AS 'MODULE_PATHNAME', 'hstore_from_array'
LANGUAGE C IMMUTABLE STRICT;
CREATE CAST (text[] AS hstore)
WITH FUNCTION hstore(text[]);
CREATE OR REPLACE FUNCTION hstore(record)
RETURNS hstore
AS 'MODULE_PATHNAME', 'hstore_from_record'
LANGUAGE C IMMUTABLE; -- not STRICT; allows (null::recordtype)
CREATE OR REPLACE FUNCTION hstore_to_array(hstore)
RETURNS text[]
AS 'MODULE_PATHNAME','hstore_to_array'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR %% (
RIGHTARG = hstore,
PROCEDURE = hstore_to_array
);
CREATE OR REPLACE FUNCTION hstore_to_matrix(hstore)
RETURNS text[]
AS 'MODULE_PATHNAME','hstore_to_matrix'
LANGUAGE C STRICT IMMUTABLE;
CREATE OPERATOR %# (
RIGHTARG = hstore,
PROCEDURE = hstore_to_matrix
);
CREATE OR REPLACE FUNCTION akeys(hstore)
RETURNS _text
AS 'MODULE_PATHNAME'
RETURNS text[]
AS 'MODULE_PATHNAME','hstore_akeys'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION avals(hstore)
RETURNS _text
AS 'MODULE_PATHNAME'
RETURNS text[]
AS 'MODULE_PATHNAME','hstore_avals'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION skeys(hstore)
RETURNS setof text
AS 'MODULE_PATHNAME'
AS 'MODULE_PATHNAME','hstore_skeys'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION svals(hstore)
RETURNS setof text
AS 'MODULE_PATHNAME'
AS 'MODULE_PATHNAME','hstore_svals'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION each(IN hs hstore,
OUT key text,
OUT value text)
RETURNS SETOF record
AS 'MODULE_PATHNAME'
AS 'MODULE_PATHNAME','hstore_each'
LANGUAGE C STRICT IMMUTABLE;
CREATE OR REPLACE FUNCTION populate_record(anyelement,hstore)
RETURNS anyelement
AS 'MODULE_PATHNAME', 'hstore_populate_record'
LANGUAGE C IMMUTABLE; -- not STRICT; allows (null::rectype,hstore)
CREATE OPERATOR #= (
LEFTARG = anyelement,
RIGHTARG = hstore,
PROCEDURE = populate_record
);
-- btree support
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);
-- define the GiST support methods
-- 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 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;
/*
* $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);
}
/*
* $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;
if (hs->size > 0)
{
HEntry *ptr = ARRPTR(hs);
char *words = STRPTR(hs);
int i = 0;
entries = (Datum *) palloc(sizeof(Datum) * 2 * hs->size);
*nentries = 2 * count;
if (count)
entries = (Datum *) palloc(sizeof(Datum) * 2 * count);
while (ptr - ARRPTR(hs) < hs->size)
for (i = 0; i < count; ++i)
{
text *item;
item = makeitem(words + ptr->pos, ptr->keylen);
item = makeitem(HS_KEY(hsent,ptr,i), HS_KEYLEN(hsent,i));
*VARDATA(item) = KEYFLAG;
entries[i++] = PointerGetDatum(item);
entries[2*i] = PointerGetDatum(item);
if (ptr->valisnull)
if (HS_VALISNULL(hsent,i))
{
item = makeitem(NULL, 0);
*VARDATA(item) = NULLFLAG;
}
else
{
item = makeitem(words + ptr->pos + ptr->keylen, ptr->vallen);
item = makeitem(HS_VAL(hsent,ptr,i), HS_VALLEN(hsent,i));
*VARDATA(item) = VALFLAG;
}
entries[i++] = PointerGetDatum(item);
ptr++;
}
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);
......
/*
* $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;
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);
......
/*
* $PostgreSQL: pgsql/contrib/hstore/hstore_io.c,v 1.11 2009/06/11 14:48:51 momjian Exp $
* $PostgreSQL: pgsql/contrib/hstore/hstore_io.c,v 1.12 2009/09/30 19:50:22 tgl Exp $
*/
#include "postgres.h"
#include <ctype.h>
#include "access/heapam.h"
#include "access/htup.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
#include "hstore.h"
PG_MODULE_MAGIC;
/* old names for C functions */
HSTORE_POLLUTE(hstore_from_text,tconvert);
typedef struct
{
char *begin;
......@@ -263,7 +275,7 @@ parse_hstore(HSParser *state)
}
}
int
static int
comparePairs(const void *a, const void *b)
{
if (((Pairs *) a)->keylen == ((Pairs *) b)->keylen)
......@@ -286,8 +298,14 @@ comparePairs(const void *a, const void *b)
return (((Pairs *) a)->keylen > ((Pairs *) b)->keylen) ? 1 : -1;
}
/*
* this code still respects pairs.needfree, even though in general
* it should never be called in a context where anything needs freeing.
* we keep it because (a) those calls are in a rare code path anyway,
* and (b) who knows whether they might be needed by some caller.
*/
int
uniquePairs(Pairs *a, int4 l, int4 *buflen)
hstoreUniquePairs(Pairs *a, int4 l, int4 *buflen)
{
Pairs *ptr,
*res;
......@@ -305,7 +323,8 @@ uniquePairs(Pairs *a, int4 l, int4 *buflen)
res = a;
while (ptr - a < l)
{
if (ptr->keylen == res->keylen && strncmp(ptr->key, res->key, res->keylen) == 0)
if (ptr->keylen == res->keylen &&
strncmp(ptr->key, res->key, res->keylen) == 0)
{
if (ptr->needfree)
{
......@@ -327,24 +346,6 @@ uniquePairs(Pairs *a, int4 l, int4 *buflen)
return res + 1 - a;
}
static void
freeHSParse(HSParser *state)
{
int i;
if (state->word)
pfree(state->word);
for (i = 0; i < state->pcur; i++)
if (state->pairs[i].needfree)
{
if (state->pairs[i].key)
pfree(state->pairs[i].key);
if (state->pairs[i].val)
pfree(state->pairs[i].val);
}
pfree(state->pairs);
}
size_t
hstoreCheckKeyLen(size_t len)
{
......@@ -366,65 +367,722 @@ hstoreCheckValLen(size_t len)
}
HStore *
hstorePairs(Pairs *pairs, int4 pcount, int4 buflen)
{
HStore *out;
HEntry *entry;
char *ptr;
char *buf;
int4 len;
int4 i;
len = CALCDATASIZE(pcount, buflen);
out = palloc(len);
SET_VARSIZE(out, len);
HS_SETCOUNT(out, pcount);
if (pcount == 0)
return out;
entry = ARRPTR(out);
buf = ptr = STRPTR(out);
for (i = 0; i < pcount; i++)
HS_ADDITEM(entry,buf,ptr,pairs[i]);
HS_FINALIZE(out,pcount,buf,ptr);
return out;
}
PG_FUNCTION_INFO_V1(hstore_in);
Datum hstore_in(PG_FUNCTION_ARGS);
Datum
hstore_in(PG_FUNCTION_ARGS)
{
HSParser state;
int4 len,
buflen,
i;
int4 buflen;
HStore *out;
HEntry *entries;
char *ptr;
state.begin = PG_GETARG_CSTRING(0);
parse_hstore(&state);
if (state.pcur == 0)
state.pcur = hstoreUniquePairs(state.pairs, state.pcur, &buflen);
out = hstorePairs(state.pairs, state.pcur, buflen);
PG_RETURN_POINTER(out);
}
PG_FUNCTION_INFO_V1(hstore_recv);
Datum hstore_recv(PG_FUNCTION_ARGS);
Datum
hstore_recv(PG_FUNCTION_ARGS)
{
int4 buflen;
HStore *out;
Pairs *pairs;
int4 i;
int4 pcount;
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
pcount = pq_getmsgint(buf, 4);
if (pcount == 0)
{
freeHSParse(&state);
len = CALCDATASIZE(0, 0);
out = palloc(len);
SET_VARSIZE(out, len);
out->size = 0;
out = hstorePairs(NULL, 0, 0);
PG_RETURN_POINTER(out);
}
state.pcur = uniquePairs(state.pairs, state.pcur, &buflen);
pairs = palloc(pcount * sizeof(Pairs));
len = CALCDATASIZE(state.pcur, buflen);
out = palloc(len);
SET_VARSIZE(out, len);
out->size = state.pcur;
for (i = 0; i < pcount; ++i)
{
int rawlen = pq_getmsgint(buf, 4);
int len;
entries = ARRPTR(out);
ptr = STRPTR(out);
if (rawlen < 0)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value not allowed for hstore key")));
for (i = 0; i < out->size; i++)
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)
{
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;
pairs[i].val = NULL;
pairs[i].vallen = 0;
pairs[i].isnull = true;
}
else
{
pairs[i].val = pq_getmsgtext(buf, rawlen, &len);
pairs[i].vallen = hstoreCheckValLen(len);
pairs[i].isnull = false;
}
}
pcount = hstoreUniquePairs(pairs, pcount, &buflen);
out = hstorePairs(pairs, pcount, buflen);
PG_RETURN_POINTER(out);
}
PG_FUNCTION_INFO_V1(hstore_from_text);
Datum hstore_from_text(PG_FUNCTION_ARGS);
Datum
hstore_from_text(PG_FUNCTION_ARGS)
{
text *key;
text *val = NULL;
Pairs p;
HStore *out;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
entries[i].valisnull = state.pairs[i].isnull;
if (entries[i].valisnull)
entries[i].vallen = 4; /* 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
{
entries[i].vallen = state.pairs[i].vallen;
memcpy(ptr, state.pairs[i].val, state.pairs[i].vallen);
ptr += entries[i].vallen;
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")));
}
freeHSParse(&state);
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));
}
/*
* $PostgreSQL
* $PostgreSQL: pgsql/contrib/hstore/hstore_op.c,v 1.14 2009/09/30 19:50:22 tgl Exp $
*/
#include "postgres.h"
#include "access/hash.h"
#include "access/heapam.h"
#include "access/htup.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "hstore.h"
/* old names for C functions */
HSTORE_POLLUTE(hstore_fetchval,fetchval);
HSTORE_POLLUTE(hstore_exists,exists);
HSTORE_POLLUTE(hstore_defined,defined);
HSTORE_POLLUTE(hstore_delete,delete);
HSTORE_POLLUTE(hstore_concat,hs_concat);
HSTORE_POLLUTE(hstore_contains,hs_contains);
HSTORE_POLLUTE(hstore_contained,hs_contained);
HSTORE_POLLUTE(hstore_akeys,akeys);
HSTORE_POLLUTE(hstore_avals,avals);
HSTORE_POLLUTE(hstore_skeys,skeys);
HSTORE_POLLUTE(hstore_svals,svals);
HSTORE_POLLUTE(hstore_each,each);
static HEntry *
findkey(HStore *hs, char *key, int keylen)
/*
* We're often finding a sequence of keys in ascending order. The
* "lowbound" parameter is used to cache lower bounds of searches
* between calls, based on this assumption. Pass NULL for it for
* one-off or unordered searches.
*/
int
hstoreFindKey(HStore * hs, int *lowbound, char *key, int keylen)
{
HEntry *StopLow = ARRPTR(hs);
HEntry *StopHigh = StopLow + hs->size;
HEntry *StopMiddle;
int difference;
HEntry *entries = ARRPTR(hs);
int stopLow = lowbound ? *lowbound : 0;
int stopHigh = HS_COUNT(hs);
int stopMiddle;
char *base = STRPTR(hs);
while (StopLow < StopHigh)
while (stopLow < stopHigh)
{
StopMiddle = StopLow + (StopHigh - StopLow) / 2;
int difference;
stopMiddle = stopLow + (stopHigh - stopLow) / 2;
if (StopMiddle->keylen == keylen)
difference = strncmp(base + StopMiddle->pos, key, StopMiddle->keylen);
if (HS_KEYLEN(entries,stopMiddle) == keylen)
difference = strncmp(HS_KEY(entries,base,stopMiddle), key, keylen);
else
difference = (StopMiddle->keylen > keylen) ? 1 : -1;
difference = (HS_KEYLEN(entries,stopMiddle) > keylen) ? 1 : -1;
if (difference == 0)
return StopMiddle;
{
if (lowbound)
*lowbound = stopMiddle + 1;
return stopMiddle;
}
else if (difference < 0)
StopLow = StopMiddle + 1;
stopLow = stopMiddle + 1;
else
StopHigh = StopMiddle;
stopHigh = stopMiddle;
}
if (lowbound)
*lowbound = stopLow;
return -1;
}
Pairs *
hstoreArrayToPairs(ArrayType *a, int *npairs)
{
Datum *key_datums;
bool *key_nulls;
int key_count;
Pairs *key_pairs;
int bufsiz;
int i,j;
deconstruct_array(a,
TEXTOID, -1, false, 'i',
&key_datums, &key_nulls, &key_count);
if (key_count == 0)
{
*npairs = 0;
return NULL;
}
key_pairs = palloc(sizeof(Pairs) * key_count);
for (i = 0, j = 0; i < key_count; i++)
{
if (!key_nulls[i])
{
key_pairs[j].key = VARDATA(key_datums[i]);
key_pairs[j].keylen = VARSIZE(key_datums[i]) - VARHDRSZ;
key_pairs[j].val = NULL;
key_pairs[j].vallen = 0;
key_pairs[j].needfree = 0;
key_pairs[j].isnull = 1;
j++;
}
}
*npairs = hstoreUniquePairs(key_pairs, j, &bufsiz);
return key_pairs;
}
PG_FUNCTION_INFO_V1(fetchval);
Datum fetchval(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_fetchval);
Datum hstore_fetchval(PG_FUNCTION_ARGS);
Datum
fetchval(PG_FUNCTION_ARGS)
hstore_fetchval(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
text *key = PG_GETARG_TEXT_P(1);
HEntry *entry;
text *key = PG_GETARG_TEXT_PP(1);
HEntry *entries = ARRPTR(hs);
text *out;
int idx = hstoreFindKey(hs, NULL,
VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key));
if ((entry = findkey(hs, VARDATA(key), VARSIZE(key) - VARHDRSZ)) == NULL || entry->valisnull)
{
PG_FREE_IF_COPY(hs, 0);
PG_FREE_IF_COPY(key, 1);
if (idx < 0 || HS_VALISNULL(entries,idx))
PG_RETURN_NULL();
}
out = cstring_to_text_with_len(STRPTR(hs) + entry->pos + entry->keylen,
entry->vallen);
out = cstring_to_text_with_len(HS_VAL(entries,STRPTR(hs),idx),
HS_VALLEN(entries,idx));
PG_FREE_IF_COPY(hs, 0);
PG_FREE_IF_COPY(key, 1);
PG_RETURN_TEXT_P(out);
}
PG_FUNCTION_INFO_V1(exists);
Datum exists(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_exists);
Datum hstore_exists(PG_FUNCTION_ARGS);
Datum
exists(PG_FUNCTION_ARGS)
hstore_exists(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
text *key = PG_GETARG_TEXT_P(1);
HEntry *entry;
text *key = PG_GETARG_TEXT_PP(1);
int idx = hstoreFindKey(hs, NULL,
VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key));
PG_RETURN_BOOL(idx >= 0);
}
PG_FUNCTION_INFO_V1(hstore_exists_any);
Datum hstore_exists_any(PG_FUNCTION_ARGS);
Datum
hstore_exists_any(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1);
int nkeys;
Pairs *key_pairs = hstoreArrayToPairs(keys, &nkeys);
int i;
int lowbound = 0;
bool res = false;
/*
* we exploit the fact that the pairs list is already sorted into
* strictly increasing order to narrow the hstoreFindKey search;
* each search can start one entry past the previous "found"
* entry, or at the lower bound of the last search.
*/
entry = findkey(hs, VARDATA(key), VARSIZE(key) - VARHDRSZ);
for (i = 0; !res && i < nkeys; ++i)
{
int idx = hstoreFindKey(hs, &lowbound,
key_pairs[i].key, key_pairs[i].keylen);
PG_FREE_IF_COPY(hs, 0);
PG_FREE_IF_COPY(key, 1);
if (idx >= 0)
res = true;
}
PG_RETURN_BOOL(entry);
PG_RETURN_BOOL(res);
}
PG_FUNCTION_INFO_V1(defined);
Datum defined(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_exists_all);
Datum hstore_exists_all(PG_FUNCTION_ARGS);
Datum
defined(PG_FUNCTION_ARGS)
hstore_exists_all(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
text *key = PG_GETARG_TEXT_P(1);
HEntry *entry;
bool res;
ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1);
int nkeys;
Pairs *key_pairs = hstoreArrayToPairs(keys, &nkeys);
int i;
int lowbound = 0;
bool res = nkeys ? true : false;
/*
* we exploit the fact that the pairs list is already sorted into
* strictly increasing order to narrow the hstoreFindKey search;
* each search can start one entry past the previous "found"
* entry, or at the lower bound of the last search.
*/
entry = findkey(hs, VARDATA(key), VARSIZE(key) - VARHDRSZ);
for (i = 0; res && i < nkeys; ++i)
{
int idx = hstoreFindKey(hs, &lowbound,
key_pairs[i].key, key_pairs[i].keylen);
res = (entry && !entry->valisnull) ? true : false;
if (idx < 0)
res = false;
}
PG_FREE_IF_COPY(hs, 0);
PG_FREE_IF_COPY(key, 1);
PG_RETURN_BOOL(res);
}
PG_FUNCTION_INFO_V1(hstore_defined);
Datum hstore_defined(PG_FUNCTION_ARGS);
Datum
hstore_defined(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
text *key = PG_GETARG_TEXT_PP(1);
HEntry *entries = ARRPTR(hs);
int idx = hstoreFindKey(hs, NULL,
VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key));
bool res = (idx >= 0 && !HS_VALISNULL(entries,idx));
PG_RETURN_BOOL(res);
}
PG_FUNCTION_INFO_V1(delete);
Datum delete(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_delete);
Datum hstore_delete(PG_FUNCTION_ARGS);
Datum
delete(PG_FUNCTION_ARGS)
hstore_delete(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
text *key = PG_GETARG_TEXT_P(1);
text *key = PG_GETARG_TEXT_PP(1);
char *keyptr = VARDATA_ANY(key);
int keylen = VARSIZE_ANY_EXHDR(key);
HStore *out = palloc(VARSIZE(hs));
char *ptrs,
char *bufs,
*bufd,
*ptrd;
HEntry *es,
*ed;
int i;
int count = HS_COUNT(hs);
int outcount = 0;
SET_VARSIZE(out, VARSIZE(hs));
out->size = hs->size; /* temporary! */
HS_SETCOUNT(out, count); /* temporary! */
ptrs = STRPTR(hs);
bufs = STRPTR(hs);
es = ARRPTR(hs);
ptrd = STRPTR(out);
bufd = ptrd = STRPTR(out);
ed = ARRPTR(out);
while (es - ARRPTR(hs) < hs->size)
for (i = 0; i < count; ++i)
{
if (!(es->keylen == VARSIZE(key) - VARHDRSZ && strncmp(ptrs, VARDATA(key), es->keylen) == 0))
int len = HS_KEYLEN(es,i);
char *ptrs = HS_KEY(es,bufs,i);
if (!(len == keylen && strncmp(ptrs, keyptr, keylen) == 0))
{
memcpy(ed, es, sizeof(HEntry));
memcpy(ptrd, ptrs, es->keylen + ((es->valisnull) ? 0 : es->vallen));
ed->pos = ptrd - STRPTR(out);
ptrd += es->keylen + ((es->valisnull) ? 0 : es->vallen);
ed++;
int vallen = HS_VALLEN(es,i);
HS_COPYITEM(ed, bufd, ptrd, ptrs, len, vallen, HS_VALISNULL(es,i));
++outcount;
}
ptrs += es->keylen + ((es->valisnull) ? 0 : es->vallen);
es++;
}
if (ed - ARRPTR(out) != out->size)
HS_FINALIZE(out,outcount,bufd,ptrd);
PG_RETURN_POINTER(out);
}
PG_FUNCTION_INFO_V1(hstore_delete_array);
Datum hstore_delete_array(PG_FUNCTION_ARGS);
Datum
hstore_delete_array(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
HStore *out = palloc(VARSIZE(hs));
int hs_count = HS_COUNT(hs);
char *ps,
*bufd,
*pd;
HEntry *es,
*ed;
int i,j;
int outcount = 0;
ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1);
int nkeys;
Pairs *key_pairs = hstoreArrayToPairs(key_array, &nkeys);
SET_VARSIZE(out, VARSIZE(hs));
HS_SETCOUNT(out, hs_count); /* temporary! */
ps = STRPTR(hs);
es = ARRPTR(hs);
bufd = pd = STRPTR(out);
ed = ARRPTR(out);
if (nkeys == 0)
{
int buflen = ptrd - STRPTR(out);
/* return a copy of the input, unchanged */
memcpy(out, hs, VARSIZE(hs));
HS_FIXSIZE(out, hs_count);
HS_SETCOUNT(out, hs_count);
PG_RETURN_POINTER(out);
}
ptrd = STRPTR(out);
/*
* this is in effect a merge between hs and key_pairs, both of
* which are already sorted by (keylen,key); we take keys from
* hs only
*/
out->size = ed - ARRPTR(out);
for (i = j = 0; i < hs_count; )
{
int difference;
memmove(STRPTR(out), ptrd, buflen);
SET_VARSIZE(out, CALCDATASIZE(out->size, buflen));
if (j >= nkeys)
difference = -1;
else
{
int skeylen = HS_KEYLEN(es,i);
if (skeylen == key_pairs[j].keylen)
difference = strncmp(HS_KEY(es,ps,i),
key_pairs[j].key,
key_pairs[j].keylen);
else
difference = (skeylen > key_pairs[j].keylen) ? 1 : -1;
}
if (difference > 0)
++j;
else if (difference == 0)
++i, ++j;
else
{
HS_COPYITEM(ed, bufd, pd,
HS_KEY(es,ps,i), HS_KEYLEN(es,i),
HS_VALLEN(es,i), HS_VALISNULL(es,i));
++outcount;
++i;
}
}
PG_FREE_IF_COPY(hs, 0);
PG_FREE_IF_COPY(key, 1);
HS_FINALIZE(out,outcount,bufd,pd);
PG_RETURN_POINTER(out);
}
PG_FUNCTION_INFO_V1(hs_concat);
Datum hs_concat(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_delete_hstore);
Datum hstore_delete_hstore(PG_FUNCTION_ARGS);
Datum
hs_concat(PG_FUNCTION_ARGS)
hstore_delete_hstore(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
HStore *hs2 = PG_GETARG_HS(1);
HStore *out = palloc(VARSIZE(hs));
int hs_count = HS_COUNT(hs);
int hs2_count = HS_COUNT(hs2);
char *ps,
*ps2,
*bufd,
*pd;
HEntry *es,
*es2,
*ed;
int i,j;
int outcount = 0;
SET_VARSIZE(out, VARSIZE(hs));
HS_SETCOUNT(out, hs_count); /* temporary! */
ps = STRPTR(hs);
es = ARRPTR(hs);
ps2 = STRPTR(hs2);
es2 = ARRPTR(hs2);
bufd = pd = STRPTR(out);
ed = ARRPTR(out);
if (hs2_count == 0)
{
/* return a copy of the input, unchanged */
memcpy(out, hs, VARSIZE(hs));
HS_FIXSIZE(out, hs_count);
HS_SETCOUNT(out, hs_count);
PG_RETURN_POINTER(out);
}
/*
* this is in effect a merge between hs and hs2, both of
* which are already sorted by (keylen,key); we take keys from
* hs only; for equal keys, we take the value from hs unless the
* values are equal
*/
for (i = j = 0; i < hs_count; )
{
int difference;
if (j >= hs2_count)
difference = -1;
else
{
int skeylen = HS_KEYLEN(es,i);
int s2keylen = HS_KEYLEN(es2,j);
if (skeylen == s2keylen)
difference = strncmp(HS_KEY(es,ps,i),
HS_KEY(es2,ps2,j),
skeylen);
else
difference = (skeylen > s2keylen) ? 1 : -1;
}
if (difference > 0)
++j;
else if (difference == 0)
{
int svallen = HS_VALLEN(es,i);
int snullval = HS_VALISNULL(es,i);
if (snullval != HS_VALISNULL(es2,j)
|| (!snullval
&& (svallen != HS_VALLEN(es2,j)
|| strncmp(HS_VAL(es,ps,i), HS_VAL(es2,ps2,j), svallen) != 0)))
{
HS_COPYITEM(ed, bufd, pd,
HS_KEY(es,ps,i), HS_KEYLEN(es,i),
svallen, snullval);
++outcount;
}
++i, ++j;
}
else
{
HS_COPYITEM(ed, bufd, pd,
HS_KEY(es,ps,i), HS_KEYLEN(es,i),
HS_VALLEN(es,i), HS_VALISNULL(es,i));
++outcount;
++i;
}
}
HS_FINALIZE(out,outcount,bufd,pd);
PG_RETURN_POINTER(out);
}
PG_FUNCTION_INFO_V1(hstore_concat);
Datum hstore_concat(PG_FUNCTION_ARGS);
Datum
hstore_concat(PG_FUNCTION_ARGS)
{
HStore *s1 = PG_GETARG_HS(0);
HStore *s2 = PG_GETARG_HS(1);
HStore *out = palloc(VARSIZE(s1) + VARSIZE(s2));
char *ps1,
*ps2,
*bufd,
*pd;
HEntry *es1,
*es2,
*ed;
int s1idx;
int s2idx;
int s1count = HS_COUNT(s1);
int s2count = HS_COUNT(s2);
int outcount = 0;
SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2) - HSHRDSIZE);
HS_SETCOUNT(out, s1count + s2count);
if (s1count == 0)
{
/* return a copy of the input, unchanged */
memcpy(out, s2, VARSIZE(s2));
HS_FIXSIZE(out, s2count);
HS_SETCOUNT(out, s2count);
PG_RETURN_POINTER(out);
}
SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2));
out->size = s1->size + s2->size;
if (s2count == 0)
{
/* return a copy of the input, unchanged */
memcpy(out, s1, VARSIZE(s1));
HS_FIXSIZE(out, s1count);
HS_SETCOUNT(out, s1count);
PG_RETURN_POINTER(out);
}
ps1 = STRPTR(s1);
ps2 = STRPTR(s2);
pd = STRPTR(out);
bufd = pd = STRPTR(out);
es1 = ARRPTR(s1);
es2 = ARRPTR(s2);
ed = ARRPTR(out);
while (es1 - ARRPTR(s1) < s1->size && es2 - ARRPTR(s2) < s2->size)
/*
* this is in effect a merge between s1 and s2, both of which
* are already sorted by (keylen,key); we take s2 for equal keys
*/
for (s1idx = s2idx = 0; s1idx < s1count || s2idx < s2count; ++outcount)
{
int difference;
if (es1->keylen == es2->keylen)
difference = strncmp(ps1, ps2, es1->keylen);
if (s1idx >= s1count)
difference = 1;
else if (s2idx >= s2count)
difference = -1;
else
difference = (es1->keylen > es2->keylen) ? 1 : -1;
if (difference == 0)
{
memcpy(ed, es2, sizeof(HEntry));
memcpy(pd, ps2, es2->keylen + ((es2->valisnull) ? 0 : es2->vallen));
ed->pos = pd - STRPTR(out);
pd += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
ed++;
ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
es1++;
ps2 += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
es2++;
int s1keylen = HS_KEYLEN(es1,s1idx);
int s2keylen = HS_KEYLEN(es2,s2idx);
if (s1keylen == s2keylen)
difference = strncmp(HS_KEY(es1,ps1,s1idx),
HS_KEY(es2,ps2,s2idx),
s1keylen);
else
difference = (s1keylen > s2keylen) ? 1 : -1;
}
else if (difference > 0)
{
memcpy(ed, es2, sizeof(HEntry));
memcpy(pd, ps2, es2->keylen + ((es2->valisnull) ? 0 : es2->vallen));
ed->pos = pd - STRPTR(out);
pd += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
ed++;
ps2 += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
es2++;
if (difference >= 0)
{
HS_COPYITEM(ed, bufd, pd,
HS_KEY(es2,ps2,s2idx), HS_KEYLEN(es2,s2idx),
HS_VALLEN(es2,s2idx), HS_VALISNULL(es2,s2idx));
++s2idx;
if (difference == 0)
++s1idx;
}
else
{
memcpy(ed, es1, sizeof(HEntry));
memcpy(pd, ps1, es1->keylen + ((es1->valisnull) ? 0 : es1->vallen));
ed->pos = pd - STRPTR(out);
pd += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
ed++;
ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
es1++;
HS_COPYITEM(ed, bufd, pd,
HS_KEY(es1,ps1,s1idx), HS_KEYLEN(es1,s1idx),
HS_VALLEN(es1,s1idx), HS_VALISNULL(es1,s1idx));
++s1idx;
}
}
while (es1 - ARRPTR(s1) < s1->size)
{
memcpy(ed, es1, sizeof(HEntry));
memcpy(pd, ps1, es1->keylen + ((es1->valisnull) ? 0 : es1->vallen));
ed->pos = pd - STRPTR(out);
pd += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
ed++;
HS_FINALIZE(out,outcount,bufd,pd);
ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen);
es1++;
}
PG_RETURN_POINTER(out);
}
while (es2 - ARRPTR(s2) < s2->size)
{
memcpy(ed, es2, sizeof(HEntry));
memcpy(pd, ps2, es2->keylen + ((es2->valisnull) ? 0 : es2->vallen));
ed->pos = pd - STRPTR(out);
pd += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
ed++;
ps2 += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen);
es2++;
}
PG_FUNCTION_INFO_V1(hstore_slice_to_array);
Datum hstore_slice_to_array(PG_FUNCTION_ARGS);
Datum
hstore_slice_to_array(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
HEntry *entries = ARRPTR(hs);
char *ptr = STRPTR(hs);
ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1);
ArrayType *aout;
Datum *key_datums;
bool *key_nulls;
Datum *out_datums;
bool *out_nulls;
int key_count;
int i;
if (ed - ARRPTR(out) != out->size)
deconstruct_array(key_array,
TEXTOID, -1, false, 'i',
&key_datums, &key_nulls, &key_count);
if (key_count == 0)
{
int buflen = pd - STRPTR(out);
aout = construct_empty_array(TEXTOID);
PG_RETURN_POINTER(aout);
}
pd = STRPTR(out);
out_datums = palloc(sizeof(Datum) * key_count);
out_nulls = palloc(sizeof(bool) * key_count);
out->size = ed - ARRPTR(out);
for (i = 0; i < key_count; ++i)
{
text *key = (text*) DatumGetPointer(key_datums[i]);
int idx;
memmove(STRPTR(out), pd, buflen);
SET_VARSIZE(out, CALCDATASIZE(out->size, buflen));
if (key_nulls[i])
idx = -1;
else
idx = hstoreFindKey(hs, NULL, VARDATA(key), VARSIZE(key) - VARHDRSZ);
if (idx < 0 || HS_VALISNULL(entries,idx))
{
out_nulls[i] = true;
out_datums[i] = (Datum) 0;
}
else
{
out_datums[i] = PointerGetDatum(
cstring_to_text_with_len(HS_VAL(entries,ptr,idx),
HS_VALLEN(entries,idx)));
out_nulls[i] = false;
}
}
PG_FREE_IF_COPY(s1, 0);
PG_FREE_IF_COPY(s2, 1);
aout = construct_md_array(out_datums, out_nulls,
ARR_NDIM(key_array),
ARR_DIMS(key_array),
ARR_LBOUND(key_array),
TEXTOID, -1, false, 'i');
PG_RETURN_POINTER(out);
PG_RETURN_POINTER(aout);
}
PG_FUNCTION_INFO_V1(tconvert);
Datum tconvert(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_slice_to_hstore);
Datum hstore_slice_to_hstore(PG_FUNCTION_ARGS);
Datum
tconvert(PG_FUNCTION_ARGS)
hstore_slice_to_hstore(PG_FUNCTION_ARGS)
{
text *key;
text *val = NULL;
int len;
HStore *hs = PG_GETARG_HS(0);
HEntry *entries = ARRPTR(hs);
char *ptr = STRPTR(hs);
ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1);
HStore *out;
int nkeys;
Pairs *key_pairs = hstoreArrayToPairs(key_array, &nkeys);
Pairs *out_pairs;
int bufsiz;
int lastidx = 0;
int i;
int out_count = 0;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
if (nkeys == 0)
{
out = hstorePairs(NULL, 0, 0);
PG_RETURN_POINTER(out);
}
key = PG_GETARG_TEXT_P(0);
out_pairs = palloc(sizeof(Pairs) * nkeys);
bufsiz = 0;
if (PG_ARGISNULL(1))
len = CALCDATASIZE(1, VARSIZE(key));
else
/*
* we exploit the fact that the pairs list is already sorted into
* strictly increasing order to narrow the hstoreFindKey search;
* each search can start one entry past the previous "found"
* entry, or at the lower bound of the last search.
*/
for (i = 0; i < nkeys; ++i)
{
val = PG_GETARG_TEXT_P(1);
len = CALCDATASIZE(1, VARSIZE(key) + VARSIZE(val) - 2 * VARHDRSZ);
}
out = palloc(len);
SET_VARSIZE(out, len);
out->size = 1;
int idx = hstoreFindKey(hs, &lastidx,
key_pairs[i].key, key_pairs[i].keylen);
ARRPTR(out)->keylen = hstoreCheckKeyLen(VARSIZE(key) - VARHDRSZ);
if (PG_ARGISNULL(1))
if (idx >= 0)
{
ARRPTR(out)->vallen = 0;
ARRPTR(out)->valisnull = true;
out_pairs[out_count].key = key_pairs[i].key;
bufsiz += (out_pairs[out_count].keylen = key_pairs[i].keylen);
out_pairs[out_count].val = HS_VAL(entries,ptr,idx);
bufsiz += (out_pairs[out_count].vallen = HS_VALLEN(entries,idx));
out_pairs[out_count].isnull = HS_VALISNULL(entries,idx);
out_pairs[out_count].needfree = false;
++out_count;
}
else
{
ARRPTR(out)->vallen = hstoreCheckValLen(VARSIZE(val) - VARHDRSZ);
ARRPTR(out)->valisnull = false;
}
ARRPTR(out)->pos = 0;
memcpy(STRPTR(out), VARDATA(key), ARRPTR(out)->keylen);
if (!PG_ARGISNULL(1))
{
memcpy(STRPTR(out) + ARRPTR(out)->keylen, VARDATA(val), ARRPTR(out)->vallen);
PG_FREE_IF_COPY(val, 1);
}
/*
* we don't use uniquePairs here because we know that the
* pairs list is already sorted and uniq'ed.
*/
PG_FREE_IF_COPY(key, 0);
out = hstorePairs(out_pairs, out_count, bufsiz);
PG_RETURN_POINTER(out);
}
PG_FUNCTION_INFO_V1(akeys);
Datum akeys(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_akeys);
Datum hstore_akeys(PG_FUNCTION_ARGS);
Datum
akeys(PG_FUNCTION_ARGS)
hstore_akeys(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
Datum *d;
ArrayType *a;
HEntry *ptr = ARRPTR(hs);
HEntry *entries = ARRPTR(hs);
char *base = STRPTR(hs);
int count = HS_COUNT(hs);
int i;
d = (Datum *) palloc(sizeof(Datum) * (hs->size + 1));
while (ptr - ARRPTR(hs) < hs->size)
if (count == 0)
{
text *item;
item = cstring_to_text_with_len(base + ptr->pos, ptr->keylen);
d[ptr - ARRPTR(hs)] = PointerGetDatum(item);
ptr++;
a = construct_empty_array(TEXTOID);
PG_RETURN_POINTER(a);
}
a = construct_array(d,
hs->size,
TEXTOID,
-1,
false,
'i');
d = (Datum *) palloc(sizeof(Datum) * count);
ptr = ARRPTR(hs);
while (ptr - ARRPTR(hs) < hs->size)
for (i = 0; i < count; ++i)
{
pfree(DatumGetPointer(d[ptr - ARRPTR(hs)]));
ptr++;
text *item = cstring_to_text_with_len(HS_KEY(entries,base,i),
HS_KEYLEN(entries,i));
d[i] = PointerGetDatum(item);
}
pfree(d);
PG_FREE_IF_COPY(hs, 0);
a = construct_array(d, count,
TEXTOID, -1, false, 'i');
PG_RETURN_POINTER(a);
}
PG_FUNCTION_INFO_V1(avals);
Datum avals(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_avals);
Datum hstore_avals(PG_FUNCTION_ARGS);
Datum
avals(PG_FUNCTION_ARGS)
hstore_avals(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
Datum *d;
bool *nulls;
ArrayType *a;
HEntry *ptr = ARRPTR(hs);
HEntry *entries = ARRPTR(hs);
char *base = STRPTR(hs);
int count = HS_COUNT(hs);
int lb = 1;
int i;
d = (Datum *) palloc(sizeof(Datum) * (hs->size + 1));
while (ptr - ARRPTR(hs) < hs->size)
if (count == 0)
{
text *item;
item = cstring_to_text_with_len(base + ptr->pos + ptr->keylen,
(ptr->valisnull) ? 0 : ptr->vallen);
d[ptr - ARRPTR(hs)] = PointerGetDatum(item);
ptr++;
a = construct_empty_array(TEXTOID);
PG_RETURN_POINTER(a);
}
a = construct_array(d,
hs->size,
TEXTOID,
-1,
false,
'i');
d = (Datum *) palloc(sizeof(Datum) * count);
nulls = (bool *) palloc(sizeof(bool) * count);
ptr = ARRPTR(hs);
while (ptr - ARRPTR(hs) < hs->size)
for (i = 0; i < count; ++i)
{
if (HS_VALISNULL(entries,i))
{
pfree(DatumGetPointer(d[ptr - ARRPTR(hs)]));
ptr++;
d[i] = (Datum) 0;
nulls[i] = true;
}
else
{
text *item = cstring_to_text_with_len(HS_VAL(entries,base,i),
HS_VALLEN(entries,i));
d[i] = PointerGetDatum(item);
nulls[i] = false;
}
}
pfree(d);
PG_FREE_IF_COPY(hs, 0);
a = construct_md_array(d, nulls, 1, &count, &lb,
TEXTOID, -1, false, 'i');
PG_RETURN_POINTER(a);
}
typedef struct
static ArrayType *
hstore_to_array_internal(HStore *hs, int ndims)
{
HStore *hs;
HEntry *entries = ARRPTR(hs);
char *base = STRPTR(hs);
int count = HS_COUNT(hs);
int out_size[2] = { 0, 2 };
int lb[2] = { 1, 1 };
Datum *out_datums;
bool *out_nulls;
int i;
} AKStore;
Assert(ndims < 3);
if (count == 0 || ndims == 0)
return construct_empty_array(TEXTOID);
out_size[0] = count * 2 / ndims;
out_datums = palloc(sizeof(Datum) * count * 2);
out_nulls = palloc(sizeof(bool) * count * 2);
for (i = 0; i < count; ++i)
{
text *key = cstring_to_text_with_len(HS_KEY(entries,base,i),
HS_KEYLEN(entries,i));
out_datums[i*2] = PointerGetDatum(key);
out_nulls[i*2] = false;
if (HS_VALISNULL(entries,i))
{
out_datums[i*2+1] = (Datum) 0;
out_nulls[i*2+1] = true;
}
else
{
text *item = cstring_to_text_with_len(HS_VAL(entries,base,i),
HS_VALLEN(entries,i));
out_datums[i*2+1] = PointerGetDatum(item);
out_nulls[i*2+1] = false;
}
}
return construct_md_array(out_datums, out_nulls,
ndims, out_size, lb,
TEXTOID, -1, false, 'i');
}
PG_FUNCTION_INFO_V1(hstore_to_array);
Datum hstore_to_array(PG_FUNCTION_ARGS);
Datum
hstore_to_array(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
ArrayType *out = hstore_to_array_internal(hs, 1);
PG_RETURN_POINTER(out);
}
PG_FUNCTION_INFO_V1(hstore_to_matrix);
Datum hstore_to_matrix(PG_FUNCTION_ARGS);
Datum
hstore_to_matrix(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
ArrayType *out = hstore_to_array_internal(hs, 2);
PG_RETURN_POINTER(out);
}
/*
* Common initialization function for the various set-returning
* funcs. fcinfo is only passed if the function is to return a
* composite; it will be used to look up the return tupledesc.
* we stash a copy of the hstore in the multi-call context in
* case it was originally toasted. (At least I assume that's why;
* there was no explanatory comment in the original code. --AG)
*/
static void
setup_firstcall(FuncCallContext *funcctx, HStore *hs)
setup_firstcall(FuncCallContext *funcctx, HStore * hs,
FunctionCallInfoData *fcinfo)
{
MemoryContext oldcontext;
AKStore *st;
HStore *st;
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
st = (AKStore *) palloc(sizeof(AKStore));
st->i = 0;
st->hs = (HStore *) palloc(VARSIZE(hs));
memcpy(st->hs, hs, VARSIZE(hs));
st = (HStore *) palloc(VARSIZE(hs));
memcpy(st, hs, VARSIZE(hs));
funcctx->user_fctx = (void *) st;
if (fcinfo)
{
TupleDesc tupdesc;
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
}
MemoryContextSwitchTo(oldcontext);
}
PG_FUNCTION_INFO_V1(skeys);
Datum skeys(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_skeys);
Datum hstore_skeys(PG_FUNCTION_ARGS);
Datum
skeys(PG_FUNCTION_ARGS)
hstore_skeys(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
AKStore *st;
HStore *hs;
int i;
if (SRF_IS_FIRSTCALL())
{
HStore *hs = PG_GETARG_HS(0);
hs = PG_GETARG_HS(0);
funcctx = SRF_FIRSTCALL_INIT();
setup_firstcall(funcctx, hs);
PG_FREE_IF_COPY(hs, 0);
setup_firstcall(funcctx, hs, NULL);
}
funcctx = SRF_PERCALL_SETUP();
st = (AKStore *) funcctx->user_fctx;
hs = (HStore *) funcctx->user_fctx;
i = funcctx->call_cntr;
if (st->i < st->hs->size)
if (i < HS_COUNT(hs))
{
HEntry *ptr = &(ARRPTR(st->hs)[st->i]);
HEntry *entries = ARRPTR(hs);
text *item;
item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos,
ptr->keylen);
st->i++;
item = cstring_to_text_with_len(HS_KEY(entries,STRPTR(hs),i),
HS_KEYLEN(entries,i));
SRF_RETURN_NEXT(funcctx, PointerGetDatum(item));
}
pfree(st->hs);
pfree(st);
SRF_RETURN_DONE(funcctx);
}
PG_FUNCTION_INFO_V1(svals);
Datum svals(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_svals);
Datum hstore_svals(PG_FUNCTION_ARGS);
Datum
svals(PG_FUNCTION_ARGS)
hstore_svals(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
AKStore *st;
HStore *hs;
int i;
if (SRF_IS_FIRSTCALL())
{
HStore *hs = PG_GETARG_HS(0);
hs = PG_GETARG_HS(0);
funcctx = SRF_FIRSTCALL_INIT();
setup_firstcall(funcctx, hs);
PG_FREE_IF_COPY(hs, 0);
setup_firstcall(funcctx, hs, NULL);
}
funcctx = SRF_PERCALL_SETUP();
st = (AKStore *) funcctx->user_fctx;
hs = (HStore *) funcctx->user_fctx;
i = funcctx->call_cntr;
if (st->i < st->hs->size)
if (i < HS_COUNT(hs))
{
HEntry *ptr = &(ARRPTR(st->hs)[st->i]);
HEntry *entries = ARRPTR(hs);
if (ptr->valisnull)
if (HS_VALISNULL(entries,i))
{
ReturnSetInfo *rsi;
st->i++;
/* ugly ugly ugly. why no macro for this? */
(funcctx)->call_cntr++;
rsi = (ReturnSetInfo *) fcinfo->resultinfo;
rsi->isDone = ExprMultipleResult;
......@@ -502,144 +939,306 @@ svals(PG_FUNCTION_ARGS)
{
text *item;
item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos + ptr->keylen,
ptr->vallen);
st->i++;
item = cstring_to_text_with_len(HS_VAL(entries,STRPTR(hs),i),
HS_VALLEN(entries,i));
SRF_RETURN_NEXT(funcctx, PointerGetDatum(item));
}
}
pfree(st->hs);
pfree(st);
SRF_RETURN_DONE(funcctx);
}
PG_FUNCTION_INFO_V1(hs_contains);
Datum hs_contains(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_contains);
Datum hstore_contains(PG_FUNCTION_ARGS);
Datum
hs_contains(PG_FUNCTION_ARGS)
hstore_contains(PG_FUNCTION_ARGS)
{
HStore *val = PG_GETARG_HS(0);
HStore *tmpl = PG_GETARG_HS(1);
bool res = true;
HEntry *te = ARRPTR(tmpl);
char *vv = STRPTR(val);
char *tv = STRPTR(tmpl);
char *tstr = STRPTR(tmpl);
HEntry *ve = ARRPTR(val);
char *vstr = STRPTR(val);
int tcount = HS_COUNT(tmpl);
int lastidx = 0;
int i;
while (res && te - ARRPTR(tmpl) < tmpl->size)
{
HEntry *entry = findkey(val, tv + te->pos, te->keylen);
/*
* we exploit the fact that keys in "tmpl" are in strictly
* increasing order to narrow the hstoreFindKey search; each search
* can start one entry past the previous "found" entry, or at the
* lower bound of the search
*/
if (entry)
for (i = 0; res && i < tcount; ++i)
{
if (te->valisnull || entry->valisnull)
int idx = hstoreFindKey(val, &lastidx,
HS_KEY(te,tstr,i), HS_KEYLEN(te,i));
if (idx >= 0)
{
if (!(te->valisnull && entry->valisnull))
res = false;
}
else if (te->vallen != entry->vallen ||
strncmp(vv + entry->pos + entry->keylen,
tv + te->pos + te->keylen,
te->vallen))
bool nullval = HS_VALISNULL(te,i);
int vallen = HS_VALLEN(te,i);
if (nullval != HS_VALISNULL(ve,idx)
|| (!nullval
&& (vallen != HS_VALLEN(ve,idx)
|| strncmp(HS_VAL(te,tstr,i), HS_VAL(ve,vstr,idx), vallen))))
res = false;
}
else
res = false;
te++;
}
PG_FREE_IF_COPY(val, 0);
PG_FREE_IF_COPY(tmpl, 1);
PG_RETURN_BOOL(res);
}
PG_FUNCTION_INFO_V1(hs_contained);
Datum hs_contained(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_contained);
Datum hstore_contained(PG_FUNCTION_ARGS);
Datum
hs_contained(PG_FUNCTION_ARGS)
hstore_contained(PG_FUNCTION_ARGS)
{
PG_RETURN_DATUM(DirectFunctionCall2(
hs_contains,
PG_RETURN_DATUM(DirectFunctionCall2(hstore_contains,
PG_GETARG_DATUM(1),
PG_GETARG_DATUM(0)
));
}
PG_FUNCTION_INFO_V1(each);
Datum each(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hstore_each);
Datum hstore_each(PG_FUNCTION_ARGS);
Datum
each(PG_FUNCTION_ARGS)
hstore_each(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
AKStore *st;
HStore *hs;
int i;
if (SRF_IS_FIRSTCALL())
{
TupleDesc tupdesc;
MemoryContext oldcontext;
HStore *hs = PG_GETARG_HS(0);
hs = PG_GETARG_HS(0);
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
st = (AKStore *) palloc(sizeof(AKStore));
st->i = 0;
st->hs = (HStore *) palloc(VARSIZE(hs));
memcpy(st->hs, hs, VARSIZE(hs));
funcctx->user_fctx = (void *) st;
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
MemoryContextSwitchTo(oldcontext);
PG_FREE_IF_COPY(hs, 0);
setup_firstcall(funcctx, hs, fcinfo);
}
funcctx = SRF_PERCALL_SETUP();
st = (AKStore *) funcctx->user_fctx;
hs = (HStore *) funcctx->user_fctx;
i = funcctx->call_cntr;
if (st->i < st->hs->size)
if (i < HS_COUNT(hs))
{
HEntry *ptr = &(ARRPTR(st->hs)[st->i]);
HEntry *entries = ARRPTR(hs);
char *ptr = STRPTR(hs);
Datum res,
dvalues[2];
bool nulls[2] = {false, false};
text *item;
HeapTuple tuple;
item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos, ptr->keylen);
item = cstring_to_text_with_len(HS_KEY(entries,ptr,i),
HS_KEYLEN(entries,i));
dvalues[0] = PointerGetDatum(item);
if (ptr->valisnull)
if (HS_VALISNULL(entries,i))
{
dvalues[1] = (Datum) 0;
nulls[1] = true;
}
else
{
item = cstring_to_text_with_len(STRPTR(st->hs) + ptr->pos + ptr->keylen,
ptr->vallen);
item = cstring_to_text_with_len(HS_VAL(entries,ptr,i),
HS_VALLEN(entries,i));
dvalues[1] = PointerGetDatum(item);
}
st->i++;
tuple = heap_form_tuple(funcctx->attinmeta->tupdesc, dvalues, nulls);
tuple = heap_form_tuple(funcctx->tuple_desc, dvalues, nulls);
res = HeapTupleGetDatum(tuple);
pfree(DatumGetPointer(dvalues[0]));
if (!nulls[1])
pfree(DatumGetPointer(dvalues[1]));
SRF_RETURN_NEXT(funcctx, PointerGetDatum(res));
}
pfree(st->hs);
pfree(st);
SRF_RETURN_DONE(funcctx);
}
/*
* btree sort order for hstores isn't intended to be useful; we really only
* care about equality versus non-equality. we compare the entire string
* buffer first, then the entry pos array.
*/
PG_FUNCTION_INFO_V1(hstore_cmp);
Datum hstore_cmp(PG_FUNCTION_ARGS);
Datum
hstore_cmp(PG_FUNCTION_ARGS)
{
HStore *hs1 = PG_GETARG_HS(0);
HStore *hs2 = PG_GETARG_HS(1);
int hcount1 = HS_COUNT(hs1);
int hcount2 = HS_COUNT(hs2);
int res = 0;
if (hcount1 == 0 || hcount2 == 0)
{
/*
* if either operand is empty, and the other is nonempty, the
* nonempty one is larger. If both are empty they are equal.
*/
if (hcount1 > 0)
res = 1;
else if (hcount2 > 0)
res = -1;
}
else
{
/* here we know both operands are nonempty */
char *str1 = STRPTR(hs1);
char *str2 = STRPTR(hs2);
HEntry *ent1 = ARRPTR(hs1);
HEntry *ent2 = ARRPTR(hs2);
size_t len1 = HSE_ENDPOS(ent1[2*hcount1 - 1]);
size_t len2 = HSE_ENDPOS(ent2[2*hcount2 - 1]);
res = memcmp(str1, str2, Min(len1,len2));
if (res == 0)
{
if (len1 > len2)
res = 1;
else if (len1 < len2)
res = -1;
else if (hcount1 > hcount2)
res = 1;
else if (hcount2 > hcount1)
res = -1;
else
{
int count = hcount1 * 2;
int i;
for (i = 0; i < count; ++i)
if (HSE_ENDPOS(ent1[i]) != HSE_ENDPOS(ent2[i]) ||
HSE_ISNULL(ent1[i]) != HSE_ISNULL(ent2[i]))
break;
if (i < count)
{
if (HSE_ENDPOS(ent1[i]) < HSE_ENDPOS(ent2[i]))
res = -1;
else if (HSE_ENDPOS(ent1[i]) > HSE_ENDPOS(ent2[i]))
res = 1;
else if (HSE_ISNULL(ent1[i]))
res = 1;
else if (HSE_ISNULL(ent2[i]))
res = -1;
}
}
}
else
{
res = (res > 0) ? 1 : -1;
}
}
/*
* this is a btree support function; this is one of the few
* places where memory needs to be explicitly freed.
*/
PG_FREE_IF_COPY(hs1,0);
PG_FREE_IF_COPY(hs2,1);
PG_RETURN_INT32(res);
}
PG_FUNCTION_INFO_V1(hstore_eq);
Datum hstore_eq(PG_FUNCTION_ARGS);
Datum
hstore_eq(PG_FUNCTION_ARGS)
{
int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1)));
PG_RETURN_BOOL(res == 0);
}
PG_FUNCTION_INFO_V1(hstore_ne);
Datum hstore_ne(PG_FUNCTION_ARGS);
Datum
hstore_ne(PG_FUNCTION_ARGS)
{
int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1)));
PG_RETURN_BOOL(res != 0);
}
PG_FUNCTION_INFO_V1(hstore_gt);
Datum hstore_gt(PG_FUNCTION_ARGS);
Datum
hstore_gt(PG_FUNCTION_ARGS)
{
int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1)));
PG_RETURN_BOOL(res > 0);
}
PG_FUNCTION_INFO_V1(hstore_ge);
Datum hstore_ge(PG_FUNCTION_ARGS);
Datum
hstore_ge(PG_FUNCTION_ARGS)
{
int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1)));
PG_RETURN_BOOL(res >= 0);
}
PG_FUNCTION_INFO_V1(hstore_lt);
Datum hstore_lt(PG_FUNCTION_ARGS);
Datum
hstore_lt(PG_FUNCTION_ARGS)
{
int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1)));
PG_RETURN_BOOL(res < 0);
}
PG_FUNCTION_INFO_V1(hstore_le);
Datum hstore_le(PG_FUNCTION_ARGS);
Datum
hstore_le(PG_FUNCTION_ARGS)
{
int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp,
PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1)));
PG_RETURN_BOOL(res <= 0);
}
PG_FUNCTION_INFO_V1(hstore_hash);
Datum hstore_hash(PG_FUNCTION_ARGS);
Datum
hstore_hash(PG_FUNCTION_ARGS)
{
HStore *hs = PG_GETARG_HS(0);
Datum hval = hash_any((unsigned char *)VARDATA(hs),
VARSIZE(hs) - VARHDRSZ);
/*
* this is the only place in the code that cares whether the
* overall varlena size exactly matches the true data size;
* this assertion should be maintained by all the other code,
* but we make it explicit here.
*/
Assert(VARSIZE(hs) ==
CALCDATASIZE(HS_COUNT(hs),
HSE_ENDPOS(ARRPTR(hs)[2*HS_COUNT(hs) - 1])));
PG_FREE_IF_COPY(hs,0);
PG_RETURN_DATUM(hval);
}
......@@ -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';
/* $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, 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, 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;
<!-- $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>
Keep in mind that the above format, when used to input hstore values,
applies <emphasis>before</> any required quoting or escaping. If you
are passing an hstore literal via a parameter, then no additional
processing is needed. If you are passing it as a quoted literal
constant, then any single-quote characters and (depending on the
setting of <varname>standard_conforming_strings</>) backslash characters
need to be escaped correctly. See <xref linkend="sql-syntax-strings">.
</para>
</note>
<para>
Currently, double quotes are always used to surround key and value
Double quotes are always used to surround key and value
strings on output, even when this is not strictly necessary.
</para>
......@@ -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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment