mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
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
This commit is contained in:
@ -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)
|
||||
|
||||
|
4
contrib/hstore_plperl/.gitignore
vendored
Normal file
4
contrib/hstore_plperl/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Generated subdirectories
|
||||
/log/
|
||||
/results/
|
||||
/tmp_check/
|
24
contrib/hstore_plperl/Makefile
Normal file
24
contrib/hstore_plperl/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
# 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
|
74
contrib/hstore_plperl/expected/create_transform.out
Normal file
74
contrib/hstore_plperl/expected/create_transform.out
Normal file
@ -0,0 +1,74 @@
|
||||
-- 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;
|
213
contrib/hstore_plperl/expected/hstore_plperl.out
Normal file
213
contrib/hstore_plperl/expected/hstore_plperl.out
Normal file
@ -0,0 +1,213 @@
|
||||
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;
|
17
contrib/hstore_plperl/hstore_plperl--1.0.sql
Normal file
17
contrib/hstore_plperl/hstore_plperl--1.0.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- 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)
|
||||
);
|
90
contrib/hstore_plperl/hstore_plperl.c
Normal file
90
contrib/hstore_plperl/hstore_plperl.c
Normal file
@ -0,0 +1,90 @@
|
||||
#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);
|
||||
}
|
6
contrib/hstore_plperl/hstore_plperl.control
Normal file
6
contrib/hstore_plperl/hstore_plperl.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plperl extension
|
||||
comment = 'transform between hstore and plperl'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plperl'
|
||||
relocatable = true
|
||||
requires = 'hstore,plperl'
|
17
contrib/hstore_plperl/hstore_plperlu--1.0.sql
Normal file
17
contrib/hstore_plperl/hstore_plperlu--1.0.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- 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)
|
||||
);
|
6
contrib/hstore_plperl/hstore_plperlu.control
Normal file
6
contrib/hstore_plperl/hstore_plperlu.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plperlu extension
|
||||
comment = 'transform between hstore and plperlu'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plperl'
|
||||
relocatable = true
|
||||
requires = 'hstore,plperlu'
|
47
contrib/hstore_plperl/sql/create_transform.sql
Normal file
47
contrib/hstore_plperl/sql/create_transform.sql
Normal file
@ -0,0 +1,47 @@
|
||||
-- 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;
|
148
contrib/hstore_plperl/sql/hstore_plperl.sql
Normal file
148
contrib/hstore_plperl/sql/hstore_plperl.sql
Normal file
@ -0,0 +1,148 @@
|
||||
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;
|
6
contrib/hstore_plpython/.gitignore
vendored
Normal file
6
contrib/hstore_plpython/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Generated subdirectories
|
||||
/expected/python3/
|
||||
/log/
|
||||
/results/
|
||||
/sql/python3/
|
||||
/tmp_check/
|
31
contrib/hstore_plpython/Makefile
Normal file
31
contrib/hstore_plpython/Makefile
Normal file
@ -0,0 +1,31 @@
|
||||
# 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
|
132
contrib/hstore_plpython/expected/hstore_plpython.out
Normal file
132
contrib/hstore_plpython/expected/hstore_plpython.out
Normal file
@ -0,0 +1,132 @@
|
||||
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)
|
||||
|
116
contrib/hstore_plpython/hstore_plpython.c
Normal file
116
contrib/hstore_plpython/hstore_plpython.c
Normal file
@ -0,0 +1,116 @@
|
||||
#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);
|
||||
}
|
19
contrib/hstore_plpython/hstore_plpython2u--1.0.sql
Normal file
19
contrib/hstore_plpython/hstore_plpython2u--1.0.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- 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';
|
6
contrib/hstore_plpython/hstore_plpython2u.control
Normal file
6
contrib/hstore_plpython/hstore_plpython2u.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plpython2u extension
|
||||
comment = 'transform between hstore and plpython2u'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plpython2'
|
||||
relocatable = true
|
||||
requires = 'hstore,plpython2u'
|
19
contrib/hstore_plpython/hstore_plpython3u--1.0.sql
Normal file
19
contrib/hstore_plpython/hstore_plpython3u--1.0.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- 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';
|
6
contrib/hstore_plpython/hstore_plpython3u.control
Normal file
6
contrib/hstore_plpython/hstore_plpython3u.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plpython3u extension
|
||||
comment = 'transform between hstore and plpython3u'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plpython3'
|
||||
relocatable = true
|
||||
requires = 'hstore,plpython3u'
|
19
contrib/hstore_plpython/hstore_plpythonu--1.0.sql
Normal file
19
contrib/hstore_plpython/hstore_plpythonu--1.0.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpythonu;
|
||||
SELECT NULL::hstore;
|
||||
|
||||
|
||||
CREATE FUNCTION hstore_to_plpython(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME';
|
||||
|
||||
CREATE FUNCTION plpython_to_hstore(val internal) RETURNS hstore
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME';
|
||||
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plpythonu (
|
||||
FROM SQL WITH FUNCTION hstore_to_plpython(internal),
|
||||
TO SQL WITH FUNCTION plpython_to_hstore(internal)
|
||||
);
|
||||
|
||||
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpythonu IS 'transform between hstore and Python dict';
|
6
contrib/hstore_plpython/hstore_plpythonu.control
Normal file
6
contrib/hstore_plpython/hstore_plpythonu.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plpythonu extension
|
||||
comment = 'transform between hstore and plpythonu'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plpython2'
|
||||
relocatable = true
|
||||
requires = 'hstore,plpythonu'
|
103
contrib/hstore_plpython/sql/hstore_plpython.sql
Normal file
103
contrib/hstore_plpython/sql/hstore_plpython.sql
Normal file
@ -0,0 +1,103 @@
|
||||
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);
|
||||
|
||||
|
||||
-- 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);
|
||||
|
||||
|
||||
-- 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']);
|
||||
|
||||
|
||||
-- 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();
|
||||
|
||||
|
||||
-- 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();
|
||||
|
||||
|
||||
-- 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();
|
||||
|
||||
|
||||
-- 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 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;
|
||||
SELECT * FROM test1;
|
6
contrib/ltree_plpython/.gitignore
vendored
Normal file
6
contrib/ltree_plpython/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Generated subdirectories
|
||||
/expected/python3/
|
||||
/log/
|
||||
/results/
|
||||
/sql/python3/
|
||||
/tmp_check/
|
31
contrib/ltree_plpython/Makefile
Normal file
31
contrib/ltree_plpython/Makefile
Normal file
@ -0,0 +1,31 @@
|
||||
# contrib/ltree_plpython/Makefile
|
||||
|
||||
MODULE_big = ltree_plpython$(python_majorversion)
|
||||
OBJS = ltree_plpython.o
|
||||
|
||||
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/ltree
|
||||
|
||||
EXTENSION = ltree_plpythonu ltree_plpython2u ltree_plpython3u
|
||||
DATA = ltree_plpythonu--1.0.sql ltree_plpython2u--1.0.sql ltree_plpython3u--1.0.sql
|
||||
|
||||
REGRESS = ltree_plpython
|
||||
REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
|
||||
|
||||
ifdef USE_PGXS
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
else
|
||||
subdir = contrib/ltree_plpython
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
include $(top_srcdir)/contrib/contrib-global.mk
|
||||
endif
|
||||
|
||||
REGRESS_OPTS = --load-extension=ltree
|
||||
ifeq ($(python_majorversion),2)
|
||||
REGRESS_OPTS += --load-extension=plpythonu --load-extension=ltree_plpythonu
|
||||
endif
|
||||
EXTRA_INSTALL = contrib/ltree
|
||||
|
||||
include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk
|
45
contrib/ltree_plpython/expected/ltree_plpython.out
Normal file
45
contrib/ltree_plpython/expected/ltree_plpython.out
Normal file
@ -0,0 +1,45 @@
|
||||
CREATE EXTENSION plpython2u;
|
||||
CREATE EXTENSION ltree_plpython2u;
|
||||
CREATE FUNCTION test1(val ltree) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
SELECT test1('aa.bb.cc'::ltree);
|
||||
INFO: ['aa', 'bb', 'cc']
|
||||
CONTEXT: PL/Python function "test1"
|
||||
test1
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
CREATE FUNCTION test1n(val ltree) RETURNS int
|
||||
LANGUAGE plpython2u
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
SELECT test1n('aa.bb.cc'::ltree);
|
||||
INFO: ['aa', 'bb', 'cc']
|
||||
CONTEXT: PL/Python function "test1n"
|
||||
test1n
|
||||
--------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
CREATE FUNCTION test2() RETURNS ltree
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
return ['foo', 'bar', 'baz']
|
||||
$$;
|
||||
-- plpython to ltree is not yet implemented, so this will fail,
|
||||
-- because it will try to parse the Python list as an ltree input
|
||||
-- string.
|
||||
SELECT test2();
|
||||
ERROR: syntax error at position 0
|
||||
CONTEXT: while creating return value
|
||||
PL/Python function "test2"
|
32
contrib/ltree_plpython/ltree_plpython.c
Normal file
32
contrib/ltree_plpython/ltree_plpython.c
Normal file
@ -0,0 +1,32 @@
|
||||
#include "postgres.h"
|
||||
#include "fmgr.h"
|
||||
#include "plpython.h"
|
||||
#include "ltree.h"
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
|
||||
PG_FUNCTION_INFO_V1(ltree_to_plpython);
|
||||
Datum ltree_to_plpython(PG_FUNCTION_ARGS);
|
||||
|
||||
Datum
|
||||
ltree_to_plpython(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ltree *in = PG_GETARG_LTREE(0);
|
||||
int i;
|
||||
PyObject *list;
|
||||
ltree_level *curlevel;
|
||||
|
||||
list = PyList_New(in->numlevel);
|
||||
|
||||
curlevel = LTREE_FIRST(in);
|
||||
for (i = 0; i < in->numlevel; i++)
|
||||
{
|
||||
PyList_SetItem(list, i, PyString_FromStringAndSize(curlevel->name, curlevel->len));
|
||||
curlevel = LEVEL_NEXT(curlevel);
|
||||
}
|
||||
|
||||
PG_FREE_IF_COPY(in, 0);
|
||||
|
||||
return PointerGetDatum(list);
|
||||
}
|
12
contrib/ltree_plpython/ltree_plpython2u--1.0.sql
Normal file
12
contrib/ltree_plpython/ltree_plpython2u--1.0.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpython2u;
|
||||
SELECT NULL::ltree;
|
||||
|
||||
|
||||
CREATE FUNCTION ltree_to_plpython2(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'ltree_to_plpython';
|
||||
|
||||
CREATE TRANSFORM FOR ltree LANGUAGE plpython2u (
|
||||
FROM SQL WITH FUNCTION ltree_to_plpython2(internal)
|
||||
);
|
6
contrib/ltree_plpython/ltree_plpython2u.control
Normal file
6
contrib/ltree_plpython/ltree_plpython2u.control
Normal file
@ -0,0 +1,6 @@
|
||||
# ltree_plpython2u extension
|
||||
comment = 'transform between ltree and plpython2u'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/ltree_plpython2'
|
||||
relocatable = true
|
||||
requires = 'ltree,plpython2u'
|
12
contrib/ltree_plpython/ltree_plpython3u--1.0.sql
Normal file
12
contrib/ltree_plpython/ltree_plpython3u--1.0.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpython3u;
|
||||
SELECT NULL::ltree;
|
||||
|
||||
|
||||
CREATE FUNCTION ltree_to_plpython3(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'ltree_to_plpython';
|
||||
|
||||
CREATE TRANSFORM FOR ltree LANGUAGE plpython3u (
|
||||
FROM SQL WITH FUNCTION ltree_to_plpython3(internal)
|
||||
);
|
6
contrib/ltree_plpython/ltree_plpython3u.control
Normal file
6
contrib/ltree_plpython/ltree_plpython3u.control
Normal file
@ -0,0 +1,6 @@
|
||||
# ltree_plpython3u extension
|
||||
comment = 'transform between ltree and plpython3u'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/ltree_plpython3'
|
||||
relocatable = true
|
||||
requires = 'ltree,plpython3u'
|
12
contrib/ltree_plpython/ltree_plpythonu--1.0.sql
Normal file
12
contrib/ltree_plpython/ltree_plpythonu--1.0.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpythonu;
|
||||
SELECT NULL::ltree;
|
||||
|
||||
|
||||
CREATE FUNCTION ltree_to_plpython(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME';
|
||||
|
||||
CREATE TRANSFORM FOR ltree LANGUAGE plpythonu (
|
||||
FROM SQL WITH FUNCTION ltree_to_plpython(internal)
|
||||
);
|
6
contrib/ltree_plpython/ltree_plpythonu.control
Normal file
6
contrib/ltree_plpython/ltree_plpythonu.control
Normal file
@ -0,0 +1,6 @@
|
||||
# ltree_plpythonu extension
|
||||
comment = 'transform between ltree and plpythonu'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/ltree_plpython2'
|
||||
relocatable = true
|
||||
requires = 'ltree,plpythonu'
|
37
contrib/ltree_plpython/sql/ltree_plpython.sql
Normal file
37
contrib/ltree_plpython/sql/ltree_plpython.sql
Normal file
@ -0,0 +1,37 @@
|
||||
CREATE EXTENSION plpython2u;
|
||||
CREATE EXTENSION ltree_plpython2u;
|
||||
|
||||
|
||||
CREATE FUNCTION test1(val ltree) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
|
||||
SELECT test1('aa.bb.cc'::ltree);
|
||||
|
||||
|
||||
CREATE FUNCTION test1n(val ltree) RETURNS int
|
||||
LANGUAGE plpython2u
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
|
||||
SELECT test1n('aa.bb.cc'::ltree);
|
||||
|
||||
|
||||
CREATE FUNCTION test2() RETURNS ltree
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
return ['foo', 'bar', 'baz']
|
||||
$$;
|
||||
|
||||
-- plpython to ltree is not yet implemented, so this will fail,
|
||||
-- because it will try to parse the Python list as an ltree input
|
||||
-- string.
|
||||
SELECT test2();
|
Reference in New Issue
Block a user