On 04/02/11 17:19, Hitoshi Harada wrote: > 2011/1/28 Jan Urbański <wulc...@wulczer.org>: >> On 23/12/10 15:15, Jan Urbański wrote: >>> Here's a patch implementing custom parsers for data types mentioned in >>> http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's >>> an incremental patch on top of the plpython-refactor patch sent eariler. >> >> Updated to master. > > I reviewed this for some time today.
Thank you. > The patch applies with hunks, compiles and tests are passed, though it > looks like not having additional test along with it. I added a simple test. I had to add an expected file for the case when hstore is compiled without PL/Python integration. > - in hstore_plpython.c, > PLyParsers parsers = { > .in = hstore_to_dict, > .out = dict_to_hstore > }; > I'm not sure if this coding style is used anywhere in the core. > Isn't this the C99 style? Ooops, you're right. Fixed. > - You need define custom variable class to use this feature. > plpython.hstore = 'public.hstore'. I wonder why it's called > plpython[u].hstore = 'public.hstore' (with 'u') because the language > is called "plpythonu". I think plpython.hstore was what showed up in discussion... I'd be fine with calling the variable plpythonu.hstore, if that's the consensus. > - typo in plpython.h, > Types for parsres functions that ... Fixed. > - I tried the sample you mention upthread, > regression=# select pick_one('a=>3, b=>4', 'b'); > ERROR: TypeError: string indices must be integers > CONTEXT: PL/Python function "pick_one" > > My python is 2.4.3 again. Hm, this means that the hstore has not been transformed into a Python dict, but into a string, which is what happens if you *don't* have plpython hstore integration enabled. I think that was because of an issue with my changes to hstore's Makefile, that made it compile without Python support, even if the sources were configured with --with-python. There's also a gotcha: if you set plpython.hstore to 'public.hstore', you will have to DROP (or CREATE OR REPLACE again) all functions that accept or return hstores, because their I/O routines are already cached. Not sure how big of a problem that is (or how to fix it in an elegant manner). Making the parameter PGC_POSTMASTER is an easy solution... but not very nice. > That's it for now. It is an exciting feature and plpython will be the > first language to think of when you're building "object database" if > this feature is in. The design here will affect following pl/perl and > other so it is important enough to discuss. Yes, I ended up writing this patch as a PoC of how you can integrate procedural languages with arbitrary addon modules, so it would be good to have a discussion about the general mechanisms. I'm aware that this discussion, and subsequently this patch, might be punted to 9.2 (although that would be a shame). Cheers, Jan
diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile index 1d533fd..fd85052 100644 *** a/contrib/hstore/Makefile --- b/contrib/hstore/Makefile *************** *** 1,8 **** # contrib/hstore/Makefile MODULE_big = hstore 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 --- 1,9 ---- # contrib/hstore/Makefile MODULE_big = hstore + OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ ! hstore_plpython.o crc32.o DATA_built = hstore.sql DATA = uninstall_hstore.sql *************** top_builddir = ../.. *** 18,20 **** --- 19,28 ---- include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif + + ifeq ($(with_python), yes) + override CFLAGS += -I$(srcdir) -I$(top_builddir)/src/pl/plpython \ + $(python_includespec) -DHSTORE_PLPYTHON_SUPPORT + SHLIB_LINK = $(python_libspec) $(python_additional_libs) \ + $(filter -lintl,$(LIBS)) $(CPPFLAGS) + endif diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out index 354fff2..049fdd5 100644 *** a/contrib/hstore/expected/hstore.out --- b/contrib/hstore/expected/hstore.out *************** select count(*) from testhstore where h *** 1461,1463 **** --- 1461,1502 ---- 1 (1 row) + -- plpython integration + set plpython.hstore = 'public.hstore'; + create language plpythonu; + create or replace function select_one(h hstore, idx text) returns text as $$ + try: + return h.get(idx) + except AttributeError: + # h has not been transformed into a dict and is a string + return None + $$ language plpythonu; + select coalesce(select_one(h, 'node'), '<null>') as node from testhstore where select_one(h, 'line')::integer < 10; + node + -------- + AA + CBB + <null> + <null> + CBA + CBC + <null> + <null> + <null> + (9 rows) + + reset plpython.hstore; + create or replace function select_one(h hstore, idx text) returns text as $$ + try: + return h.get(idx) + except AttributeError: + # h has not been transformed into a dict and is a string + return None + $$ language plpythonu; + select coalesce(select_one(h, 'node'), '<null>') as node from testhstore where select_one(h, 'line')::integer < 10; + node + ------ + (0 rows) + + drop function select_one(hstore, text); + drop language plpythonu; diff --git a/contrib/hstore/expected/hstore_0.out b/contrib/hstore/expected/hstore_0.out index ...af06337 . *** a/contrib/hstore/expected/hstore_0.out --- b/contrib/hstore/expected/hstore_0.out *************** *** 0 **** --- 1,1495 ---- + -- + -- first, define the datatype. Turn off echoing so that expected file + -- does not depend on contents of hstore.sql. + -- + SET client_min_messages = warning; + \set ECHO none + psql:hstore.sql:228: WARNING: => is deprecated as an operator name + DETAIL: This name may be disallowed altogether in future versions of PostgreSQL. + RESET client_min_messages; + set escape_string_warning=off; + --hstore; + select ''::hstore; + hstore + -------- + + (1 row) + + select 'a=>b'::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select ' a=>b'::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select 'a =>b'::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select 'a=>b '::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select 'a=> b'::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select '"a"=>"b"'::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select ' "a"=>"b"'::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select '"a" =>"b"'::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select '"a"=>"b" '::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select '"a"=> "b"'::hstore; + hstore + ---------- + "a"=>"b" + (1 row) + + select 'aa=>bb'::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select ' aa=>bb'::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select 'aa =>bb'::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select 'aa=>bb '::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select 'aa=> bb'::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select '"aa"=>"bb"'::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select ' "aa"=>"bb"'::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select '"aa" =>"bb"'::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select '"aa"=>"bb" '::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select '"aa"=> "bb"'::hstore; + hstore + ------------ + "aa"=>"bb" + (1 row) + + select 'aa=>bb, cc=>dd'::hstore; + hstore + ------------------------ + "aa"=>"bb", "cc"=>"dd" + (1 row) + + select 'aa=>bb , cc=>dd'::hstore; + hstore + ------------------------ + "aa"=>"bb", "cc"=>"dd" + (1 row) + + select 'aa=>bb ,cc=>dd'::hstore; + hstore + ------------------------ + "aa"=>"bb", "cc"=>"dd" + (1 row) + + select 'aa=>bb, "cc"=>dd'::hstore; + hstore + ------------------------ + "aa"=>"bb", "cc"=>"dd" + (1 row) + + select 'aa=>bb , "cc"=>dd'::hstore; + hstore + ------------------------ + "aa"=>"bb", "cc"=>"dd" + (1 row) + + select 'aa=>bb ,"cc"=>dd'::hstore; + hstore + ------------------------ + "aa"=>"bb", "cc"=>"dd" + (1 row) + + select 'aa=>"bb", cc=>dd'::hstore; + hstore + ------------------------ + "aa"=>"bb", "cc"=>"dd" + (1 row) + + select 'aa=>"bb" , cc=>dd'::hstore; + hstore + ------------------------ + "aa"=>"bb", "cc"=>"dd" + (1 row) + + select 'aa=>"bb" ,cc=>dd'::hstore; + hstore + ------------------------ + "aa"=>"bb", "cc"=>"dd" + (1 row) + + select 'aa=>null'::hstore; + hstore + ------------ + "aa"=>NULL + (1 row) + + select 'aa=>NuLl'::hstore; + hstore + ------------ + "aa"=>NULL + (1 row) + + select 'aa=>"NuLl"'::hstore; + hstore + -------------- + "aa"=>"NuLl" + (1 row) + + select e'\\=a=>q=w'::hstore; + hstore + ------------- + "=a"=>"q=w" + (1 row) + + select e'"=a"=>q\\=w'::hstore; + hstore + ------------- + "=a"=>"q=w" + (1 row) + + select e'"\\"a"=>q>w'::hstore; + hstore + -------------- + "\"a"=>"q>w" + (1 row) + + select e'\\"a=>q"w'::hstore; + hstore + --------------- + "\"a"=>"q\"w" + (1 row) + + select ''::hstore; + hstore + -------- + + (1 row) + + select ' '::hstore; + hstore + -------- + + (1 row) + + -- -> operator + select 'aa=>b, c=>d , b=>16'::hstore->'c'; + ?column? + ---------- + d + (1 row) + + select 'aa=>b, c=>d , b=>16'::hstore->'b'; + ?column? + ---------- + 16 + (1 row) + + select 'aa=>b, c=>d , b=>16'::hstore->'aa'; + ?column? + ---------- + b + (1 row) + + select ('aa=>b, c=>d , b=>16'::hstore->'gg') is null; + ?column? + ---------- + t + (1 row) + + select ('aa=>NULL, c=>d , b=>16'::hstore->'aa') is null; + ?column? + ---------- + t + (1 row) + + select ('aa=>"NULL", c=>d , b=>16'::hstore->'aa') is null; + ?column? + ---------- + 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 + ------- + t + (1 row) + + select exist('a=>NULL, b=>qq', 'b'); + exist + ------- + t + (1 row) + + select exist('a=>NULL, b=>qq', 'c'); + exist + ------- + f + (1 row) + + select exist('a=>"NULL", b=>qq', 'a'); + exist + ------- + t + (1 row) + + select defined('a=>NULL, b=>qq', 'a'); + defined + --------- + f + (1 row) + + select defined('a=>NULL, b=>qq', 'b'); + defined + --------- + t + (1 row) + + select defined('a=>NULL, b=>qq', 'c'); + defined + --------- + f + (1 row) + + select defined('a=>"NULL", b=>qq', 'a'); + defined + --------- + 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? + ---------- + t + (1 row) + + -- delete + select delete('a=>1 , b=>2, c=>3'::hstore, 'a'); + delete + -------------------- + "b"=>"2", "c"=>"3" + (1 row) + + select delete('a=>null , b=>2, c=>3'::hstore, 'a'); + delete + -------------------- + "b"=>"2", "c"=>"3" + (1 row) + + select delete('a=>1 , b=>2, c=>3'::hstore, 'b'); + delete + -------------------- + "a"=>"1", "c"=>"3" + (1 row) + + select delete('a=>1 , b=>2, c=>3'::hstore, 'c'); + delete + -------------------- + "a"=>"1", "b"=>"2" + (1 row) + + select delete('a=>1 , b=>2, c=>3'::hstore, 'd'); + delete + ------------------------------ + "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? + ------------------------------------------- + "b"=>"g", "aa"=>"1", "cq"=>"l", "fg"=>"f" + (1 row) + + select 'aa=>1 , b=>2, cq=>3'::hstore || 'aq=>l'; + ?column? + ------------------------------------------- + "b"=>"2", "aa"=>"1", "aq"=>"l", "cq"=>"3" + (1 row) + + select 'aa=>1 , b=>2, cq=>3'::hstore || 'aa=>l'; + ?column? + -------------------------------- + "b"=>"2", "aa"=>"l", "cq"=>"3" + (1 row) + + select 'aa=>1 , b=>2, cq=>3'::hstore || ''; + ?column? + -------------------------------- + "b"=>"2", "aa"=>"1", "cq"=>"3" + (1 row) + + select ''::hstore || 'cq=>l, b=>g, fg=>f'; + ?column? + -------------------------------- + "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? + --------------------------------- + "a"=>"g", "b"=>"c", "asd"=>"gf" + (1 row) + + select 'a=>g, b=>c'::hstore || ( 'b'=>'gf' ); + ?column? + --------------------- + "a"=>"g", "b"=>"gf" + (1 row) + + select 'a=>g, b=>c'::hstore || ( 'b'=>'NULL' ); + ?column? + ----------------------- + "a"=>"g", "b"=>"NULL" + (1 row) + + select 'a=>g, b=>c'::hstore || ( 'b'=>NULL ); + ?column? + --------------------- + "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) + + -- slice() + select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['g','h','i']); + slice + ------- + + (1 row) + + select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b']); + slice + -------------------- + "b"=>"2", "c"=>"3" + (1 row) + + select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['aa','b']); + slice + --------------------- + "b"=>"2", "aa"=>"1" + (1 row) + + select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']); + slice + ------------------------------- + "b"=>"2", "c"=>"3", "aa"=>"1" + (1 row) + + select pg_column_size(slice(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(slice(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) + + -- pairs of arrays + select hstore(ARRAY['a','b','asd'], ARRAY['g','h','i']); + hstore + -------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" + (1 row) + + select hstore(ARRAY['a','b','asd'], ARRAY['g','h',NULL]); + hstore + --------------------------------- + "a"=>"g", "b"=>"h", "asd"=>NULL + (1 row) + + select hstore(ARRAY['z','y','x'], ARRAY['1','2','3']); + hstore + ------------------------------ + "x"=>"3", "y"=>"2", "z"=>"1" + (1 row) + + select hstore(ARRAY['aaa','bb','c','d'], ARRAY[null::text,null,null,null]); + hstore + ----------------------------------------------- + "c"=>NULL, "d"=>NULL, "bb"=>NULL, "aaa"=>NULL + (1 row) + + select hstore(ARRAY['aaa','bb','c','d'], null); + hstore + ----------------------------------------------- + "c"=>NULL, "d"=>NULL, "bb"=>NULL, "aaa"=>NULL + (1 row) + + select quote_literal(hstore('{}'::text[], '{}'::text[])); + quote_literal + --------------- + '' + (1 row) + + select quote_literal(hstore('{}'::text[], null)); + quote_literal + --------------- + '' + (1 row) + + select hstore(ARRAY['a'], '{}'::text[]); -- error + ERROR: arrays must have same bounds + select hstore('{}'::text[], ARRAY['a']); -- error + ERROR: arrays must have same bounds + select pg_column_size(hstore(ARRAY['a','b','asd'], ARRAY['g','h','i'])) + = pg_column_size('a=>g, b=>h, asd=>i'::hstore); + ?column? + ---------- + t + (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 + -------------- + {b,aa,cq,fg} + (1 row) + + select akeys('""=>1'); + akeys + ------- + {""} + (1 row) + + select akeys(''); + akeys + ------- + {} + (1 row) + + select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + avals + ----------- + {g,1,l,f} + (1 row) + + select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); + avals + -------------- + {g,1,l,NULL} + (1 row) + + select avals('""=>1'); + avals + ------- + {1} + (1 row) + + select avals(''); + 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 + ------- + b + aa + cq + fg + (4 rows) + + select * from skeys('""=>1'); + skeys + ------- + + (1 row) + + select * from skeys(''); + skeys + ------- + (0 rows) + + select * from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + svals + ------- + g + 1 + l + f + (4 rows) + + select *, svals is null from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); + svals | ?column? + -------+---------- + g | f + 1 | f + l | f + | t + (4 rows) + + select * from svals('""=>1'); + svals + ------- + 1 + (1 row) + + select * from svals(''); + svals + ------- + (0 rows) + + select * from each('aaa=>bq, b=>NULL, ""=>1 '); + key | value + -----+------- + | 1 + b | + aaa | bq + (3 rows) + + -- @> + select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; + ?column? + ---------- + t + (1 row) + + select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>NULL'; + ?column? + ---------- + t + (1 row) + + select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, g=>NULL'; + ?column? + ---------- + f + (1 row) + + select 'a=>b, b=>1, c=>NULL'::hstore @> 'g=>NULL'; + ?column? + ---------- + f + (1 row) + + select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>c'; + ?column? + ---------- + f + (1 row) + + select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; + ?column? + ---------- + t + (1 row) + + select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>q'; + ?column? + ---------- + f + (1 row) + + CREATE TABLE testhstore (h hstore); + \copy testhstore from 'data/hstore.data' + select count(*) from testhstore where h @> 'wait=>NULL'; + count + ------- + 1 + (1 row) + + select count(*) from testhstore where h @> 'wait=>CC'; + count + ------- + 15 + (1 row) + + select count(*) from testhstore where h @> 'wait=>CC, public=>t'; + count + ------- + 2 + (1 row) + + select count(*) from testhstore where h ? 'public'; + count + ------- + 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'; + count + ------- + 1 + (1 row) + + select count(*) from testhstore where h @> 'wait=>CC'; + count + ------- + 15 + (1 row) + + select count(*) from testhstore where h @> 'wait=>CC, public=>t'; + count + ------- + 2 + (1 row) + + select count(*) from testhstore where h ? 'public'; + count + ------- + 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; + select count(*) from testhstore where h @> 'wait=>NULL'; + count + ------- + 1 + (1 row) + + select count(*) from testhstore where h @> 'wait=>CC'; + count + ------- + 15 + (1 row) + + select count(*) from testhstore where h @> 'wait=>CC, public=>t'; + count + ------- + 2 + (1 row) + + select count(*) from testhstore where h ? 'public'; + count + ------- + 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 + ------- + 4781 + (1 row) + + select key, count(*) from (select (each(h)).key from testhstore) as wow group by key order by count desc, key; + key | count + -----------+------- + line | 884 + query | 207 + pos | 203 + node | 202 + space | 197 + status | 195 + public | 194 + title | 190 + wait | 190 + org | 189 + user | 189 + coauthors | 188 + disabled | 185 + indexed | 184 + cleaned | 180 + bad | 179 + date | 179 + world | 176 + state | 172 + subtitle | 169 + auth | 168 + 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) + + -- plpython integration + set plpython.hstore = 'public.hstore'; + ERROR: unrecognized configuration parameter "plpython.hstore" + create language plpythonu; + create or replace function select_one(h hstore, idx text) returns text as $$ + try: + return h.get(idx) + except AttributeError: + # h has not been transformed into a dict and is a string + return None + $$ language plpythonu; + select coalesce(select_one(h, 'node'), '<null>') as node from testhstore where select_one(h, 'line')::integer < 10; + node + ------ + (0 rows) + + reset plpython.hstore; + ERROR: unrecognized configuration parameter "plpython.hstore" + create or replace function select_one(h hstore, idx text) returns text as $$ + try: + return h.get(idx) + except AttributeError: + # h has not been transformed into a dict and is a string + return None + $$ language plpythonu; + select coalesce(select_one(h, 'node'), '<null>') as node from testhstore where select_one(h, 'line')::integer < 10; + node + ------ + (0 rows) + + drop function select_one(hstore, text); + drop language plpythonu; diff --git a/contrib/hstore/hstore.h b/contrib/hstore/hstore.h index 8906397..6edfc70 100644 *** a/contrib/hstore/hstore.h --- b/contrib/hstore/hstore.h *************** extern Pairs *hstoreArrayToPairs(ArrayTy *** 174,179 **** --- 174,182 ---- #define HStoreExistsAllStrategyNumber 11 #define HStoreOldContainsStrategyNumber 13 /* backwards compatibility */ + /* PL/Python support */ + extern void hstore_plpython_init(void); + /* * 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 diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c index 0d6f0b6..92c8db9 100644 *** a/contrib/hstore/hstore_io.c --- b/contrib/hstore/hstore_io.c *************** PG_MODULE_MAGIC; *** 20,25 **** --- 20,26 ---- /* old names for C functions */ HSTORE_POLLUTE(hstore_from_text, tconvert); + void _PG_init(void); typedef struct { *************** hstore_send(PG_FUNCTION_ARGS) *** 1211,1213 **** --- 1212,1220 ---- PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } + + void + _PG_init(void) + { + hstore_plpython_init(); + } diff --git a/contrib/hstore/hstore_plpython.c b/contrib/hstore/hstore_plpython.c index ...4bcdd83 . *** a/contrib/hstore/hstore_plpython.c --- b/contrib/hstore/hstore_plpython.c *************** *** 0 **** --- 1,247 ---- + /* + * contrib/src/hstore_plpython.c + * + * bidirectional transformation between hstores and Python dictionary objects + */ + + /* Only build if PL/Python support is needed */ + #if defined(HSTORE_PLPYTHON_SUPPORT) + + #if defined(_MSC_VER) && defined(_DEBUG) + /* Python uses #pragma to bring in a non-default libpython on VC++ if + * _DEBUG is defined */ + #undef _DEBUG + /* Also hide away errcode, since we load Python.h before postgres.h */ + #define errcode __msvc_errcode + #include <Python.h> + #undef errcode + #define _DEBUG + #elif defined (_MSC_VER) + #define errcode __msvc_errcode + #include <Python.h> + #undef errcode + #else + #include <Python.h> + #endif + + #include "postgres.h" + #include "utils/guc.h" + #include "utils/builtins.h" + #include "utils/syscache.h" + #include "catalog/namespace.h" + + #include "plpython.h" + #include "hstore.h" + + static Oid get_hstore_oid(const char *name); + static void set_hstore_parsers(Oid); + + static PyObject *hstore_to_dict(void *, Datum); + static Datum dict_to_hstore(void *, int32, PyObject *); + + /* GUC variables */ + + static char *hstore_name; + + /* Previous hstore OID */ + + static Oid previous; + + PLyParsers parsers; + + static PyObject * + hstore_to_dict(void *ignored, Datum d) + { + HStore *hstore = DatumGetHStoreP(d); + char *base; + HEntry *entries; + int count; + int i; + PyObject *ret; + + base = STRPTR(hstore); + entries = ARRPTR(hstore); + + ret = PyDict_New(); + + count = HS_COUNT(hstore); + + for (i = 0; i < count; i++) + { + PyObject *key, *val; + + key = PyString_FromStringAndSize(HS_KEY(entries, base, i), + HS_KEYLEN(entries, i)); + if (HS_VALISNULL(entries, i)) { + Py_INCREF(Py_None); + val = Py_None; + } + else { + val = PyString_FromStringAndSize(HS_VAL(entries, base, i), + HS_VALLEN(entries, i)); + } + + PyDict_SetItem(ret, key, val); + } + + return ret; + } + + static Datum + dict_to_hstore(void *ignored, int32 typmod, PyObject *dict) + { + HStore *hstore; + int pcount; + Pairs *pairs; + PyObject *key; + PyObject *value; + Py_ssize_t pos; + char *keys; + char *vals; + int keylen; + int vallen; + int buflen; + int i; + + if (!PyDict_Check(dict)) + ereport(ERROR, + (errmsg("hstores can only be constructed " + "from Python dictionaries"))); + + pcount = PyDict_Size(dict); + pairs = palloc(pcount * sizeof(Pairs)); + pos = i = 0; + /* loop over the dictionary, creating a Pair for each key/value pair */ + while (PyDict_Next(dict, &pos, &key, &value)) { + if (!PyString_Check(key)) + elog(ERROR, "hstore keys have to be strings"); + + PyString_AsStringAndSize(key, &keys, &keylen); + + if (strlen(keys) != keylen) + elog(ERROR, "hstore keys cannot contain NUL bytes"); + + pairs[i].key = pstrdup(keys); + pairs[i].keylen = hstoreCheckKeyLen(keylen); + pairs[i].needfree = true; + + if (value == Py_None) { + pairs[i].val = NULL; + pairs[i].vallen = 0; + pairs[i].isnull = true; + } + else { + if (!PyString_Check(value)) + elog(ERROR, "hstore values have to be strings"); + + PyString_AsStringAndSize(value, &vals, &vallen); + + if (strlen(vals) != vallen) + elog(ERROR, "hstore values cannot contain NUL bytes"); + + pairs[i].val = pstrdup(vals); + pairs[i].vallen = hstoreCheckValLen(vallen); + pairs[i].isnull = false; + } + + i++; + } + pcount = hstoreUniquePairs(pairs, pcount, &buflen); + hstore = hstorePairs(pairs, pcount, buflen); + + return PointerGetDatum(hstore); + } + + static const char * + recheck_hstore_oid(const char *newvalue, bool doit, GucSource source) + { + Oid hstore_oid; + + hstore_oid = get_hstore_oid(newvalue); + + if (*newvalue && !OidIsValid(hstore_oid)) + return NULL; + + if (doit) + set_hstore_parsers(hstore_oid); + + return newvalue; + } + + void + hstore_plpython_init(void) + { + DefineCustomStringVariable("plpython.hstore", + "The fully qualified name of the hstore type.", + NULL, + &hstore_name, + "", + PGC_SUSET, + 0, + recheck_hstore_oid, + NULL); + + EmitWarningsOnPlaceholders("plpython"); + + previous = InvalidOid; + parsers.in = hstore_to_dict; + parsers.out = dict_to_hstore; + + if (hstore_name && *hstore_name) + recheck_hstore_oid(hstore_name, true, PGC_S_FILE); + } + + static Oid + get_hstore_oid(const char *name) + { + text *text_name; + List *hstore_name; + char *type_name; + Oid type_namespace; + Oid typoid; + + Assert(name != NULL); + + if (!(*name)) + return InvalidOid; + + text_name = cstring_to_text(name); + hstore_name = textToQualifiedNameList(text_name); + pfree(text_name); + + type_namespace = QualifiedNameGetCreationNamespace(hstore_name, &type_name); + + typoid = GetSysCacheOid2(TYPENAMENSP, + CStringGetDatum(type_name), + ObjectIdGetDatum(type_namespace)); + + return typoid; + } + + static void + set_hstore_parsers(Oid hstore_oid) + { + char name[NAMEDATALEN]; + + if (OidIsValid(previous)) + { + snprintf(name, NAMEDATALEN, PARSERS_VARIABLE_PATTERN, previous); + *find_rendezvous_variable(name) = NULL; + } + + if (OidIsValid(hstore_oid)) + { + snprintf(name, NAMEDATALEN, PARSERS_VARIABLE_PATTERN, hstore_oid); + *find_rendezvous_variable(name) = &parsers; + previous = hstore_oid; + } + } + + #else /* !defined(HSTORE_PLPYTHON_SUPPORT) */ + + extern void hstore_plpython_init(void); + + void + hstore_plpython_init(void) {}; + + #endif /* defined(HSTORE_PLPYTHON_SUPPORT) */ diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql index 58a7967..065e9a1 100644 *** a/contrib/hstore/sql/hstore.sql --- b/contrib/hstore/sql/hstore.sql *************** set enable_seqscan=off; *** 338,340 **** --- 338,369 ---- select count(*) from testhstore where h #># 'p=>1'; select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t'; + + -- plpython integration + set plpython.hstore = 'public.hstore'; + create language plpythonu; + + create or replace function select_one(h hstore, idx text) returns text as $$ + try: + return h.get(idx) + except AttributeError: + # h has not been transformed into a dict and is a string + return None + $$ language plpythonu; + + select coalesce(select_one(h, 'node'), '<null>') as node from testhstore where select_one(h, 'line')::integer < 10; + + reset plpython.hstore; + + create or replace function select_one(h hstore, idx text) returns text as $$ + try: + return h.get(idx) + except AttributeError: + # h has not been transformed into a dict and is a string + return None + $$ language plpythonu; + + select coalesce(select_one(h, 'node'), '<null>') as node from testhstore where select_one(h, 'line')::integer < 10; + + drop function select_one(hstore, text); + drop language plpythonu; diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index fff7de7..2ed06f4 100644 *** a/src/pl/plpython/plpython.c --- b/src/pl/plpython/plpython.c *************** typedef int Py_ssize_t; *** 90,95 **** --- 90,97 ---- #include <fcntl.h> /* postgreSQL stuff */ + #include "plpython.h" + #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/trigger.h" *************** static PyObject *PLyList_FromArray(PLyDa *** 358,363 **** --- 360,368 ---- static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc); + static PLyParserIn PLy_get_custom_input_function(Oid oid); + static PLyParserOut PLy_get_custom_output_function(Oid oid); + static Datum PLyObject_ToBool(PLyObToDatum *, int32, PyObject *); static Datum PLyObject_ToBytea(PLyObToDatum *, int32, PyObject *); static Datum PLyObject_ToDatum(PLyObToDatum *, int32, PyObject *); *************** PLy_output_datum_func2(PLyObToDatum *arg *** 1819,1824 **** --- 1824,1830 ---- { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); Oid element_type; + Oid argument_type; perm_fmgr_info(typeStruct->typinput, &arg->typfunc); arg->typoid = HeapTupleGetOid(typeTup); *************** PLy_output_datum_func2(PLyObToDatum *arg *** 1826,1837 **** arg->typbyval = typeStruct->typbyval; element_type = get_element_type(arg->typoid); /* * Select a conversion function to convert Python objects to PostgreSQL * datums. Most data types can go through the generic function. */ ! switch (getBaseType(element_type ? element_type : arg->typoid)) { case BOOLOID: arg->func = PLyObject_ToBool; --- 1832,1844 ---- arg->typbyval = typeStruct->typbyval; element_type = get_element_type(arg->typoid); + argument_type = getBaseType(element_type ? element_type : arg->typoid); /* * Select a conversion function to convert Python objects to PostgreSQL * datums. Most data types can go through the generic function. */ ! switch (argument_type) { case BOOLOID: arg->func = PLyObject_ToBool; *************** PLy_output_datum_func2(PLyObToDatum *arg *** 1840,1846 **** arg->func = PLyObject_ToBytea; break; default: ! arg->func = PLyObject_ToDatum; break; } --- 1847,1859 ---- arg->func = PLyObject_ToBytea; break; default: ! /* Last ditch effort of finding a rendezvous variable pointing to ! * a parser function, useful for extension modules plugging in ! * their own parsers ! */ ! arg->func = (PLyObToDatumFunc) PLy_get_custom_output_function(argument_type); ! if (arg->func == NULL) ! arg->func = PLyObject_ToDatum; break; } *************** PLy_input_datum_func2(PLyDatumToOb *arg, *** 1882,1887 **** --- 1895,1901 ---- { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); Oid element_type = get_element_type(typeOid); + Oid argument_type; /* Get the type's conversion information */ perm_fmgr_info(typeStruct->typoutput, &arg->typfunc); *************** PLy_input_datum_func2(PLyDatumToOb *arg, *** 1891,1898 **** arg->typlen = typeStruct->typlen; arg->typalign = typeStruct->typalign; /* Determine which kind of Python object we will convert to */ ! switch (getBaseType(element_type ? element_type : typeOid)) { case BOOLOID: arg->func = PLyBool_FromBool; --- 1905,1914 ---- arg->typlen = typeStruct->typlen; arg->typalign = typeStruct->typalign; + argument_type = getBaseType(element_type ? element_type : typeOid); + /* Determine which kind of Python object we will convert to */ ! switch (argument_type) { case BOOLOID: arg->func = PLyBool_FromBool; *************** PLy_input_datum_func2(PLyDatumToOb *arg, *** 1919,1925 **** arg->func = PLyBytes_FromBytea; break; default: ! arg->func = PLyString_FromDatum; break; } --- 1935,1947 ---- arg->func = PLyBytes_FromBytea; break; default: ! /* Last ditch effort of finding a rendezvous variable pointing to ! * a parser function, useful for extension modules plugging in ! * their own parsers ! */ ! arg->func = (PLyDatumToObFunc) PLy_get_custom_input_function(argument_type); ! if (arg->func == NULL) ! arg->func = PLyString_FromDatum; break; } *************** PLy_typeinfo_dealloc(PLyTypeInfo *arg) *** 1960,1965 **** --- 1982,2021 ---- } } + /* + * Getting the parser functions from a rendezvous variable set by another + * extension. + */ + static PLyParserIn + PLy_get_custom_input_function(Oid oid) + { + PLyParsers *parsers; + char name[NAMEDATALEN]; + + snprintf(name, NAMEDATALEN, PARSERS_VARIABLE_PATTERN, oid); + parsers = *find_rendezvous_variable(name); + + if (parsers == NULL) + return NULL; + + return parsers->in; + } + + static PLyParserOut + PLy_get_custom_output_function(Oid oid) + { + PLyParsers *parsers; + char name[NAMEDATALEN]; + + snprintf(name, NAMEDATALEN, PARSERS_VARIABLE_PATTERN, oid); + parsers = *find_rendezvous_variable(name); + + if (parsers == NULL) + return NULL; + + return parsers->out; + } + static PyObject * PLyBool_FromBool(PLyDatumToOb *arg, Datum d) { diff --git a/src/pl/plpython/plpython.h b/src/pl/plpython/plpython.h index ...63c40c5 . *** a/src/pl/plpython/plpython.h --- b/src/pl/plpython/plpython.h *************** *** 0 **** --- 1,40 ---- + /* + * src/pl/plpython/plpython.h + */ + #ifndef __PLPYTHON_H__ + #define __PLPYTHON_H__ + + + + /* + * Rendezvous variable pattern for parsers exported from other extensions + * + * An extension providing parsres for type X should look up the type's OID and + * set a rendezvous variable using this pattern that points to a PLyParsers + * structure. PL/Python will then use these parsers for arguments with that + * OID. + */ + #define PARSERS_VARIABLE_PATTERN "plpython_%u_parsers" + + /* + * Types for parsers functions that other modules can export to transform + * Datums into PyObjects and back. The types need to be compatible with + * PLyObToDatumFunc and PLyDatumToObFunc, but we don't want to expose too much + * of plpython.c's guts here, so the first arguments is mandated to be a void + * pointer that should not be touched. An extension should know exactly what + * it's dealing with, so there's no need for it to look at anything contained + * in PLyTypeInfo, which is what gets passed here. + * + * The output parser also gets the type's typmod, which might actually be + * useful. + */ + typedef PyObject *(*PLyParserIn) (void *, Datum); + typedef Datum (*PLyParserOut) (void *, int32, PyObject *); + + typedef struct PLyParsers + { + PLyParserIn in; + PLyParserOut out; + } PLyParsers; + + #endif /* __PLPYTHON_H__ */
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers