Skip to content
Snippets Groups Projects
Commit cac76582 authored by Peter Eisentraut's avatar Peter Eisentraut
Browse files

Add transforms feature

This provides a mechanism for specifying conversions between SQL data
types and procedural languages.  As examples, there are transforms
for hstore and ltree for PL/Perl and PL/Python.

reviews by Pavel Stěhule and Andres Freund
parent f320cbb6
No related branches found
No related tags found
No related merge requests found
Showing
with 993 additions and 0 deletions
......@@ -71,6 +71,18 @@ else
ALWAYS_SUBDIRS += sepgsql
endif
ifeq ($(with_perl),yes)
SUBDIRS += hstore_plperl
else
ALWAYS_SUBDIRS += hstore_plperl
endif
ifeq ($(with_python),yes)
SUBDIRS += hstore_plpython ltree_plpython
else
ALWAYS_SUBDIRS += hstore_plpython ltree_plpython
endif
# Missing:
# start-scripts \ (does not have a makefile)
......
# Generated subdirectories
/log/
/results/
/tmp_check/
# contrib/hstore_plperl/Makefile
MODULE_big = hstore_plperl
OBJS = hstore_plperl.o
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl -I$(perl_archlibexp)/CORE -I$(top_srcdir)/contrib/hstore
EXTENSION = hstore_plperl hstore_plperlu
DATA = hstore_plperl--1.0.sql hstore_plperlu--1.0.sql
REGRESS = hstore_plperl create_transform
REGRESS_OPTS = --load-extension=hstore --load-extension=plperl --load-extension=plperlu
EXTRA_INSTALL = contrib/hstore
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/hstore_plperl
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
-- general regression test for transforms
DROP EXTENSION IF EXISTS hstore CASCADE;
NOTICE: extension "hstore" does not exist, skipping
DROP EXTENSION IF EXISTS plperl CASCADE;
NOTICE: extension "plperl" does not exist, skipping
DROP EXTENSION IF EXISTS hstore_plperl CASCADE;
NOTICE: extension "hstore_plperl" does not exist, skipping
CREATE EXTENSION hstore;
CREATE EXTENSION plperl;
CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS '$libdir/hstore_plperl';
CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS '$libdir/hstore_plperl';
CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: type "foo" does not exist
CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: language "foo" does not exist
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: return data type of FROM SQL function must be "internal"
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: first argument of transform function must be type "internal"
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: transform for type hstore language plperl already exists
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl;
NOTICE: type "fake_type" does not exist, skipping
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang;
NOTICE: transform for type hstore language fake_lang does not exist, skipping
DROP TRANSFORM FOR foo LANGUAGE plperl;
ERROR: type "foo" does not exist
DROP TRANSFORM FOR hstore LANGUAGE foo;
ERROR: language "foo" does not exist
DROP TRANSFORM FOR hstore LANGUAGE plperl;
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl;
NOTICE: transform for type hstore language plperl does not exist, skipping
DROP FUNCTION hstore_to_plperl(val internal);
DROP FUNCTION plperl_to_hstore(val internal);
CREATE EXTENSION hstore_plperl;
\dx+ hstore_plperl
Objects in extension "hstore_plperl"
Object Description
--------------------------------------
function hstore_to_plperl(internal)
function plperl_to_hstore(internal)
transform for hstore language plperl
(3 rows)
ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl;
\dx+ hstore_plperl
Objects in extension "hstore_plperl"
Object Description
-------------------------------------
function hstore_to_plperl(internal)
function plperl_to_hstore(internal)
(2 rows)
ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl;
\dx+ hstore_plperl
Objects in extension "hstore_plperl"
Object Description
--------------------------------------
function hstore_to_plperl(internal)
function plperl_to_hstore(internal)
transform for hstore language plperl
(3 rows)
DROP EXTENSION hstore CASCADE;
NOTICE: drop cascades to extension hstore_plperl
DROP EXTENSION plperl CASCADE;
CREATE EXTENSION hstore_plperl;
CREATE EXTENSION hstore_plperlu;
SELECT transforms.udt_schema, transforms.udt_name,
routine_schema, routine_name,
group_name, transform_type
FROM information_schema.transforms JOIN information_schema.routines
USING (specific_catalog, specific_schema, specific_name)
ORDER BY 1, 2, 5, 6;
udt_schema | udt_name | routine_schema | routine_name | group_name | transform_type
------------+----------+----------------+-------------------+------------+----------------
public | hstore | public | hstore_to_plperl | plperl | FROM SQL
public | hstore | public | plperl_to_hstore | plperl | TO SQL
public | hstore | public | hstore_to_plperlu | plperlu | FROM SQL
public | hstore | public | plperlu_to_hstore | plperlu | TO SQL
(4 rows)
-- test hstore -> perl
CREATE FUNCTION test1(val hstore) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1('aa=>bb, cc=>NULL'::hstore);
INFO: $VAR1 = {
'aa' => 'bb',
'cc' => undef
};
CONTEXT: PL/Perl function "test1"
test1
-------
2
(1 row)
CREATE FUNCTION test1none(val hstore) RETURNS int
LANGUAGE plperlu
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1none('aa=>bb, cc=>NULL'::hstore);
INFO: $VAR1 = '"aa"=>"bb", "cc"=>NULL';
CONTEXT: PL/Perl function "test1none"
test1none
-----------
0
(1 row)
CREATE FUNCTION test1list(val hstore) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1list('aa=>bb, cc=>NULL'::hstore);
INFO: $VAR1 = {
'aa' => 'bb',
'cc' => undef
};
CONTEXT: PL/Perl function "test1list"
test1list
-----------
2
(1 row)
-- test hstore[] -> perl
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]->[0], $_[0]->[1]));
return scalar(keys %{$_[0]});
$$;
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
INFO: $VAR1 = {
'aa' => 'bb',
'cc' => undef
};
$VAR2 = {
'dd' => 'ee'
};
CONTEXT: PL/Perl function "test1arr"
test1arr
----------
2
(1 row)
-- test perl -> hstore
CREATE FUNCTION test2() RETURNS hstore
LANGUAGE plperl
TRANSFORM FOR TYPE hstore
AS $$
$val = {a => 1, b => 'boo', c => undef};
return $val;
$$;
SELECT test2();
test2
---------------------------------
"a"=>"1", "b"=>"boo", "c"=>NULL
(1 row)
-- test perl -> hstore[]
CREATE FUNCTION test2arr() RETURNS hstore[]
LANGUAGE plperl
TRANSFORM FOR TYPE hstore
AS $$
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}];
return $val;
$$;
SELECT test2arr();
test2arr
--------------------------------------------------------------
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
(1 row)
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1});
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
$val = {a => 1, b => 'boo', c => undef};
$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore");
$rv = spi_exec_prepared($plan, {}, $val);
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
$$;
SELECT test3();
INFO: $VAR1 = {
'aa' => 'bb',
'cc' => undef
};
CONTEXT: PL/Perl function "test3"
INFO: $VAR1 = '"a"=>"1", "b"=>"boo", "c"=>NULL';
CONTEXT: PL/Perl function "test3"
test3
-------
(1 row)
-- test trigger
CREATE TABLE test1 (a int, b hstore);
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
SELECT * FROM test1;
a | b
---+------------------------
1 | "aa"=>"bb", "cc"=>NULL
(1 row)
CREATE FUNCTION test4() RETURNS trigger
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_TD->{new}));
if ($_TD->{new}{a} == 1) {
$_TD->{new}{b} = {a => 1, b => 'boo', c => undef};
}
return "MODIFY";
$$;
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
UPDATE test1 SET a = a;
INFO: $VAR1 = {
'a' => '1',
'b' => {
'aa' => 'bb',
'cc' => undef
}
};
CONTEXT: PL/Perl function "test4"
SELECT * FROM test1;
a | b
---+---------------------------------
1 | "a"=>"1", "b"=>"boo", "c"=>NULL
(1 row)
DROP TABLE test1;
DROP FUNCTION test1(hstore);
DROP FUNCTION test1none(hstore);
DROP FUNCTION test1list(hstore);
DROP FUNCTION test1arr(hstore[]);
DROP FUNCTION test2();
DROP FUNCTION test2arr();
DROP FUNCTION test3();
DROP FUNCTION test4();
DROP EXTENSION hstore_plperl;
DROP EXTENSION hstore_plperlu;
DROP EXTENSION hstore;
DROP EXTENSION plperl;
DROP EXTENSION plperlu;
-- make sure the prerequisite libraries are loaded
DO '' LANGUAGE plperl;
SELECT NULL::hstore;
CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME';
CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME';
CREATE TRANSFORM FOR hstore LANGUAGE plperl (
FROM SQL WITH FUNCTION hstore_to_plperl(internal),
TO SQL WITH FUNCTION plperl_to_hstore(internal)
);
#include "postgres.h"
#undef _
#include "fmgr.h"
#include "plperl.h"
#include "plperl_helpers.h"
#include "hstore.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(hstore_to_plperl);
Datum hstore_to_plperl(PG_FUNCTION_ARGS);
Datum
hstore_to_plperl(PG_FUNCTION_ARGS)
{
HStore *in = PG_GETARG_HS(0);
int i;
int count = HS_COUNT(in);
char *base = STRPTR(in);
HEntry *entries = ARRPTR(in);
HV *hv;
hv = newHV();
for (i = 0; i < count; i++)
{
const char *key;
SV *value;
key = pnstrdup(HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
value = HS_VALISNULL(entries, i) ? newSV(0) : cstr2sv(pnstrdup(HS_VAL(entries, base,i), HS_VALLEN(entries, i)));
(void) hv_store(hv, key, strlen(key), value, 0);
}
return PointerGetDatum(newRV((SV *) hv));
}
PG_FUNCTION_INFO_V1(plperl_to_hstore);
Datum plperl_to_hstore(PG_FUNCTION_ARGS);
Datum
plperl_to_hstore(PG_FUNCTION_ARGS)
{
HV *hv;
HE *he;
int32 buflen;
int32 i;
int32 pcount;
HStore *out;
Pairs *pairs;
hv = (HV *) SvRV((SV *) PG_GETARG_POINTER(0));
pcount = hv_iterinit(hv);
pairs = palloc(pcount * sizeof(Pairs));
i = 0;
while ((he = hv_iternext(hv)))
{
char *key = sv2cstr(HeSVKEY_force(he));
SV *value = HeVAL(he);
pairs[i].key = pstrdup(key);
pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key));
pairs[i].needfree = true;
if (!SvOK(value))
{
pairs[i].val = NULL;
pairs[i].vallen = 0;
pairs[i].isnull = true;
}
else
{
pairs[i].val = pstrdup(sv2cstr(value));
pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val));
pairs[i].isnull = false;
}
i++;
}
pcount = hstoreUniquePairs(pairs, pcount, &buflen);
out = hstorePairs(pairs, pcount, buflen);
PG_RETURN_POINTER(out);
}
# hstore_plperl extension
comment = 'transform between hstore and plperl'
default_version = '1.0'
module_pathname = '$libdir/hstore_plperl'
relocatable = true
requires = 'hstore,plperl'
-- make sure the prerequisite libraries are loaded
DO '' LANGUAGE plperlu;
SELECT NULL::hstore;
CREATE FUNCTION hstore_to_plperlu(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'hstore_to_plperl';
CREATE FUNCTION plperlu_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'plperl_to_hstore';
CREATE TRANSFORM FOR hstore LANGUAGE plperlu (
FROM SQL WITH FUNCTION hstore_to_plperlu(internal),
TO SQL WITH FUNCTION plperlu_to_hstore(internal)
);
# hstore_plperlu extension
comment = 'transform between hstore and plperlu'
default_version = '1.0'
module_pathname = '$libdir/hstore_plperl'
relocatable = true
requires = 'hstore,plperlu'
-- general regression test for transforms
DROP EXTENSION IF EXISTS hstore CASCADE;
DROP EXTENSION IF EXISTS plperl CASCADE;
DROP EXTENSION IF EXISTS hstore_plperl CASCADE;
CREATE EXTENSION hstore;
CREATE EXTENSION plperl;
CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS '$libdir/hstore_plperl';
CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS '$libdir/hstore_plperl';
CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl;
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang;
DROP TRANSFORM FOR foo LANGUAGE plperl;
DROP TRANSFORM FOR hstore LANGUAGE foo;
DROP TRANSFORM FOR hstore LANGUAGE plperl;
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl;
DROP FUNCTION hstore_to_plperl(val internal);
DROP FUNCTION plperl_to_hstore(val internal);
CREATE EXTENSION hstore_plperl;
\dx+ hstore_plperl
ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl;
\dx+ hstore_plperl
ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl;
\dx+ hstore_plperl
DROP EXTENSION hstore CASCADE;
DROP EXTENSION plperl CASCADE;
CREATE EXTENSION hstore_plperl;
CREATE EXTENSION hstore_plperlu;
SELECT transforms.udt_schema, transforms.udt_name,
routine_schema, routine_name,
group_name, transform_type
FROM information_schema.transforms JOIN information_schema.routines
USING (specific_catalog, specific_schema, specific_name)
ORDER BY 1, 2, 5, 6;
-- test hstore -> perl
CREATE FUNCTION test1(val hstore) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1('aa=>bb, cc=>NULL'::hstore);
CREATE FUNCTION test1none(val hstore) RETURNS int
LANGUAGE plperlu
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1none('aa=>bb, cc=>NULL'::hstore);
CREATE FUNCTION test1list(val hstore) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1list('aa=>bb, cc=>NULL'::hstore);
-- test hstore[] -> perl
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]->[0], $_[0]->[1]));
return scalar(keys %{$_[0]});
$$;
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
-- test perl -> hstore
CREATE FUNCTION test2() RETURNS hstore
LANGUAGE plperl
TRANSFORM FOR TYPE hstore
AS $$
$val = {a => 1, b => 'boo', c => undef};
return $val;
$$;
SELECT test2();
-- test perl -> hstore[]
CREATE FUNCTION test2arr() RETURNS hstore[]
LANGUAGE plperl
TRANSFORM FOR TYPE hstore
AS $$
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}];
return $val;
$$;
SELECT test2arr();
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1});
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
$val = {a => 1, b => 'boo', c => undef};
$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore");
$rv = spi_exec_prepared($plan, {}, $val);
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
$$;
SELECT test3();
-- test trigger
CREATE TABLE test1 (a int, b hstore);
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
SELECT * FROM test1;
CREATE FUNCTION test4() RETURNS trigger
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_TD->{new}));
if ($_TD->{new}{a} == 1) {
$_TD->{new}{b} = {a => 1, b => 'boo', c => undef};
}
return "MODIFY";
$$;
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
UPDATE test1 SET a = a;
SELECT * FROM test1;
DROP TABLE test1;
DROP FUNCTION test1(hstore);
DROP FUNCTION test1none(hstore);
DROP FUNCTION test1list(hstore);
DROP FUNCTION test1arr(hstore[]);
DROP FUNCTION test2();
DROP FUNCTION test2arr();
DROP FUNCTION test3();
DROP FUNCTION test4();
DROP EXTENSION hstore_plperl;
DROP EXTENSION hstore_plperlu;
DROP EXTENSION hstore;
DROP EXTENSION plperl;
DROP EXTENSION plperlu;
# Generated subdirectories
/expected/python3/
/log/
/results/
/sql/python3/
/tmp_check/
# contrib/hstore_plpython/Makefile
MODULE_big = hstore_plpython$(python_majorversion)
OBJS = hstore_plpython.o
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/hstore
EXTENSION = hstore_plpythonu hstore_plpython2u hstore_plpython3u
DATA = hstore_plpythonu--1.0.sql hstore_plpython2u--1.0.sql hstore_plpython3u--1.0.sql
REGRESS = hstore_plpython
REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/hstore_plpython
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
REGRESS_OPTS = --load-extension=hstore
ifeq ($(python_majorversion),2)
REGRESS_OPTS += --load-extension=plpythonu --load-extension=hstore_plpythonu
endif
EXTRA_INSTALL = contrib/hstore
include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk
CREATE EXTENSION plpython2u;
CREATE EXTENSION hstore_plpython2u;
-- test hstore -> python
CREATE FUNCTION test1(val hstore) RETURNS int
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
assert isinstance(val, dict)
plpy.info(sorted(val.items()))
return len(val)
$$;
SELECT test1('aa=>bb, cc=>NULL'::hstore);
INFO: [('aa', 'bb'), ('cc', None)]
CONTEXT: PL/Python function "test1"
test1
-------
2
(1 row)
-- the same with the versioned language name
CREATE FUNCTION test1n(val hstore) RETURNS int
LANGUAGE plpython2u
TRANSFORM FOR TYPE hstore
AS $$
assert isinstance(val, dict)
plpy.info(sorted(val.items()))
return len(val)
$$;
SELECT test1n('aa=>bb, cc=>NULL'::hstore);
INFO: [('aa', 'bb'), ('cc', None)]
CONTEXT: PL/Python function "test1n"
test1n
--------
2
(1 row)
-- test hstore[] -> python
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
plpy.info(repr(val))
return len(val)
$$;
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
INFO: [{'aa': 'bb', 'cc': None}, {'dd': 'ee'}]
CONTEXT: PL/Python function "test1arr"
test1arr
----------
2
(1 row)
-- test python -> hstore
CREATE FUNCTION test2() RETURNS hstore
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
val = {'a': 1, 'b': 'boo', 'c': None}
return val
$$;
SELECT test2();
test2
---------------------------------
"a"=>"1", "b"=>"boo", "c"=>NULL
(1 row)
-- test python -> hstore[]
CREATE FUNCTION test2arr() RETURNS hstore[]
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val
$$;
SELECT test2arr();
test2arr
--------------------------------------------------------------
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
(1 row)
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1")
plpy.info(repr(rv[0]["col1"]))
val = {'a': 1, 'b': 'boo', 'c': None}
plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"])
rv = plpy.execute(plan, [val])
plpy.info(repr(rv[0]["col1"]))
$$;
SELECT test3();
INFO: {'aa': 'bb', 'cc': None}
CONTEXT: PL/Python function "test3"
INFO: '"a"=>"1", "b"=>"boo", "c"=>NULL'
CONTEXT: PL/Python function "test3"
test3
-------
(1 row)
-- test trigger
CREATE TABLE test1 (a int, b hstore);
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
SELECT * FROM test1;
a | b
---+------------------------
1 | "aa"=>"bb", "cc"=>NULL
(1 row)
CREATE FUNCTION test4() RETURNS trigger
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
plpy.info("Trigger row: {'a': %r, 'b': %r}" % (TD["new"]["a"], TD["new"]["b"]))
if TD["new"]["a"] == 1:
TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None}
return "MODIFY"
$$;
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
UPDATE test1 SET a = a;
INFO: Trigger row: {'a': 1, 'b': {'aa': 'bb', 'cc': None}}
CONTEXT: PL/Python function "test4"
SELECT * FROM test1;
a | b
---+---------------------------------
1 | "a"=>"1", "b"=>"boo", "c"=>NULL
(1 row)
#include "postgres.h"
#include "fmgr.h"
#include "plpython.h"
#include "plpy_typeio.h"
#include "hstore.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(hstore_to_plpython);
Datum hstore_to_plpython(PG_FUNCTION_ARGS);
Datum
hstore_to_plpython(PG_FUNCTION_ARGS)
{
HStore *in = PG_GETARG_HS(0);
int i;
int count = HS_COUNT(in);
char *base = STRPTR(in);
HEntry *entries = ARRPTR(in);
PyObject *dict;
dict = PyDict_New();
for (i = 0; i < count; i++)
{
PyObject *key;
key = PyString_FromStringAndSize(HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
if (HS_VALISNULL(entries, i))
PyDict_SetItem(dict, key, Py_None);
else
{
PyObject *value;
value = PyString_FromStringAndSize(HS_VAL(entries, base,i), HS_VALLEN(entries, i));
PyDict_SetItem(dict, key, value);
Py_XDECREF(value);
}
Py_XDECREF(key);
}
return PointerGetDatum(dict);
}
PG_FUNCTION_INFO_V1(plpython_to_hstore);
Datum plpython_to_hstore(PG_FUNCTION_ARGS);
Datum
plpython_to_hstore(PG_FUNCTION_ARGS)
{
PyObject *dict;
volatile PyObject *items_v = NULL;
int32 pcount;
HStore *out;
dict = (PyObject *) PG_GETARG_POINTER(0);
if (!PyMapping_Check(dict))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("not a Python mapping")));
pcount = PyMapping_Size(dict);
items_v = PyMapping_Items(dict);
PG_TRY();
{
int32 buflen;
int32 i;
Pairs *pairs;
PyObject *items = (PyObject *) items_v;
pairs = palloc(pcount * sizeof(*pairs));
for (i = 0; i < pcount; i++)
{
PyObject *tuple;
PyObject *key;
PyObject *value;
tuple = PyList_GetItem(items, i);
key = PyTuple_GetItem(tuple, 0);
value = PyTuple_GetItem(tuple, 1);
pairs[i].key = PLyObject_AsString(key);
pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key));
pairs[i].needfree = true;
if (value == Py_None)
{
pairs[i].val = NULL;
pairs[i].vallen = 0;
pairs[i].isnull = true;
}
else
{
pairs[i].val = PLyObject_AsString(value);
pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val));
pairs[i].isnull = false;
}
}
Py_DECREF(items_v);
pcount = hstoreUniquePairs(pairs, pcount, &buflen);
out = hstorePairs(pairs, pcount, buflen);
}
PG_CATCH();
{
Py_DECREF(items_v);
PG_RE_THROW();
}
PG_END_TRY();
PG_RETURN_POINTER(out);
}
-- make sure the prerequisite libraries are loaded
DO '1' LANGUAGE plpython2u;
SELECT NULL::hstore;
CREATE FUNCTION hstore_to_plpython2(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'hstore_to_plpython';
CREATE FUNCTION plpython2_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'plpython_to_hstore';
CREATE TRANSFORM FOR hstore LANGUAGE plpython2u (
FROM SQL WITH FUNCTION hstore_to_plpython2(internal),
TO SQL WITH FUNCTION plpython2_to_hstore(internal)
);
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython2u IS 'transform between hstore and Python dict';
# hstore_plpython2u extension
comment = 'transform between hstore and plpython2u'
default_version = '1.0'
module_pathname = '$libdir/hstore_plpython2'
relocatable = true
requires = 'hstore,plpython2u'
-- make sure the prerequisite libraries are loaded
DO '1' LANGUAGE plpython3u;
SELECT NULL::hstore;
CREATE FUNCTION hstore_to_plpython3(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'hstore_to_plpython';
CREATE FUNCTION plpython3_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'plpython_to_hstore';
CREATE TRANSFORM FOR hstore LANGUAGE plpython3u (
FROM SQL WITH FUNCTION hstore_to_plpython3(internal),
TO SQL WITH FUNCTION plpython3_to_hstore(internal)
);
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython3u IS 'transform between hstore and Python dict';
# hstore_plpython3u extension
comment = 'transform between hstore and plpython3u'
default_version = '1.0'
module_pathname = '$libdir/hstore_plpython3'
relocatable = true
requires = 'hstore,plpython3u'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment