0001-Add-transforms-feature.patch
text/x-patch
Filename: 0001-Add-transforms-feature.patch
Type: text/x-patch
Part: 0
Message:
Re: [PATCH] Add transforms feature
>From 1943ea2428cbeb099f267fc07d4dabb8c1ce953d Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Tue, 12 Nov 2013 06:16:17 -0500
Subject: [PATCH] Add transforms feature
A transform is an SQL object that supplies to functions for converting
between data types and procedural languages. For example, a transform
could arrange that hstore is converted to an appropriate hash or
dictionary object in PL/Perl or PL/Python.
Externally visible changes:
- new SQL commands CREATE TRANSFORM and DROP TRANSFORM
- system catalog pg_transform
- transform support in PL/Perl and PL/Python
- PL/Perl and PL/Python install their header files for use by external
types
- transforms contrib modules hstore_plperl, hstore_plpython,
ltree_plpython
- regression test mangling for Python 3 was moved to a separate
makefile, for use by extensions
The regression tests for general CREATE TRANSFORM functionality are
under the hstore_plperl module in order to be able to test it on a real
transform implementation, instead of having to create an entire fake
procedural language under src/test/regress/, say.
Dynamic module linking on OS X was changed to allow undefined symbols at
build time. This is necessary so that a transform module can use
symbols from the type and the language modules, if necessary. Other
platforms already behaved this way, but the default on OS X was
different.
---
contrib/Makefile | 12 +
contrib/hstore_plperl/.gitignore | 4 +
contrib/hstore_plperl/Makefile | 23 ++
.../hstore_plperl/expected/create_transform.out | 70 +++++
contrib/hstore_plperl/expected/hstore_plperl.out | 175 +++++++++++++
contrib/hstore_plperl/hstore_plperl--1.0.sql | 17 ++
contrib/hstore_plperl/hstore_plperl.c | 90 +++++++
contrib/hstore_plperl/hstore_plperl.control | 6 +
contrib/hstore_plperl/hstore_plperlu--1.0.sql | 17 ++
contrib/hstore_plperl/hstore_plperlu.control | 6 +
contrib/hstore_plperl/sql/create_transform.sql | 45 ++++
contrib/hstore_plperl/sql/hstore_plperl.sql | 125 +++++++++
contrib/hstore_plpython/.gitignore | 6 +
contrib/hstore_plpython/Makefile | 30 +++
.../hstore_plpython/expected/hstore_plpython.out | 139 ++++++++++
contrib/hstore_plpython/hstore_plpython.c | 116 +++++++++
contrib/hstore_plpython/hstore_plpython2u--1.0.sql | 19 ++
contrib/hstore_plpython/hstore_plpython2u.control | 6 +
contrib/hstore_plpython/hstore_plpython3u--1.0.sql | 19 ++
contrib/hstore_plpython/hstore_plpython3u.control | 6 +
contrib/hstore_plpython/hstore_plpythonu--1.0.sql | 19 ++
contrib/hstore_plpython/hstore_plpythonu.control | 6 +
contrib/hstore_plpython/sql/hstore_plpython.sql | 108 ++++++++
contrib/ltree_plpython/.gitignore | 6 +
contrib/ltree_plpython/Makefile | 30 +++
contrib/ltree_plpython/expected/ltree_plpython.out | 42 +++
contrib/ltree_plpython/ltree_plpython.c | 32 +++
contrib/ltree_plpython/ltree_plpython2u--1.0.sql | 12 +
contrib/ltree_plpython/ltree_plpython2u.control | 6 +
contrib/ltree_plpython/ltree_plpython3u--1.0.sql | 12 +
contrib/ltree_plpython/ltree_plpython3u.control | 6 +
contrib/ltree_plpython/ltree_plpythonu--1.0.sql | 12 +
contrib/ltree_plpython/ltree_plpythonu.control | 6 +
contrib/ltree_plpython/sql/ltree_plpython.sql | 34 +++
doc/src/sgml/catalogs.sgml | 73 ++++++
doc/src/sgml/hstore.sgml | 18 ++
doc/src/sgml/ltree.sgml | 15 ++
doc/src/sgml/ref/allfiles.sgml | 2 +
doc/src/sgml/ref/alter_extension.sgml | 21 ++
doc/src/sgml/ref/comment.sgml | 22 ++
doc/src/sgml/ref/create_transform.sgml | 190 ++++++++++++++
doc/src/sgml/ref/drop_transform.sgml | 123 +++++++++
doc/src/sgml/reference.sgml | 2 +
src/Makefile.shlib | 2 +-
src/backend/catalog/Makefile | 1 +
src/backend/catalog/dependency.c | 8 +
src/backend/catalog/objectaddress.c | 65 ++++-
src/backend/catalog/pg_proc.c | 21 ++
src/backend/commands/dropcmds.c | 6 +
src/backend/commands/event_trigger.c | 3 +
src/backend/commands/functioncmds.c | 288 +++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 17 ++
src/backend/nodes/equalfuncs.c | 15 ++
src/backend/parser/gram.y | 82 +++++-
src/backend/tcop/utility.c | 16 ++
src/backend/utils/adt/ruleutils.c | 11 +-
src/backend/utils/cache/lsyscache.c | 65 +++++
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 5 +
src/bin/pg_dump/pg_dump.c | 230 ++++++++++++++++
src/bin/pg_dump/pg_dump.h | 11 +
src/bin/pg_dump/pg_dump_sort.c | 13 +-
src/include/catalog/catversion.h | 2 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/indexing.h | 5 +
src/include/catalog/pg_transform.h | 47 ++++
src/include/commands/defrem.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 15 ++
src/include/parser/kwlist.h | 2 +
src/include/utils/lsyscache.h | 3 +
src/include/utils/syscache.h | 2 +
src/interfaces/ecpg/preproc/ecpg.tokens | 2 +-
src/interfaces/ecpg/preproc/ecpg.trailer | 5 +-
src/interfaces/ecpg/preproc/ecpg_keywords.c | 2 -
src/pl/plperl/GNUmakefile | 4 +-
src/pl/plperl/plperl.c | 32 ++-
src/pl/plperl/plperl_helpers.h | 2 +
src/pl/plpython/Makefile | 40 +--
src/pl/plpython/plpy_main.c | 1 +
src/pl/plpython/plpy_procedure.c | 31 ++-
src/pl/plpython/plpy_procedure.h | 1 +
src/pl/plpython/plpy_spi.c | 3 +-
src/pl/plpython/plpy_typeio.c | 158 +++++++----
src/pl/plpython/plpy_typeio.h | 9 +-
src/pl/plpython/plpy_util.c | 21 +-
src/pl/plpython/plpy_util.h | 1 +
src/pl/plpython/plpython.h | 1 +
src/pl/plpython/regress-python3-mangle.mk | 35 +++
src/test/regress/expected/sanity_check.out | 1 +
90 files changed, 2897 insertions(+), 143 deletions(-)
create mode 100644 contrib/hstore_plperl/.gitignore
create mode 100644 contrib/hstore_plperl/Makefile
create mode 100644 contrib/hstore_plperl/expected/create_transform.out
create mode 100644 contrib/hstore_plperl/expected/hstore_plperl.out
create mode 100644 contrib/hstore_plperl/hstore_plperl--1.0.sql
create mode 100644 contrib/hstore_plperl/hstore_plperl.c
create mode 100644 contrib/hstore_plperl/hstore_plperl.control
create mode 100644 contrib/hstore_plperl/hstore_plperlu--1.0.sql
create mode 100644 contrib/hstore_plperl/hstore_plperlu.control
create mode 100644 contrib/hstore_plperl/sql/create_transform.sql
create mode 100644 contrib/hstore_plperl/sql/hstore_plperl.sql
create mode 100644 contrib/hstore_plpython/.gitignore
create mode 100644 contrib/hstore_plpython/Makefile
create mode 100644 contrib/hstore_plpython/expected/hstore_plpython.out
create mode 100644 contrib/hstore_plpython/hstore_plpython.c
create mode 100644 contrib/hstore_plpython/hstore_plpython2u--1.0.sql
create mode 100644 contrib/hstore_plpython/hstore_plpython2u.control
create mode 100644 contrib/hstore_plpython/hstore_plpython3u--1.0.sql
create mode 100644 contrib/hstore_plpython/hstore_plpython3u.control
create mode 100644 contrib/hstore_plpython/hstore_plpythonu--1.0.sql
create mode 100644 contrib/hstore_plpython/hstore_plpythonu.control
create mode 100644 contrib/hstore_plpython/sql/hstore_plpython.sql
create mode 100644 contrib/ltree_plpython/.gitignore
create mode 100644 contrib/ltree_plpython/Makefile
create mode 100644 contrib/ltree_plpython/expected/ltree_plpython.out
create mode 100644 contrib/ltree_plpython/ltree_plpython.c
create mode 100644 contrib/ltree_plpython/ltree_plpython2u--1.0.sql
create mode 100644 contrib/ltree_plpython/ltree_plpython2u.control
create mode 100644 contrib/ltree_plpython/ltree_plpython3u--1.0.sql
create mode 100644 contrib/ltree_plpython/ltree_plpython3u.control
create mode 100644 contrib/ltree_plpython/ltree_plpythonu--1.0.sql
create mode 100644 contrib/ltree_plpython/ltree_plpythonu.control
create mode 100644 contrib/ltree_plpython/sql/ltree_plpython.sql
create mode 100644 doc/src/sgml/ref/create_transform.sgml
create mode 100644 doc/src/sgml/ref/drop_transform.sgml
create mode 100644 src/include/catalog/pg_transform.h
create mode 100644 src/pl/plpython/regress-python3-mangle.mk
diff --git a/contrib/Makefile b/contrib/Makefile
index 8a2a937..6495b13 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -79,6 +79,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)
diff --git a/contrib/hstore_plperl/.gitignore b/contrib/hstore_plperl/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/contrib/hstore_plperl/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/hstore_plperl/Makefile b/contrib/hstore_plperl/Makefile
new file mode 100644
index 0000000..81f037e
--- /dev/null
+++ b/contrib/hstore_plperl/Makefile
@@ -0,0 +1,23 @@
+# 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 = --extra-install=contrib/hstore --load-extension=hstore --load-extension=plperl --load-extension=plperlu
+
+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
diff --git a/contrib/hstore_plperl/expected/create_transform.out b/contrib/hstore_plperl/expected/create_transform.out
new file mode 100644
index 0000000..8111d1b
--- /dev/null
+++ b/contrib/hstore_plperl/expected/create_transform.out
@@ -0,0 +1,70 @@
+-- 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 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;
diff --git a/contrib/hstore_plperl/expected/hstore_plperl.out b/contrib/hstore_plperl/expected/hstore_plperl.out
new file mode 100644
index 0000000..7e9fd8a
--- /dev/null
+++ b/contrib/hstore_plperl/expected/hstore_plperl.out
@@ -0,0 +1,175 @@
+CREATE EXTENSION hstore_plperl;
+CREATE EXTENSION hstore_plperlu;
+-- test hstore -> perl
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plperlu
+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)
+
+-- test hstore[] -> perl
+CREATE FUNCTION test1arr(val hstore[]) RETURNS int
+LANGUAGE plperlu
+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
+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
+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
+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 inline
+DO LANGUAGE plperlu $$
+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}));
+$$;
+INFO: $VAR1 = {
+ 'aa' => 'bb',
+ 'cc' => undef
+ };
+
+CONTEXT: PL/Perl anonymous code block
+INFO: $VAR1 = '"a"=>"1", "b"=>"boo", "c"=>NULL';
+
+CONTEXT: PL/Perl anonymous code block
+-- 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
+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 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;
diff --git a/contrib/hstore_plperl/hstore_plperl--1.0.sql b/contrib/hstore_plperl/hstore_plperl--1.0.sql
new file mode 100644
index 0000000..ea0ad76
--- /dev/null
+++ b/contrib/hstore_plperl/hstore_plperl--1.0.sql
@@ -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)
+);
diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c
new file mode 100644
index 0000000..cdc224c
--- /dev/null
+++ b/contrib/hstore_plperl/hstore_plperl.c
@@ -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);
+}
diff --git a/contrib/hstore_plperl/hstore_plperl.control b/contrib/hstore_plperl/hstore_plperl.control
new file mode 100644
index 0000000..16277f6
--- /dev/null
+++ b/contrib/hstore_plperl/hstore_plperl.control
@@ -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'
diff --git a/contrib/hstore_plperl/hstore_plperlu--1.0.sql b/contrib/hstore_plperl/hstore_plperlu--1.0.sql
new file mode 100644
index 0000000..46ad35c
--- /dev/null
+++ b/contrib/hstore_plperl/hstore_plperlu--1.0.sql
@@ -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)
+);
diff --git a/contrib/hstore_plperl/hstore_plperlu.control b/contrib/hstore_plperl/hstore_plperlu.control
new file mode 100644
index 0000000..c8d43b4
--- /dev/null
+++ b/contrib/hstore_plperl/hstore_plperlu.control
@@ -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'
diff --git a/contrib/hstore_plperl/sql/create_transform.sql b/contrib/hstore_plperl/sql/create_transform.sql
new file mode 100644
index 0000000..4f615e7
--- /dev/null
+++ b/contrib/hstore_plperl/sql/create_transform.sql
@@ -0,0 +1,45 @@
+-- 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 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;
diff --git a/contrib/hstore_plperl/sql/hstore_plperl.sql b/contrib/hstore_plperl/sql/hstore_plperl.sql
new file mode 100644
index 0000000..666b99a
--- /dev/null
+++ b/contrib/hstore_plperl/sql/hstore_plperl.sql
@@ -0,0 +1,125 @@
+CREATE EXTENSION hstore_plperl;
+CREATE EXTENSION hstore_plperlu;
+
+
+-- test hstore -> perl
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plperlu
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_[0]));
+return scalar(keys %{$_[0]});
+$$;
+
+SELECT test1('aa=>bb, cc=>NULL'::hstore);
+
+
+-- test hstore[] -> perl
+CREATE FUNCTION test1arr(val hstore[]) RETURNS int
+LANGUAGE plperlu
+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
+AS $$
+$val = {a => 1, b => 'boo', c => undef};
+return $val;
+$$;
+
+SELECT test2();
+
+
+-- test perl -> hstore[]
+CREATE FUNCTION test2arr() RETURNS hstore[]
+LANGUAGE plperl
+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
+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 inline
+DO LANGUAGE plperlu $$
+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}));
+$$;
+
+
+-- 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
+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 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;
diff --git a/contrib/hstore_plpython/.gitignore b/contrib/hstore_plpython/.gitignore
new file mode 100644
index 0000000..ce6fab9
--- /dev/null
+++ b/contrib/hstore_plpython/.gitignore
@@ -0,0 +1,6 @@
+# Generated subdirectories
+/expected/python3/
+/log/
+/results/
+/sql/python3/
+/tmp_check/
diff --git a/contrib/hstore_plpython/Makefile b/contrib/hstore_plpython/Makefile
new file mode 100644
index 0000000..65ddcf7
--- /dev/null
+++ b/contrib/hstore_plpython/Makefile
@@ -0,0 +1,30 @@
+# 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 = --extra-install=contrib/hstore --load-extension=hstore
+ifeq ($(python_majorversion),2)
+REGRESS_OPTS += --load-extension=plpythonu --load-extension=hstore_plpythonu
+endif
+
+include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk
diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out b/contrib/hstore_plpython/expected/hstore_plpython.out
new file mode 100644
index 0000000..fa61251
--- /dev/null
+++ b/contrib/hstore_plpython/expected/hstore_plpython.out
@@ -0,0 +1,139 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION hstore_plpython2u;
+-- test hstore -> python
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plpythonu
+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
+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
+ 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
+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
+ 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
+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 inline
+DO LANGUAGE plpythonu $$
+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"]))
+$$;
+INFO: {'aa': 'bb', 'cc': None}
+CONTEXT: PL/Python anonymous code block
+INFO: '"a"=>"1", "b"=>"boo", "c"=>NULL'
+CONTEXT: PL/Python anonymous code block
+-- 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
+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)
+
diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c
new file mode 100644
index 0000000..92cd4f8
--- /dev/null
+++ b/contrib/hstore_plpython/hstore_plpython.c
@@ -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);
+}
diff --git a/contrib/hstore_plpython/hstore_plpython2u--1.0.sql b/contrib/hstore_plpython/hstore_plpython2u--1.0.sql
new file mode 100644
index 0000000..c998de5
--- /dev/null
+++ b/contrib/hstore_plpython/hstore_plpython2u--1.0.sql
@@ -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';
diff --git a/contrib/hstore_plpython/hstore_plpython2u.control b/contrib/hstore_plpython/hstore_plpython2u.control
new file mode 100644
index 0000000..ed90567
--- /dev/null
+++ b/contrib/hstore_plpython/hstore_plpython2u.control
@@ -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'
diff --git a/contrib/hstore_plpython/hstore_plpython3u--1.0.sql b/contrib/hstore_plpython/hstore_plpython3u--1.0.sql
new file mode 100644
index 0000000..61d0e47
--- /dev/null
+++ b/contrib/hstore_plpython/hstore_plpython3u--1.0.sql
@@ -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';
diff --git a/contrib/hstore_plpython/hstore_plpython3u.control b/contrib/hstore_plpython/hstore_plpython3u.control
new file mode 100644
index 0000000..d86f38e
--- /dev/null
+++ b/contrib/hstore_plpython/hstore_plpython3u.control
@@ -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'
diff --git a/contrib/hstore_plpython/hstore_plpythonu--1.0.sql b/contrib/hstore_plpython/hstore_plpythonu--1.0.sql
new file mode 100644
index 0000000..6acb97a
--- /dev/null
+++ b/contrib/hstore_plpython/hstore_plpythonu--1.0.sql
@@ -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';
diff --git a/contrib/hstore_plpython/hstore_plpythonu.control b/contrib/hstore_plpython/hstore_plpythonu.control
new file mode 100644
index 0000000..8e9b35e
--- /dev/null
+++ b/contrib/hstore_plpython/hstore_plpythonu.control
@@ -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'
diff --git a/contrib/hstore_plpython/sql/hstore_plpython.sql b/contrib/hstore_plpython/sql/hstore_plpython.sql
new file mode 100644
index 0000000..2638645
--- /dev/null
+++ b/contrib/hstore_plpython/sql/hstore_plpython.sql
@@ -0,0 +1,108 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION hstore_plpython2u;
+
+
+-- test hstore -> python
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plpythonu
+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
+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
+ 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
+AS $$
+val = {'a': 1, 'b': 'boo', 'c': None}
+return val
+$$;
+
+SELECT test2();
+
+
+-- test python -> hstore[]
+ CREATE FUNCTION test2arr() RETURNS hstore[]
+ LANGUAGE plpythonu
+ 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
+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 inline
+DO LANGUAGE plpythonu $$
+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"]))
+$$;
+
+
+-- 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
+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;
diff --git a/contrib/ltree_plpython/.gitignore b/contrib/ltree_plpython/.gitignore
new file mode 100644
index 0000000..ce6fab9
--- /dev/null
+++ b/contrib/ltree_plpython/.gitignore
@@ -0,0 +1,6 @@
+# Generated subdirectories
+/expected/python3/
+/log/
+/results/
+/sql/python3/
+/tmp_check/
diff --git a/contrib/ltree_plpython/Makefile b/contrib/ltree_plpython/Makefile
new file mode 100644
index 0000000..34e03ce
--- /dev/null
+++ b/contrib/ltree_plpython/Makefile
@@ -0,0 +1,30 @@
+# 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 = --extra-install=contrib/ltree --load-extension=ltree
+ifeq ($(python_majorversion),2)
+REGRESS_OPTS += --load-extension=plpythonu --load-extension=ltree_plpythonu
+endif
+
+include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk
diff --git a/contrib/ltree_plpython/expected/ltree_plpython.out b/contrib/ltree_plpython/expected/ltree_plpython.out
new file mode 100644
index 0000000..6626fe9
--- /dev/null
+++ b/contrib/ltree_plpython/expected/ltree_plpython.out
@@ -0,0 +1,42 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION ltree_plpython2u;
+CREATE FUNCTION test1(val ltree) RETURNS int
+LANGUAGE plpythonu
+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
+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
+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"
diff --git a/contrib/ltree_plpython/ltree_plpython.c b/contrib/ltree_plpython/ltree_plpython.c
new file mode 100644
index 0000000..111e3e3
--- /dev/null
+++ b/contrib/ltree_plpython/ltree_plpython.c
@@ -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);
+}
diff --git a/contrib/ltree_plpython/ltree_plpython2u--1.0.sql b/contrib/ltree_plpython/ltree_plpython2u--1.0.sql
new file mode 100644
index 0000000..29a12d4
--- /dev/null
+++ b/contrib/ltree_plpython/ltree_plpython2u--1.0.sql
@@ -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)
+);
diff --git a/contrib/ltree_plpython/ltree_plpython2u.control b/contrib/ltree_plpython/ltree_plpython2u.control
new file mode 100644
index 0000000..bedfd0a
--- /dev/null
+++ b/contrib/ltree_plpython/ltree_plpython2u.control
@@ -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'
diff --git a/contrib/ltree_plpython/ltree_plpython3u--1.0.sql b/contrib/ltree_plpython/ltree_plpython3u--1.0.sql
new file mode 100644
index 0000000..1300a78
--- /dev/null
+++ b/contrib/ltree_plpython/ltree_plpython3u--1.0.sql
@@ -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)
+);
diff --git a/contrib/ltree_plpython/ltree_plpython3u.control b/contrib/ltree_plpython/ltree_plpython3u.control
new file mode 100644
index 0000000..96c9764
--- /dev/null
+++ b/contrib/ltree_plpython/ltree_plpython3u.control
@@ -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'
diff --git a/contrib/ltree_plpython/ltree_plpythonu--1.0.sql b/contrib/ltree_plpython/ltree_plpythonu--1.0.sql
new file mode 100644
index 0000000..1d1af28
--- /dev/null
+++ b/contrib/ltree_plpython/ltree_plpythonu--1.0.sql
@@ -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)
+);
diff --git a/contrib/ltree_plpython/ltree_plpythonu.control b/contrib/ltree_plpython/ltree_plpythonu.control
new file mode 100644
index 0000000..b03c89a
--- /dev/null
+++ b/contrib/ltree_plpython/ltree_plpythonu.control
@@ -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'
diff --git a/contrib/ltree_plpython/sql/ltree_plpython.sql b/contrib/ltree_plpython/sql/ltree_plpython.sql
new file mode 100644
index 0000000..2785592
--- /dev/null
+++ b/contrib/ltree_plpython/sql/ltree_plpython.sql
@@ -0,0 +1,34 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION ltree_plpython2u;
+
+
+CREATE FUNCTION test1(val ltree) RETURNS int
+LANGUAGE plpythonu
+AS $$
+plpy.info(repr(val))
+return len(val)
+$$;
+
+SELECT test1('aa.bb.cc'::ltree);
+
+
+CREATE FUNCTION test1n(val ltree) RETURNS int
+LANGUAGE plpython2u
+AS $$
+plpy.info(repr(val))
+return len(val)
+$$;
+
+SELECT test1n('aa.bb.cc'::ltree);
+
+
+CREATE FUNCTION test2() RETURNS ltree
+LANGUAGE plpythonu
+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();
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9388df5..9b06a50 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -264,6 +264,11 @@ <title>System Catalogs</title>
</row>
<row>
+ <entry><link linkend="catalog-pg-transform"><structname>pg_transform</structname></link></entry>
+ <entry>transforms (data type to procedural language conversions)</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-trigger"><structname>pg_trigger</structname></link></entry>
<entry>triggers</entry>
</row>
@@ -5756,6 +5761,74 @@ <title><structname>pg_tablespace</> Columns</title>
</sect1>
+ <sect1 id="catalog-pg-transform">
+ <title><structname>pg_transform</structname></title>
+
+ <indexterm zone="catalog-pg-transform">
+ <primary>pg_transform</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_transform</structname> stores information about
+ transforms, which are a mechanism to adapt data types to procedural
+ languages. See <xref linkend="sql-createtransform"> for more information.
+ </para>
+
+ <table>
+ <title><structname>pg_transform</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>trftype</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
+ <entry>OID of the data type this transform is for</entry>
+ </row>
+
+ <row>
+ <entry><structfield>trflang</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-language"><structname>pg_language</structname></link>.oid</literal></entry>
+ <entry>OID of the language this transform is for</entry>
+ </row>
+
+ <row>
+ <entry><structfield>trffromsql</structfield></entry>
+ <entry><type>regproc</type></entry>
+ <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+ <entry>
+ The OID of the function to use when converting the data type for input
+ to the procedural language (e.g., function parameters). Zero is stored
+ if this operation is not supported.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>trftosql</structfield></entry>
+ <entry><type>regproc</type></entry>
+ <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+ <entry>
+ The OID of the function to use when converting output from the
+ procedural language (e.g., return values) to the data type. Zero is
+ stored if this operation is not supported.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
+
<sect1 id="catalog-pg-trigger">
<title><structname>pg_trigger</structname></title>
diff --git a/doc/src/sgml/hstore.sgml b/doc/src/sgml/hstore.sgml
index fbe9543..a2657a8 100644
--- a/doc/src/sgml/hstore.sgml
+++ b/doc/src/sgml/hstore.sgml
@@ -597,6 +597,24 @@ <title>Compatibility</title>
</sect2>
<sect2>
+ <title>Transforms</title>
+
+ <para>
+ Additional extensions are available that implement transforms for
+ the <type>hstore</type> type for the languages PL/Perl and PL/Python. The
+ extensions for PL/Perl are called <literal>hstore_plperl</literal>
+ and <literal>hstore_plperlu</literal>, for trusted and untrusted PL/Perl.
+ If you install these extensions, <type>hstore</type> values are
+ automatically mapped to Perl hashes. The extensions for PL/Python are
+ called <literal>hstore_plpythonu</literal>, <literal>hstore_plpython2u</literal>,
+ and <literal>hstore_plpython3u</literal>
+ (see <xref linkend="plpython-python23"> for the PL/Python naming
+ convention). If you install them, <type>hstore</type> values are
+ automatically mapped to Python dictionaries.
+ </para>
+ </sect2>
+
+ <sect2>
<title>Authors</title>
<para>
diff --git a/doc/src/sgml/ltree.sgml b/doc/src/sgml/ltree.sgml
index cd8a061..051d63b 100644
--- a/doc/src/sgml/ltree.sgml
+++ b/doc/src/sgml/ltree.sgml
@@ -665,6 +665,21 @@ <title>Example</title>
</sect2>
<sect2>
+ <title>Transforms</title>
+
+ <para>
+ Additional extensions are available that implement transforms for
+ the <type>ltree</type> type for PL/Python. The extensions are
+ called <literal>ltree_plpythonu</literal>, <literal>ltree_plpython2u</literal>,
+ and <literal>ltree_plpython3u</literal>
+ (see <xref linkend="plpython-python23"> for the PL/Python naming
+ convention). If you install them, <type>ltree</type> values are
+ automatically mapped to Python lists. (The reverse is currently not
+ supported, however.)
+ </para>
+ </sect2>
+
+ <sect2>
<title>Authors</title>
<para>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 5846974..9ee8517 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -76,6 +76,7 @@
<!ENTITY createTable SYSTEM "create_table.sgml">
<!ENTITY createTableAs SYSTEM "create_table_as.sgml">
<!ENTITY createTableSpace SYSTEM "create_tablespace.sgml">
+<!ENTITY createTransform SYSTEM "create_transform.sgml">
<!ENTITY createTrigger SYSTEM "create_trigger.sgml">
<!ENTITY createTSConfig SYSTEM "create_tsconfig.sgml">
<!ENTITY createTSDictionary SYSTEM "create_tsdictionary.sgml">
@@ -116,6 +117,7 @@
<!ENTITY dropServer SYSTEM "drop_server.sgml">
<!ENTITY dropTable SYSTEM "drop_table.sgml">
<!ENTITY dropTableSpace SYSTEM "drop_tablespace.sgml">
+<!ENTITY dropTransform SYSTEM "drop_transform.sgml">
<!ENTITY dropTrigger SYSTEM "drop_trigger.sgml">
<!ENTITY dropTSConfig SYSTEM "drop_tsconfig.sgml">
<!ENTITY dropTSDictionary SYSTEM "drop_tsdictionary.sgml">
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index a14fcb4..90b8f93 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -52,6 +52,7 @@
TEXT SEARCH DICTIONARY <replaceable class="PARAMETER">object_name</replaceable> |
TEXT SEARCH PARSER <replaceable class="PARAMETER">object_name</replaceable> |
TEXT SEARCH TEMPLATE <replaceable class="PARAMETER">object_name</replaceable> |
+ TRANSFORM FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable> |
TYPE <replaceable class="PARAMETER">object_name</replaceable> |
VIEW <replaceable class="PARAMETER">object_name</replaceable>
</synopsis>
@@ -264,6 +265,26 @@ <title>Parameters</title>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable>type_name</replaceable></term>
+
+ <listitem>
+ <para>
+ The name of the data type of the transform.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>lang_name</replaceable></term>
+
+ <listitem>
+ <para>
+ The name of the language of the transform.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index e550500..7d4dcd0 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -54,6 +54,7 @@
TEXT SEARCH DICTIONARY <replaceable class="PARAMETER">object_name</replaceable> |
TEXT SEARCH PARSER <replaceable class="PARAMETER">object_name</replaceable> |
TEXT SEARCH TEMPLATE <replaceable class="PARAMETER">object_name</replaceable> |
+ TRANSFORM FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable> |
TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> |
TYPE <replaceable class="PARAMETER">object_name</replaceable> |
VIEW <replaceable class="PARAMETER">object_name</replaceable>
@@ -217,6 +218,26 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable>type_name</replaceable></term>
+
+ <listitem>
+ <para>
+ The name of the data type of the transform.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>lang_name</replaceable></term>
+
+ <listitem>
+ <para>
+ The name of the language of the transform.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">text</replaceable></term>
<listitem>
@@ -296,6 +317,7 @@ <title>Examples</title>
COMMENT ON TEXT SEARCH DICTIONARY swedish IS 'Snowball stemmer for swedish language';
COMMENT ON TEXT SEARCH PARSER my_parser IS 'Splits text into words';
COMMENT ON TEXT SEARCH TEMPLATE snowball IS 'Snowball stemmer';
+COMMENT ON TRANSFORM FOR hstore LANGUAGE plpythonu IS 'Transform between hstore and Python dict';
COMMENT ON TRIGGER my_trigger ON my_table IS 'Used for RI';
COMMENT ON TYPE complex IS 'Complex number data type';
COMMENT ON VIEW my_view IS 'View of departmental costs';
diff --git a/doc/src/sgml/ref/create_transform.sgml b/doc/src/sgml/ref/create_transform.sgml
new file mode 100644
index 0000000..5fdbfda
--- /dev/null
+++ b/doc/src/sgml/ref/create_transform.sgml
@@ -0,0 +1,190 @@
+<!-- doc/src/sgml/ref/create_transform.sgml -->
+
+<refentry id="SQL-CREATETRANSFORM">
+ <refmeta>
+ <refentrytitle>CREATE TRANSFORM</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>CREATE TRANSFORM</refname>
+ <refpurpose>define a new transform</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-createtransform">
+ <primary>CREATE TRANSFORM</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE [ OR REPLACE ] TRANSFORM FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable> (
+ FROM SQL WITH FUNCTION <replaceable>from_sql_function_name</replaceable> (<replaceable>argument_type</replaceable> [, ...]),
+ TO SQL WITH FUNCTION <replaceable>to_sql_function_name</replaceable> (<replaceable>argument_type</replaceable> [, ...])
+);
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id="sql-createtransform-description">
+ <title>Description</title>
+
+ <para>
+ <command>CREATE TRANSFORM</command> defines a new transform.
+ <command>CREATE OR REPLACE TRANSFORM</command> will either create a new
+ transform, or replace an existing definition.
+ </para>
+
+ <para>
+ A transform specifies how to adapt a data type to a procedural language.
+ For example, when writing a function in PL/Python using the hstore type,
+ PL/Python has no prior knowledge how to present hstore values in the Python
+ environment. Language implementations usually default to using the text
+ representation, but that is inconvenient when, for example, an associative
+ array or a list would be more appropriate. A transform specifies two
+ functions: one <quote>from SQL</quote> function that converts the type from
+ the SQL environment to the language (In other words, this function will be
+ invoked on the arguments of a function written in the language.), and one
+ <quote>to SQL</quote> function that converts the type from the language to
+ the SQL environment (In other words, this function will be invoked on the
+ return value of a function written in the language.). It is not necessary
+ to provide both of these functions. If one is not specified, the
+ language-specific default behavior will be used if necessary. (To prevent a
+ transformation in a certain direction from happening at all, you could also
+ write a transform function that always errors out.)
+ </para>
+
+ <para>
+ To be able to create a transform, you must own and
+ have <literal>USAGE</literal> privilege on the type, have
+ <literal>USAGE</literal> privilege on the language, and own and
+ have <literal>EXECUTE</literal> privilege on the from-SQL and to-SQL
+ functions, if specified.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><replaceable>type_name</replaceable></term>
+
+ <listitem>
+ <para>
+ The name of the data type of the transform.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>lang_name</replaceable></term>
+
+ <listitem>
+ <para>
+ The name of the language of the transform.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>from_sql_function_name</replaceable>(<replaceable>argument_type</replaceable> [, ...])</term>
+
+ <listitem>
+ <para>
+ The name of the function for converting the type from the SQL
+ environment to the language. It must take one argument of
+ type <type>internal</type> and return type <type>internal</type>. The
+ actual argument will be of the type for the transform, and the function
+ should be coded as if it were, but it is not allowed to declare an
+ SQL-level function function returning <type>internal</type> without at
+ least one argument of type <type>internal</type>. The actual return
+ value will be something specific to the language implementation.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>to_sql_function_name</replaceable>(<replaceable>argument_type</replaceable> [, ...])</term>
+
+ <listitem>
+ <para>
+ The name of the function for converting the type from the language to
+ the SQL environment. It must take one argument of type
+ <type>internal</type> and return the type that is the type for the
+ transform. The actual argument value will be something specific to the
+ language implementation.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id="sql-createtransform-notes">
+ <title>Notes</title>
+
+ <para>
+ Use <xref linkend="sql-droptransform"> to remove transforms.
+ </para>
+ </refsect1>
+
+ <refsect1 id="sql-createtransform-examples">
+ <title>Examples</title>
+
+ <para>
+ To create a transform for type <type>hstore</type> and language
+ <literal>plpythonu</literal>, first set up the type and the language:
+<programlisting>
+CREATE TYPE hstore ...;
+
+CREATE LANGUAGE plpythonu ...;
+</programlisting>
+ Then create the necessary functions:
+<programlisting>
+CREATE FUNCTION hstore_to_plpython(val internal) RETURNS internal
+LANGUAGE C STRICT IMMUTABLE
+AS ...;
+
+CREATE FUNCTION plpython_to_hstore(val internal) RETURNS hstore
+LANGUAGE C STRICT IMMUTABLE
+AS ...;
+</programlisting>
+ And finally create the transform to connect them all together:
+<programlisting>
+CREATE TRANSFORM FOR hstore LANGUAGE plpythonu (
+ FROM SQL WITH FUNCTION hstore_to_plpython(internal),
+ TO SQL WITH FUNCTION plpython_to_hstore(internal)
+);
+</programlisting>
+ In practice, these commands would be wrapped up in extensions.
+ </para>
+
+ <para>
+ The <filename>contrib</filename> section contains a number of extensions
+ that provide transforms, which can serve as real-world examples.
+ </para>
+ </refsect1>
+
+ <refsect1 id="sql-createtransform-compat">
+ <title>Compatibility</title>
+
+ <para>
+ This form of <command>CREATE TRANSFORM</command> is a
+ <productname>PostgreSQL</productname> extension. There is a <command>CREATE
+ TRANSFORM</command> command in the <acronym>SQL</acronym> standard, but it
+ is for adapting data types to client languages. That usage is not supported
+ by <productname>PostgreSQL</productname>.
+ </para>
+ </refsect1>
+
+ <refsect1 id="sql-createtransform-seealso">
+ <title>See Also</title>
+
+ <para>
+ <xref linkend="sql-createfunction">,
+ <xref linkend="sql-createlanguage">,
+ <xref linkend="sql-createtype">,
+ <xref linkend="sql-droptransform">
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_transform.sgml b/doc/src/sgml/ref/drop_transform.sgml
new file mode 100644
index 0000000..c9b558e
--- /dev/null
+++ b/doc/src/sgml/ref/drop_transform.sgml
@@ -0,0 +1,123 @@
+<!-- doc/src/sgml/ref/drop_transform.sgml -->
+
+<refentry id="SQL-DROPTRANSFORM">
+ <refmeta>
+ <refentrytitle>DROP TRANSFORM</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>DROP TRANSFORM</refname>
+ <refpurpose>remove a transform</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-droptransform">
+ <primary>DROP TRANSFORM</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP TRANSFORM [ IF EXISTS ] FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id="sql-droptransform-description">
+ <title>Description</title>
+
+ <para>
+ <command>DROP TRANSFORM</command> removes a previously defined transform.
+ </para>
+
+ <para>
+ To be able to drop a transform, you must own the type and the language.
+ These are the same privileges that are required to create a transform.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>IF EXISTS</literal></term>
+ <listitem>
+ <para>
+ Do not throw an error if the transform does not exist. A notice is issued
+ in this case.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>type_name</replaceable></term>
+
+ <listitem>
+ <para>
+ The name of the data type of the transform.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>lang_name</replaceable></term>
+
+ <listitem>
+ <para>
+ The name of the language of the transform.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>CASCADE</literal></term>
+ <listitem>
+ <para>
+ Automatically drop objects that depend on the transform.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Refuse to drop the transform if any objects depend on it. This is the
+ default.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id="sql-droptransform-examples">
+ <title>Examples</title>
+
+ <para>
+ To drop the transform for type <type>hstore</type> and language
+ <literal>plpythonu</literal>:
+<programlisting>
+DROP TRANSFORM FOR hstore LANGUAGE plpythonu;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1 id="sql-droptransform-compat">
+ <title>Compatibility</title>
+
+ <para>
+ This form of <command>DROP TRANSFORM</command> is a
+ <productname>PostgreSQL</productname> extension. See <xref
+ linkend="sql-createtransform"> for details.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-createtransform"></member>
+ </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 14e217a..0aac876 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -108,6 +108,7 @@ <title>SQL Commands</title>
&createTSDictionary;
&createTSParser;
&createTSTemplate;
+ &createTransform;
&createTrigger;
&createType;
&createUser;
@@ -148,6 +149,7 @@ <title>SQL Commands</title>
&dropTSDictionary;
&dropTSParser;
&dropTSTemplate;
+ &dropTransform;
&dropTrigger;
&dropType;
&dropUser;
diff --git a/src/Makefile.shlib b/src/Makefile.shlib
index 4e476c3..5ac9f05 100644
--- a/src/Makefile.shlib
+++ b/src/Makefile.shlib
@@ -133,7 +133,7 @@ ifeq ($(PORTNAME), darwin)
else
# loadable module
DLSUFFIX = .so
- LINK.shared = $(COMPILER) -bundle -multiply_defined suppress
+ LINK.shared = $(COMPILER) -bundle -multiply_defined suppress -Wl,-undefined,dynamic_lookup
endif
BUILD.exports = $(AWK) '/^[^\#]/ {printf "_%s\n",$$1}' $< >$@
exports_file = $(SHLIB_EXPORTS:%.txt=%.list)
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..dcb4f24 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,6 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
+ pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index fe17c96..257edc3 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -46,6 +46,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_tablespace.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
@@ -1249,6 +1250,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemoveEventTriggerById(object->objectId);
break;
+ case OCLASS_TRANSFORM:
+ DropTransformById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
@@ -2308,6 +2313,9 @@ getObjectClass(const ObjectAddress *object)
case EventTriggerRelationId:
return OCLASS_EVENT_TRIGGER;
+
+ case TransformRelationId:
+ return OCLASS_TRANSFORM;
}
/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9011190..595a676 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -44,6 +44,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_tablespace.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
@@ -332,6 +333,12 @@ static ObjectPropertyType ObjectProperty[] =
true
},
{
+ TransformRelationId,
+ TransformOidIndexId,
+ TRFOID,
+ InvalidAttrNumber
+ },
+ {
TriggerRelationId,
TriggerOidIndexId,
-1,
@@ -589,6 +596,19 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
address.objectSubId = 0;
}
break;
+ case OBJECT_TRANSFORM:
+ {
+ TypeName *typename = (TypeName *) linitial(objname);
+ char *langname = (char *) linitial(objargs);
+ Oid type_id = typenameTypeId(NULL, typename);
+ Oid lang_id = get_language_oid(langname, false);
+
+ address.classId = TransformRelationId;
+ address.objectId =
+ get_transform_oid(type_id, lang_id, missing_ok);
+ address.objectSubId = 0;
+ }
+ break;
case OBJECT_TSPARSER:
address.classId = TSParserRelationId;
address.objectId = get_ts_parser_oid(objname, missing_ok);
@@ -1234,6 +1254,15 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
format_type_be(targettypeid))));
}
break;
+ case OBJECT_TRANSFORM:
+ {
+ TypeName *typename = (TypeName *) linitial(objname);
+ Oid typeid = typenameTypeId(NULL, typename);
+
+ if (!pg_type_ownercheck(typeid, roleid))
+ aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
+ }
+ break;
case OBJECT_TABLESPACE:
if (!pg_tablespace_ownercheck(address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
@@ -1667,19 +1696,10 @@ getObjectDescription(const ObjectAddress *object)
}
case OCLASS_LANGUAGE:
- {
- HeapTuple langTup;
+ appendStringInfo(&buffer, _("language %s"),
+ get_language_name(object->objectId, false));
+ break;
- langTup = SearchSysCache1(LANGOID,
- ObjectIdGetDatum(object->objectId));
- if (!HeapTupleIsValid(langTup))
- elog(ERROR, "cache lookup failed for language %u",
- object->objectId);
- appendStringInfo(&buffer, _("language %s"),
- NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname));
- ReleaseSysCache(langTup);
- break;
- }
case OCLASS_LARGEOBJECT:
appendStringInfo(&buffer, _("large object %u"),
object->objectId);
@@ -1867,6 +1887,27 @@ getObjectDescription(const ObjectAddress *object)
break;
}
+ case OCLASS_TRANSFORM:
+ {
+ HeapTuple trfTup;
+ Form_pg_transform trfForm;
+
+ trfTup = SearchSysCache1(TRFOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(trfTup))
+ elog(ERROR, "could not find tuple for transform %u",
+ object->objectId);
+
+ trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
+
+ appendStringInfo(&buffer, _("transform for %s language %s"),
+ format_type_be(trfForm->trftype),
+ get_language_name(trfForm->trflang, false));
+
+ ReleaseSysCache(trfTup);
+ break;
+ }
+
case OCLASS_TRIGGER:
{
Relation trigDesc;
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 05a550e..f71ebb5 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -23,7 +23,9 @@
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "commands/defrem.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
@@ -116,6 +118,7 @@ ProcedureCreate(const char *procedureName,
ObjectAddress myself,
referenced;
int i;
+ Oid trfid;
/*
* sanity checks
@@ -624,6 +627,15 @@ ProcedureCreate(const char *procedureName,
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ /* dependency on transform used by return type, if any */
+ if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
+ {
+ referenced.classId = TransformRelationId;
+ referenced.objectId = trfid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
/* dependency on parameter types */
for (i = 0; i < allParamCount; i++)
{
@@ -631,6 +643,15 @@ ProcedureCreate(const char *procedureName,
referenced.objectId = allParams[i];
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* dependency on transform used by parameter type, if any */
+ if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
+ {
+ referenced.classId = TransformRelationId;
+ referenced.objectId = trfid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
}
/* dependency on parameter default expressions */
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index b32ad3a..c63e07b 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -200,6 +200,12 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
args = format_type_be(typenameTypeId(NULL,
(TypeName *) linitial(objargs)));
break;
+ case OBJECT_TRANSFORM:
+ msg = gettext_noop("transform for type %s language %s does not exist, skipping");
+ name = format_type_be(typenameTypeId(NULL,
+ (TypeName *) linitial(objname)));
+ args = (char *) linitial(objargs);
+ break;
case OBJECT_TRIGGER:
msg = gettext_noop("trigger \"%s\" for table \"%s\" does not exist, skipping");
name = strVal(llast(objname));
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 328e2a8..7a53d56 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -92,6 +92,7 @@ static event_trigger_support_data event_trigger_support[] = {
{"SERVER", true},
{"TABLE", true},
{"TABLESPACE", false},
+ {"TRANSFORM", true},
{"TRIGGER", true},
{"TEXT SEARCH CONFIGURATION", true},
{"TEXT SEARCH DICTIONARY", true},
@@ -937,6 +938,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_SCHEMA:
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
+ case OBJECT_TRANSFORM:
case OBJECT_TRIGGER:
case OBJECT_TSCONFIGURATION:
case OBJECT_TSDICTIONARY:
@@ -983,6 +985,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_REWRITE:
case OCLASS_TRIGGER:
case OCLASS_SCHEMA:
+ case OCLASS_TRANSFORM:
case OCLASS_TSPARSER:
case OCLASS_TSDICT:
case OCLASS_TSTEMPLATE:
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index ca754b4..3ab501a 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -45,6 +45,7 @@
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "commands/alter.h"
@@ -1642,6 +1643,293 @@ DropCastById(Oid castOid)
heap_close(relation, RowExclusiveLock);
}
+
+static void
+check_transform_function(Form_pg_proc procstruct)
+{
+ if (procstruct->provolatile == PROVOLATILE_VOLATILE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must not be volatile")));
+ if (procstruct->proisagg)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must not be an aggregate function")));
+ if (procstruct->proiswindow)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must not be a window function")));
+ if (procstruct->proretset)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must not return a set")));
+ if (procstruct->pronargs != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must take one argument")));
+ if (procstruct->proargtypes.values[0] != INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("first argument of transform function must be type \"internal\"")));
+}
+
+
+/*
+ * CREATE TRANSFORM
+ */
+Oid
+CreateTransform(CreateTransformStmt *stmt)
+{
+ Oid typeid;
+ char typtype;
+ Oid langid;
+ Oid fromsqlfuncid;
+ Oid tosqlfuncid;
+ AclResult aclresult;
+ Form_pg_proc procstruct;
+ Datum values[Natts_pg_transform];
+ bool nulls[Natts_pg_transform];
+ bool replaces[Natts_pg_transform];
+ Oid transformid;
+ HeapTuple tuple;
+ HeapTuple newtuple;
+ Relation relation;
+ ObjectAddress myself,
+ referenced;
+ bool is_replace;
+
+ /*
+ * Get the type
+ */
+ typeid = typenameTypeId(NULL, stmt->type_name);
+ typtype = get_typtype(typeid);
+
+ if (typtype == TYPTYPE_PSEUDO)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("data type %s is a pseudo-type",
+ TypeNameToString(stmt->type_name))));
+
+ if (typtype == TYPTYPE_DOMAIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("data type %s is a domain",
+ TypeNameToString(stmt->type_name))));
+
+ if (!pg_type_ownercheck(typeid, GetUserId()))
+ aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
+
+ aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, typeid);
+
+ /*
+ * Get the language
+ */
+ langid = get_language_oid(stmt->lang, false);
+
+ aclresult = pg_language_aclcheck(langid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_LANGUAGE, stmt->lang);
+
+ /*
+ * Get the functions
+ */
+ if (stmt->fromsql)
+ {
+ fromsqlfuncid = LookupFuncNameTypeNames(stmt->fromsql->funcname, stmt->fromsql->funcargs, false);
+
+ if (!pg_proc_ownercheck(fromsqlfuncid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
+
+ aclresult = pg_proc_aclcheck(fromsqlfuncid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
+
+ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fromsqlfuncid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u", fromsqlfuncid);
+ procstruct = (Form_pg_proc) GETSTRUCT(tuple);
+ if (procstruct->prorettype != INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("return data type of FROM SQL function must be \"internal\"")));
+ check_transform_function(procstruct);
+ ReleaseSysCache(tuple);
+ }
+ else
+ fromsqlfuncid = InvalidOid;
+
+ if (stmt->tosql)
+ {
+ tosqlfuncid = LookupFuncNameTypeNames(stmt->tosql->funcname, stmt->tosql->funcargs, false);
+
+ if (!pg_proc_ownercheck(tosqlfuncid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
+
+ aclresult = pg_proc_aclcheck(tosqlfuncid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
+
+ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(tosqlfuncid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u", tosqlfuncid);
+ procstruct = (Form_pg_proc) GETSTRUCT(tuple);
+ if (procstruct->prorettype != typeid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("return data type of TO SQL function must be the transform data type")));
+ check_transform_function(procstruct);
+ ReleaseSysCache(tuple);
+ }
+ else
+ tosqlfuncid = InvalidOid;
+
+ /*
+ * Ready to go
+ */
+ values[Anum_pg_transform_trftype - 1] = ObjectIdGetDatum(typeid);
+ values[Anum_pg_transform_trflang - 1] = ObjectIdGetDatum(langid);
+ values[Anum_pg_transform_trffromsql - 1] = ObjectIdGetDatum(fromsqlfuncid);
+ values[Anum_pg_transform_trftosql - 1] = ObjectIdGetDatum(tosqlfuncid);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ relation = heap_open(TransformRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache2(TRFTYPELANG,
+ ObjectIdGetDatum(typeid),
+ ObjectIdGetDatum(langid));
+ if (HeapTupleIsValid(tuple))
+ {
+ if (!stmt->replace)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("transform for type %s language %s already exists",
+ format_type_be(typeid),
+ stmt->lang)));
+
+ MemSet(replaces, false, sizeof(replaces));
+ replaces[Anum_pg_transform_trffromsql - 1] = true;
+ replaces[Anum_pg_transform_trftosql - 1] = true;
+
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);
+ simple_heap_update(relation, &newtuple->t_self, newtuple);
+
+ transformid = HeapTupleGetOid(tuple);
+ ReleaseSysCache(tuple);
+ is_replace = true;
+ }
+ else
+ {
+ newtuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
+ transformid = simple_heap_insert(relation, newtuple);
+ is_replace = false;
+ }
+
+ CatalogUpdateIndexes(relation, newtuple);
+
+ if (is_replace)
+ deleteDependencyRecordsFor(TransformRelationId, transformid, true);
+
+ /* make dependency entries */
+ myself.classId = TransformRelationId;
+ myself.objectId = transformid;
+ myself.objectSubId = 0;
+
+ /* dependency on language */
+ referenced.classId = LanguageRelationId;
+ referenced.objectId = langid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* dependency on type */
+ referenced.classId = TypeRelationId;
+ referenced.objectId = typeid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* dependencies on functions */
+ if (OidIsValid(fromsqlfuncid))
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = fromsqlfuncid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ if (OidIsValid(tosqlfuncid))
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = tosqlfuncid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, is_replace);
+
+ /* Post creation hook for new transform */
+ InvokeObjectPostCreateHook(TransformRelationId, transformid, 0);
+
+ heap_freetuple(newtuple);
+
+ heap_close(relation, RowExclusiveLock);
+
+ return transformid;
+}
+
+
+/*
+ * get_transform_oid - given type OID and language OID, look up a transform OID
+ *
+ * If missing_ok is false, throw an error if the transform is not found. If
+ * true, just return InvalidOid.
+ */
+Oid
+get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok)
+{
+ Oid oid;
+
+ oid = GetSysCacheOid2(TRFTYPELANG,
+ ObjectIdGetDatum(type_id),
+ ObjectIdGetDatum(lang_id));
+ if (!OidIsValid(oid) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("transform for type %s language \"%s\" does not exist",
+ format_type_be(type_id),
+ get_language_name(lang_id, false))));
+ return oid;
+}
+
+
+void
+DropTransformById(Oid transformOid)
+{
+ Relation relation;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tuple;
+
+ relation = heap_open(TransformRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&scankey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(transformOid));
+ scan = systable_beginscan(relation, TransformOidIndexId, true,
+ NULL, 1, &scankey);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for transform %u", transformOid);
+ simple_heap_delete(relation, &tuple->t_self);
+
+ systable_endscan(scan);
+ heap_close(relation, RowExclusiveLock);
+}
+
+
/*
* Subroutine for ALTER FUNCTION/AGGREGATE SET SCHEMA/RENAME
*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1733da6..5061a73 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3510,6 +3510,20 @@ _copyCreateForeignTableStmt(const CreateForeignTableStmt *from)
return newnode;
}
+static CreateTransformStmt *
+_copyCreateTransformStmt(const CreateTransformStmt *from)
+{
+ CreateTransformStmt *newnode = makeNode(CreateTransformStmt);
+
+ COPY_SCALAR_FIELD(replace);
+ COPY_NODE_FIELD(type_name);
+ COPY_STRING_FIELD(lang);
+ COPY_NODE_FIELD(fromsql);
+ COPY_NODE_FIELD(tosql);
+
+ return newnode;
+}
+
static CreateTrigStmt *
_copyCreateTrigStmt(const CreateTrigStmt *from)
{
@@ -4414,6 +4428,9 @@ copyObject(const void *from)
case T_CreateForeignTableStmt:
retval = _copyCreateForeignTableStmt(from);
break;
+ case T_CreateTransformStmt:
+ retval = _copyCreateTransformStmt(from);
+ break;
case T_CreateTrigStmt:
retval = _copyCreateTrigStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7b29812..d404930 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1740,6 +1740,18 @@ _equalCreateForeignTableStmt(const CreateForeignTableStmt *a, const CreateForeig
}
static bool
+_equalCreateTransformStmt(const CreateTransformStmt *a, const CreateTransformStmt *b)
+{
+ COMPARE_SCALAR_FIELD(replace);
+ COMPARE_NODE_FIELD(type_name);
+ COMPARE_STRING_FIELD(lang);
+ COMPARE_NODE_FIELD(fromsql);
+ COMPARE_NODE_FIELD(tosql);
+
+ return true;
+}
+
+static bool
_equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
{
COMPARE_STRING_FIELD(trigname);
@@ -2882,6 +2894,9 @@ equal(const void *a, const void *b)
case T_CreateForeignTableStmt:
retval = _equalCreateForeignTableStmt(a, b);
break;
+ case T_CreateTransformStmt:
+ retval = _equalCreateTransformStmt(a, b);
+ break;
case T_CreateTrigStmt:
retval = _equalCreateTrigStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 11f6291..c728d6b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,12 +227,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
- CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
+ CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
- DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
+ DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropTransformStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
LockStmt NotifyStmt ExplainableStmt PreparableStmt
@@ -346,6 +346,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_enum_val_list enum_val_list table_func_column_list
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
+ transform_element_list
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -583,12 +584,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
- SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
+ SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP
- TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P
+ TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
TRUNCATE TRUSTED TYPE_P TYPES_P
UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
@@ -757,6 +758,7 @@ stmt :
| CreateSeqStmt
| CreateStmt
| CreateTableSpaceStmt
+ | CreateTransformStmt
| CreateTrigStmt
| CreateEventTrigStmt
| CreateRoleStmt
@@ -781,6 +783,7 @@ stmt :
| DropRuleStmt
| DropStmt
| DropTableSpaceStmt
+ | DropTransformStmt
| DropTrigStmt
| DropRoleStmt
| DropUserStmt
@@ -3910,6 +3913,16 @@ AlterExtensionContentsStmt:
n->objname = list_make1(makeString($6));
$$ = (Node *)n;
}
+ | ALTER EXTENSION name add_drop TRANSFORM FOR Typename LANGUAGE name
+ {
+ AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
+ n->extname = $3;
+ n->action = $4;
+ n->objtype = OBJECT_TRANSFORM;
+ n->objname = list_make1($7);
+ n->objargs = list_make1($9);
+ $$ = (Node *)n;
+ }
| ALTER EXTENSION name add_drop TYPE_P any_name
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
@@ -5333,6 +5346,15 @@ CommentStmt:
n->comment = $6;
$$ = (Node *) n;
}
+ | COMMENT ON TRANSFORM FOR Typename LANGUAGE name IS comment_text
+ {
+ CommentStmt *n = makeNode(CommentStmt);
+ n->objtype = OBJECT_TRANSFORM;
+ n->objname = list_make1($5);
+ n->objargs = list_make1($7);
+ n->comment = $9;
+ $$ = (Node *) n;
+ }
| COMMENT ON TRIGGER name ON any_name IS comment_text
{
CommentStmt *n = makeNode(CommentStmt);
@@ -6825,6 +6847,56 @@ opt_if_exists: IF_P EXISTS { $$ = TRUE; }
/*****************************************************************************
*
+ * CREATE TRANSFORM / DROP TRANSFORM
+ *
+ *****************************************************************************/
+
+CreateTransformStmt: CREATE opt_or_replace TRANSFORM FOR Typename LANGUAGE name '(' transform_element_list ')'
+ {
+ CreateTransformStmt *n = makeNode(CreateTransformStmt);
+ n->replace = $2;
+ n->type_name = $5;
+ n->lang = $7;
+ n->fromsql = linitial($9);
+ n->tosql = lsecond($9);
+ $$ = (Node *)n;
+ }
+ ;
+
+transform_element_list: FROM SQL_P WITH FUNCTION function_with_argtypes ',' TO SQL_P WITH FUNCTION function_with_argtypes
+ {
+ $$ = list_make2($5, $11);
+ }
+ | TO SQL_P WITH FUNCTION function_with_argtypes ',' FROM SQL_P WITH FUNCTION function_with_argtypes
+ {
+ $$ = list_make2($11, $5);
+ }
+ | FROM SQL_P WITH FUNCTION function_with_argtypes
+ {
+ $$ = list_make2($5, NULL);
+ }
+ | TO SQL_P WITH FUNCTION function_with_argtypes
+ {
+ $$ = list_make2(NULL, $5);
+ }
+ ;
+
+
+DropTransformStmt: DROP TRANSFORM opt_if_exists FOR Typename LANGUAGE name opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_TRANSFORM;
+ n->objects = list_make1(list_make1($5));
+ n->arguments = list_make1(list_make1($7));
+ n->behavior = $8;
+ n->missing_ok = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
+
+/*****************************************************************************
+ *
* QUERY:
*
* REINDEX type <name> [FORCE]
@@ -12702,6 +12774,7 @@ unreserved_keyword:
| SHOW
| SIMPLE
| SNAPSHOT
+ | SQL_P
| STABLE
| STANDALONE_P
| START
@@ -12721,6 +12794,7 @@ unreserved_keyword:
| TEMPORARY
| TEXT_P
| TRANSACTION
+ | TRANSFORM
| TRIGGER
| TRUNCATE
| TRUSTED
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6a7bf0d..3257d57 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -214,6 +214,7 @@ check_xact_readonly(Node *parsetree)
case T_CreateTableAsStmt:
case T_RefreshMatViewStmt:
case T_CreateTableSpaceStmt:
+ case T_CreateTransformStmt:
case T_CreateTrigStmt:
case T_CompositeTypeStmt:
case T_CreateEnumStmt:
@@ -1292,6 +1293,10 @@ ProcessUtilitySlow(Node *parsetree,
DefineOpFamily((CreateOpFamilyStmt *) parsetree);
break;
+ case T_CreateTransformStmt:
+ CreateTransform((CreateTransformStmt *) parsetree);
+ break;
+
case T_AlterOpFamilyStmt:
AlterOpFamily((AlterOpFamilyStmt *) parsetree);
break;
@@ -1948,6 +1953,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_OPFAMILY:
tag = "DROP OPERATOR FAMILY";
break;
+ case OBJECT_TRANSFORM:
+ tag = "DROP TRANSFORM";
+ break;
default:
tag = "???";
}
@@ -2199,6 +2207,10 @@ CreateCommandTag(Node *parsetree)
}
break;
+ case T_CreateTransformStmt:
+ tag = "CREATE TRANSFORM";
+ break;
+
case T_CreateTrigStmt:
tag = "CREATE TRIGGER";
break;
@@ -2814,6 +2826,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
+ case T_CreateTransformStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_AlterOpFamilyStmt:
lev = LOGSTMT_DDL;
break;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 915fb7a..f026598 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1869,9 +1869,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
StringInfoData buf;
StringInfoData dq;
HeapTuple proctup;
- HeapTuple langtup;
Form_pg_proc proc;
- Form_pg_language lang;
Datum tmp;
bool isnull;
const char *prosrc;
@@ -1894,12 +1892,6 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an aggregate function", name)));
- /* Need its pg_language tuple for the language name */
- langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
- if (!HeapTupleIsValid(langtup))
- elog(ERROR, "cache lookup failed for language %u", proc->prolang);
- lang = (Form_pg_language) GETSTRUCT(langtup);
-
/*
* We always qualify the function name, to ensure the right function gets
* replaced.
@@ -1911,7 +1903,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendStringInfoString(&buf, ")\n RETURNS ");
print_function_rettype(&buf, proctup);
appendStringInfo(&buf, "\n LANGUAGE %s\n",
- quote_identifier(NameStr(lang->lanname)));
+ quote_identifier(get_language_name(proc->prolang, false)));
/* Emit some miscellaneous options on one line */
oldlen = buf.len;
@@ -2031,7 +2023,6 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendStringInfoChar(&buf, '\n');
- ReleaseSysCache(langtup);
ReleaseSysCache(proctup);
PG_RETURN_TEXT_P(string_to_text(buf.data));
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 5865962..48e14ee 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -23,12 +23,14 @@
#include "catalog/pg_amproc.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
+#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_statistic.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -1032,6 +1034,30 @@ get_constraint_name(Oid conoid)
return NULL;
}
+/* ---------- LANGUAGE CACHE ---------- */
+
+char *
+get_language_name(Oid langoid, bool missing_ok)
+{
+ HeapTuple tp;
+
+ tp = SearchSysCache1(LANGOID, ObjectIdGetDatum(langoid));
+ if (HeapTupleIsValid(tp))
+ {
+ Form_pg_language lantup = (Form_pg_language) GETSTRUCT(tp);
+ char *result;
+
+ result = pstrdup(NameStr(lantup->lanname));
+ ReleaseSysCache(tp);
+ return result;
+ }
+
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for language %u",
+ langoid);
+ return NULL;
+}
+
/* ---------- OPCLASS CACHE ---------- */
/*
@@ -1779,6 +1805,45 @@ get_rel_tablespace(Oid relid)
}
+/* ---------- TRANSFORM CACHE ---------- */
+
+Oid
+get_transform_fromsql(Oid typid, Oid langid)
+{
+ HeapTuple tup;
+
+ tup = SearchSysCache2(TRFTYPELANG, typid, langid);
+ if (HeapTupleIsValid(tup))
+ {
+ Oid funcid;
+
+ funcid = ((Form_pg_transform) GETSTRUCT(tup))->trffromsql;
+ ReleaseSysCache(tup);
+ return funcid;
+ }
+ else
+ return InvalidOid;
+}
+
+Oid
+get_transform_tosql(Oid typid, Oid langid)
+{
+ HeapTuple tup;
+
+ tup = SearchSysCache2(TRFTYPELANG, typid, langid);
+ if (HeapTupleIsValid(tup))
+ {
+ Oid funcid;
+
+ funcid = ((Form_pg_transform) GETSTRUCT(tup))->trftosql;
+ ReleaseSysCache(tup);
+ return funcid;
+ }
+ else
+ return InvalidOid;
+}
+
+
/* ---------- TYPE CACHE ---------- */
/*
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e9bdfea..45c2ef9 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -56,6 +56,7 @@
#include "catalog/pg_shseclabel.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_config_map.h"
#include "catalog/pg_ts_dict.h"
@@ -653,6 +654,28 @@ static const struct cachedesc cacheinfo[] = {
},
4
},
+ {TransformRelationId, /* TRFOID */
+ TransformOidIndexId,
+ 1,
+ {
+ ObjectIdAttributeNumber,
+ 0,
+ 0,
+ 0,
+ },
+ 16
+ },
+ {TransformRelationId, /* TRFTYPELANG */
+ TransformTypeLangIndexId,
+ 2,
+ {
+ Anum_pg_transform_trftype,
+ Anum_pg_transform_trflang,
+ 0,
+ 0,
+ },
+ 16
+ },
{TSConfigMapRelationId, /* TSCONFIGMAP */
TSConfigMapIndexId,
3,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 247ad92..da4dd57 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -89,6 +89,7 @@ getSchemaData(Archive *fout, int *numTablesPtr)
int numRules;
int numProcLangs;
int numCasts;
+ int numTransforms;
int numOpclasses;
int numOpfamilies;
int numConversions;
@@ -199,6 +200,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
getCasts(fout, &numCasts);
if (g_verbose)
+ write_msg(NULL, "reading transforms\n");
+ getTransforms(fout, &numTransforms);
+
+ if (g_verbose)
write_msg(NULL, "reading table inheritance information\n");
inhinfo = getInherits(fout, &numInherits);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 769058d..e5e1c32 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -182,6 +182,7 @@ static void dumpShellType(Archive *fout, ShellTypeInfo *stinfo);
static void dumpProcLang(Archive *fout, ProcLangInfo *plang);
static void dumpFunc(Archive *fout, FuncInfo *finfo);
static void dumpCast(Archive *fout, CastInfo *cast);
+static void dumpTransform(Archive *fout, TransformInfo *transform);
static void dumpOpr(Archive *fout, OprInfo *oprinfo);
static void dumpOpclass(Archive *fout, OpclassInfo *opcinfo);
static void dumpOpfamily(Archive *fout, OpfamilyInfo *opfinfo);
@@ -6184,6 +6185,110 @@ getCasts(Archive *fout, int *numCasts)
return castinfo;
}
+static char *
+get_language_name(Archive *fout, Oid langid)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ char *lanname;
+
+ query = createPQExpBuffer();
+ appendPQExpBuffer(query, "SELECT lanname FROM pg_language WHERE oid = %u", langid);
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+ lanname = pg_strdup(fmtId(PQgetvalue(res, 0, 0)));
+ destroyPQExpBuffer(query);
+ PQclear(res);
+
+ return lanname;
+}
+
+/*
+ * getTransforms
+ * get basic information about every transform in the system
+ *
+ * numTransforms is set to the number of transforms read in
+ */
+TransformInfo *
+getTransforms(Archive *fout, int *numTransforms)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ TransformInfo *transforminfo;
+ int i_tableoid;
+ int i_oid;
+ int i_trftype;
+ int i_trflang;
+ int i_trffromsql;
+ int i_trftosql;
+
+ /* Transforms didn't exist pre-9.4 */
+ if (fout->remoteVersion < 90400)
+ {
+ *numTransforms = 0;
+ return NULL;
+ }
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, "
+ "trftype, trflang, trffromsql::oid, trftosql::oid "
+ "FROM pg_transform "
+ "ORDER BY 3,4");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numTransforms = ntups;
+
+ transforminfo = (TransformInfo *) pg_malloc(ntups * sizeof(TransformInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_trftype = PQfnumber(res, "trftype");
+ i_trflang = PQfnumber(res, "trflang");
+ i_trffromsql = PQfnumber(res, "trffromsql");
+ i_trftosql = PQfnumber(res, "trftosql");
+
+ for (i = 0; i < ntups; i++)
+ {
+ PQExpBufferData namebuf;
+ TypeInfo *typeInfo;
+ char *lanname;
+
+ transforminfo[i].dobj.objType = DO_TRANSFORM;
+ transforminfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ transforminfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&transforminfo[i].dobj);
+ transforminfo[i].trftype = atooid(PQgetvalue(res, i, i_trftype));
+ transforminfo[i].trflang = atooid(PQgetvalue(res, i, i_trflang));
+ transforminfo[i].trffromsql = atooid(PQgetvalue(res, i, i_trffromsql));
+ transforminfo[i].trftosql = atooid(PQgetvalue(res, i, i_trftosql));
+
+ /*
+ * Try to name transform as concatenation of type and language name.
+ * This is only used for purposes of sorting. If we fail to find
+ * either, the name will be an empty string.
+ */
+ initPQExpBuffer(&namebuf);
+ typeInfo = findTypeByOid(transforminfo[i].trftype);
+ lanname = get_language_name(fout, transforminfo[i].trflang);
+ if (typeInfo && lanname)
+ appendPQExpBuffer(&namebuf, "%s %s",
+ typeInfo->dobj.name, lanname);
+ transforminfo[i].dobj.name = namebuf.data;
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return transforminfo;
+}
+
/*
* getTableAttrs -
* for each interesting table, read info about its attributes
@@ -7794,6 +7899,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
case DO_CAST:
dumpCast(fout, (CastInfo *) dobj);
break;
+ case DO_TRANSFORM:
+ dumpTransform(fout, (TransformInfo *) dobj);
+ break;
case DO_TABLE_DATA:
if (((TableDataInfo *) dobj)->tdtable->relkind == RELKIND_SEQUENCE)
dumpSequenceData(fout, (TableDataInfo *) dobj);
@@ -10190,6 +10298,127 @@ dumpCast(Archive *fout, CastInfo *cast)
}
/*
+ * Dump a transform
+ */
+static void
+dumpTransform(Archive *fout, TransformInfo *transform)
+{
+ PQExpBuffer defqry;
+ PQExpBuffer delqry;
+ PQExpBuffer labelq;
+ FuncInfo *fromsqlFuncInfo = NULL;
+ FuncInfo *tosqlFuncInfo = NULL;
+ char *lanname;
+
+ /* Skip if not to be dumped */
+ if (!transform->dobj.dump || dataOnly)
+ return;
+
+ /* Cannot dump if we don't have the transform functions' info */
+ if (OidIsValid(transform->trffromsql))
+ {
+ fromsqlFuncInfo = findFuncByOid(transform->trffromsql);
+ if (fromsqlFuncInfo == NULL)
+ return;
+ }
+ if (OidIsValid(transform->trftosql))
+ {
+ tosqlFuncInfo = findFuncByOid(transform->trftosql);
+ if (tosqlFuncInfo == NULL)
+ return;
+ }
+
+ /* Make sure we are in proper schema (needed for getFormattedTypeName) */
+ selectSourceSchema(fout, "pg_catalog");
+
+ defqry = createPQExpBuffer();
+ delqry = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ lanname = get_language_name(fout, transform->trflang);
+
+ appendPQExpBuffer(delqry, "DROP TRANSFORM FOR %s LANGUAGE %s;\n",
+ getFormattedTypeName(fout, transform->trftype, zeroAsNone),
+ lanname);
+
+ appendPQExpBuffer(defqry, "CREATE TRANSFORM FOR %s LANGUAGE %s (",
+ getFormattedTypeName(fout, transform->trftype, zeroAsNone),
+ lanname);
+
+ if (!transform->trffromsql && !transform->trftosql)
+ write_msg(NULL, "WARNING: bogus transform definition, at least one of trffromsql and trftosql should be nonzero\n");
+
+ if (transform->trffromsql)
+ {
+ if (fromsqlFuncInfo)
+ {
+ char *fsig = format_function_signature(fout, fromsqlFuncInfo, true);
+
+ /*
+ * Always qualify the function name, in case it is not in
+ * pg_catalog schema (format_function_signature won't qualify
+ * it).
+ */
+ appendPQExpBuffer(defqry, "FROM SQL WITH FUNCTION %s.%s",
+ fmtId(fromsqlFuncInfo->dobj.namespace->dobj.name), fsig);
+ free(fsig);
+ }
+ else
+ write_msg(NULL, "WARNING: bogus value in pg_transform.trffromsql field\n");
+ }
+
+ if (transform->trftosql)
+ {
+ if (transform->trffromsql)
+ appendPQExpBuffer(defqry, ", ");
+
+ if (tosqlFuncInfo)
+ {
+ char *fsig = format_function_signature(fout, tosqlFuncInfo, true);
+
+ /*
+ * Always qualify the function name, in case it is not in
+ * pg_catalog schema (format_function_signature won't qualify
+ * it).
+ */
+ appendPQExpBuffer(defqry, "TO SQL WITH FUNCTION %s.%s",
+ fmtId(tosqlFuncInfo->dobj.namespace->dobj.name), fsig);
+ free(fsig);
+ }
+ else
+ write_msg(NULL, "WARNING: bogus value in pg_transform.trftosql field\n");
+ }
+
+ appendPQExpBuffer(defqry, ");\n");
+
+ appendPQExpBuffer(labelq, "TRANSFORM FOR %s LANGUAGE %s",
+ getFormattedTypeName(fout, transform->trftype, zeroAsNone),
+ lanname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(defqry, &transform->dobj, labelq->data);
+
+ ArchiveEntry(fout, transform->dobj.catId, transform->dobj.dumpId,
+ labelq->data,
+ "pg_catalog", NULL, "",
+ false, "TRANSFORM", SECTION_PRE_DATA,
+ defqry->data, delqry->data, NULL,
+ transform->dobj.dependencies, transform->dobj.nDeps,
+ NULL, NULL);
+
+ /* Dump Transform Comments */
+ dumpComment(fout, labelq->data,
+ NULL, "",
+ transform->dobj.catId, 0, transform->dobj.dumpId);
+
+ free(lanname);
+ destroyPQExpBuffer(defqry);
+ destroyPQExpBuffer(delqry);
+ destroyPQExpBuffer(labelq);
+}
+
+
+/*
* dumpOpr
* write out a single operator definition
*/
@@ -15063,6 +15292,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_TSCONFIG:
case DO_FDW:
case DO_FOREIGN_SERVER:
+ case DO_TRANSFORM:
case DO_BLOB:
/* Pre-data objects: must come before the pre-data boundary */
addObjectDependency(preDataBound, dobj->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 915e82c..8483a17 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -106,6 +106,7 @@ typedef enum
DO_FDW,
DO_FOREIGN_SERVER,
DO_DEFAULT_ACL,
+ DO_TRANSFORM,
DO_BLOB,
DO_BLOB_DATA,
DO_PRE_DATA_BOUNDARY,
@@ -409,6 +410,15 @@ typedef struct _castInfo
char castmethod;
} CastInfo;
+typedef struct _transformInfo
+{
+ DumpableObject dobj;
+ Oid trftype;
+ Oid trflang;
+ Oid trffromsql;
+ Oid trftosql;
+} TransformInfo;
+
/* InhInfo isn't a DumpableObject, just temporary state */
typedef struct _inhInfo
{
@@ -561,6 +571,7 @@ extern RuleInfo *getRules(Archive *fout, int *numRules);
extern void getTriggers(Archive *fout, TableInfo tblinfo[], int numTables);
extern ProcLangInfo *getProcLangs(Archive *fout, int *numProcLangs);
extern CastInfo *getCasts(Archive *fout, int *numCasts);
+extern TransformInfo *getTransforms(Archive *fout, int *numTransforms);
extern void getTableAttrs(Archive *fout, TableInfo *tbinfo, int numTables);
extern bool shouldPrintColumn(TableInfo *tbinfo, int colno);
extern TSParserInfo *getTSParsers(Archive *fout, int *numTSParsers);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 141e713..9d0a6d1 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -26,8 +26,8 @@ static const char *modulename = gettext_noop("sorter");
* by OID. (This is a relatively crude hack to provide semi-reasonable
* behavior for old databases without full dependency info.) Note: collations,
* extensions, text search, foreign-data, materialized view, event trigger,
- * and default ACL objects can't really happen here, so the rather bogus
- * priorities for them don't matter.
+ * transforms, and default ACL objects can't really happen here, so the rather
+ * bogus priorities for them don't matter.
*
* NOTE: object-type priorities must match the section assignments made in
* pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY,
@@ -65,6 +65,7 @@ static const int oldObjectTypePriority[] =
4, /* DO_FDW */
4, /* DO_FOREIGN_SERVER */
19, /* DO_DEFAULT_ACL */
+ 4, /* DO_TRANSFORM */
9, /* DO_BLOB */
12, /* DO_BLOB_DATA */
10, /* DO_PRE_DATA_BOUNDARY */
@@ -113,6 +114,7 @@ static const int newObjectTypePriority[] =
16, /* DO_FDW */
17, /* DO_FOREIGN_SERVER */
31, /* DO_DEFAULT_ACL */
+ 3, /* DO_TRANSFORM */
21, /* DO_BLOB */
24, /* DO_BLOB_DATA */
22, /* DO_PRE_DATA_BOUNDARY */
@@ -1287,6 +1289,13 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
((CastInfo *) obj)->casttarget,
obj->dumpId, obj->catId.oid);
return;
+ case DO_TRANSFORM:
+ snprintf(buf, bufsize,
+ "TRANSFORM %u lang %u (ID %d OID %u)",
+ ((TransformInfo *) obj)->trftype,
+ ((TransformInfo *) obj)->trflang,
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_TABLE_DATA:
snprintf(buf, bufsize,
"TABLE DATA %s (ID %d OID %u)",
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 4108f6c..292863a 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201311081
+#define CATALOG_VERSION_NO 201311102
#endif
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3aefbb5e..404fda4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
OCLASS_DEFACL, /* pg_default_acl */
OCLASS_EXTENSION, /* pg_extension */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
+ OCLASS_TRANSFORM, /* pg_transform */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 4860e98..a98b768 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -228,6 +228,11 @@ DECLARE_UNIQUE_INDEX(pg_tablespace_oid_index, 2697, on pg_tablespace using btree
DECLARE_UNIQUE_INDEX(pg_tablespace_spcname_index, 2698, on pg_tablespace using btree(spcname name_ops));
#define TablespaceNameIndexId 2698
+DECLARE_UNIQUE_INDEX(pg_transform_oid_index, 3780, on pg_transform using btree(oid oid_ops));
+#define TransformOidIndexId 3780
+DECLARE_UNIQUE_INDEX(pg_transform_type_lang_index, 3781, on pg_transform using btree(trftype oid_ops, trflang oid_ops));
+#define TransformTypeLangIndexId 3781
+
/* This following index is not used for a cache and is not unique */
DECLARE_INDEX(pg_trigger_tgconstraint_index, 2699, on pg_trigger using btree(tgconstraint oid_ops));
#define TriggerConstraintIndexId 2699
diff --git a/src/include/catalog/pg_transform.h b/src/include/catalog/pg_transform.h
new file mode 100644
index 0000000..80b191e
--- /dev/null
+++ b/src/include/catalog/pg_transform.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_transform.h
+ *
+ * Copyright (c) 2012, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/pg_transform.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_TRANSFORM_H
+#define PG_TRANSFORM_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_transform definition. cpp turns this into
+ * typedef struct FormData_pg_transform
+ * ----------------
+ */
+#define TransformRelationId 3779
+
+CATALOG(pg_transform,3779)
+{
+ Oid trftype;
+ Oid trflang;
+ regproc trffromsql;
+ regproc trftosql;
+} FormData_pg_transform;
+
+typedef FormData_pg_transform *Form_pg_transform;
+
+/* ----------------
+ * compiler constants for pg_transform
+ * ----------------
+ */
+#define Natts_pg_transform 4
+#define Anum_pg_transform_trftype 1
+#define Anum_pg_transform_trflang 2
+#define Anum_pg_transform_trffromsql 3
+#define Anum_pg_transform_trftosql 4
+
+#endif /* PG_TRANSFORM_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 836c99e..cddd431 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -50,10 +50,13 @@ extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
extern Oid AlterFunction(AlterFunctionStmt *stmt);
extern Oid CreateCast(CreateCastStmt *stmt);
extern void DropCastById(Oid castOid);
+extern Oid CreateTransform(CreateTransformStmt *stmt);
+extern void DropTransformById(Oid transformOid);
extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
oidvector *proargtypes, Oid nspOid);
extern void ExecuteDoStmt(DoStmt *stmt);
extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok);
+extern Oid get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok);
extern void interpret_function_parameter_list(List *parameters,
Oid languageOid,
bool is_aggregate,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fc6b1d7..6131159 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -363,6 +363,7 @@ typedef enum NodeTag
T_AlterEventTrigStmt,
T_RefreshMatViewStmt,
T_ReplicaIdentityStmt,
+ T_CreateTransformStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 55524b4..63f3e09 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1184,6 +1184,7 @@ typedef enum ObjectType
OBJECT_SEQUENCE,
OBJECT_TABLE,
OBJECT_TABLESPACE,
+ OBJECT_TRANSFORM,
OBJECT_TRIGGER,
OBJECT_TSCONFIGURATION,
OBJECT_TSDICTIONARY,
@@ -2627,6 +2628,20 @@ typedef struct CreateCastStmt
} CreateCastStmt;
/* ----------------------
+ * CREATE TRANSFORM Statement
+ * ----------------------
+ */
+typedef struct CreateTransformStmt
+{
+ NodeTag type;
+ bool replace;
+ TypeName *type_name;
+ char *lang;
+ FuncWithArgs *fromsql;
+ FuncWithArgs *tosql;
+} CreateTransformStmt;
+
+/* ----------------------
* PREPARE Statement
* ----------------------
*/
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8bd34d6..e8bb988 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -347,6 +347,7 @@ PG_KEYWORD("simple", SIMPLE, UNRESERVED_KEYWORD)
PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD)
PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD)
PG_KEYWORD("some", SOME, RESERVED_KEYWORD)
+PG_KEYWORD("sql", SQL_P, UNRESERVED_KEYWORD)
PG_KEYWORD("stable", STABLE, UNRESERVED_KEYWORD)
PG_KEYWORD("standalone", STANDALONE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("start", START, UNRESERVED_KEYWORD)
@@ -374,6 +375,7 @@ PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD)
PG_KEYWORD("to", TO, RESERVED_KEYWORD)
PG_KEYWORD("trailing", TRAILING, RESERVED_KEYWORD)
PG_KEYWORD("transaction", TRANSACTION, UNRESERVED_KEYWORD)
+PG_KEYWORD("transform", TRANSFORM, UNRESERVED_KEYWORD)
PG_KEYWORD("treat", TREAT, COL_NAME_KEYWORD)
PG_KEYWORD("trigger", TRIGGER, UNRESERVED_KEYWORD)
PG_KEYWORD("trim", TRIM, COL_NAME_KEYWORD)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 49f459a..85c5a6a 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -72,6 +72,7 @@ extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum,
Oid *typid, int32 *typmod, Oid *collid);
extern char *get_collation_name(Oid colloid);
extern char *get_constraint_name(Oid conoid);
+extern char *get_language_name(Oid langoid, bool missing_ok);
extern Oid get_opclass_family(Oid opclass);
extern Oid get_opclass_input_type(Oid opclass);
extern RegProcedure get_opcode(Oid opno);
@@ -102,6 +103,8 @@ extern Oid get_rel_namespace(Oid relid);
extern Oid get_rel_type_id(Oid relid);
extern char get_rel_relkind(Oid relid);
extern Oid get_rel_tablespace(Oid relid);
+extern Oid get_transform_fromsql(Oid typid, Oid langid);
+extern Oid get_transform_tosql(Oid typid, Oid langid);
extern bool get_typisdefined(Oid typid);
extern int16 get_typlen(Oid typid);
extern bool get_typbyval(Oid typid);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e41b3d2..bad0fd9 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -80,6 +80,8 @@ enum SysCacheIdentifier
RULERELNAME,
STATRELATTINH,
TABLESPACEOID,
+ TRFOID,
+ TRFTYPELANG,
TSCONFIGMAP,
TSCONFIGNAMENSP,
TSCONFIGOID,
diff --git a/src/interfaces/ecpg/preproc/ecpg.tokens b/src/interfaces/ecpg/preproc/ecpg.tokens
index b55138a..68ba925 100644
--- a/src/interfaces/ecpg/preproc/ecpg.tokens
+++ b/src/interfaces/ecpg/preproc/ecpg.tokens
@@ -12,7 +12,7 @@
SQL_LONG SQL_NULLABLE SQL_OCTET_LENGTH
SQL_OPEN SQL_OUTPUT SQL_REFERENCE
SQL_RETURNED_LENGTH SQL_RETURNED_OCTET_LENGTH SQL_SCALE
- SQL_SECTION SQL_SHORT SQL_SIGNED SQL_SQL SQL_SQLERROR
+ SQL_SECTION SQL_SHORT SQL_SIGNED SQL_SQLERROR
SQL_SQLPRINT SQL_SQLWARNING SQL_START SQL_STOP
SQL_STRUCT SQL_UNSIGNED SQL_VAR SQL_WHENEVER
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 58155ab..c91b8b0 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -1000,7 +1000,7 @@ ecpg_using: USING using_list { $$ = EMPTY; }
| using_descriptor { $$ = $1; }
;
-using_descriptor: USING SQL_SQL SQL_DESCRIPTOR quoted_ident_stringvar
+using_descriptor: USING SQL_P SQL_DESCRIPTOR quoted_ident_stringvar
{
add_variable_to_head(&argsinsert, descriptor_variable($4,0), &no_indicator);
$$ = EMPTY;
@@ -1012,7 +1012,7 @@ using_descriptor: USING SQL_SQL SQL_DESCRIPTOR quoted_ident_stringvar
}
;
-into_descriptor: INTO SQL_SQL SQL_DESCRIPTOR quoted_ident_stringvar
+into_descriptor: INTO SQL_P SQL_DESCRIPTOR quoted_ident_stringvar
{
add_variable_to_head(&argsresult, descriptor_variable($4,1), &no_indicator);
$$ = EMPTY;
@@ -1489,7 +1489,6 @@ ECPGKeywords_vanames: SQL_BREAK { $$ = mm_strdup("break"); }
| SQL_RETURNED_OCTET_LENGTH { $$ = mm_strdup("returned_octet_length"); }
| SQL_SCALE { $$ = mm_strdup("scale"); }
| SQL_SECTION { $$ = mm_strdup("section"); }
- | SQL_SQL { $$ = mm_strdup("sql"); }
| SQL_SQLERROR { $$ = mm_strdup("sqlerror"); }
| SQL_SQLPRINT { $$ = mm_strdup("sqlprint"); }
| SQL_SQLWARNING { $$ = mm_strdup("sqlwarning"); }
diff --git a/src/interfaces/ecpg/preproc/ecpg_keywords.c b/src/interfaces/ecpg/preproc/ecpg_keywords.c
index fb54d7b..6c819fd 100644
--- a/src/interfaces/ecpg/preproc/ecpg_keywords.c
+++ b/src/interfaces/ecpg/preproc/ecpg_keywords.c
@@ -67,8 +67,6 @@ static const ScanKeyword ECPGScanKeywords[] = {
{"section", SQL_SECTION, 0},
{"short", SQL_SHORT, 0},
{"signed", SQL_SIGNED, 0},
- {"sql", SQL_SQL, 0}, /* strange thing, used for into sql descriptor
- * MYDESC; */
{"sqlerror", SQL_SQLERROR, 0},
{"sqlprint", SQL_SQLPRINT, 0},
{"sqlwarning", SQL_SQLWARNING, 0},
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index e0e31ec..49a4ed8 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -79,15 +79,17 @@ Util.c: Util.xs plperl_helpers.h
install: all install-lib install-data
installdirs: installdirs-lib
- $(MKDIR_P) '$(DESTDIR)$(datadir)/extension'
+ $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)'
uninstall: uninstall-lib uninstall-data
install-data: installdirs
$(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
+ $(INSTALL_DATA) $(srcdir)/plperl.h $(srcdir)/ppport.h '$(DESTDIR)$(includedir_server)'
uninstall-data:
rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA)))
+ rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, plperl.h ppport.h)
.PHONY: install-data uninstall-data
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index de8cb0e..851a41f 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -110,6 +110,7 @@ typedef struct plperl_proc_desc
SV *reference; /* CODE reference for Perl sub */
plperl_interp_desc *interp; /* interpreter it's created in */
bool fn_readonly; /* is function readonly (not volatile)? */
+ Oid lang_oid;
bool lanpltrusted; /* is it plperl, rather than plperlu? */
bool fn_retistuple; /* true, if function returns tuple */
bool fn_retisset; /* true, if function returns set */
@@ -210,6 +211,7 @@ typedef struct plperl_array_info
bool *nulls;
int *nelems;
FmgrInfo proc;
+ FmgrInfo transform_proc;
} plperl_array_info;
/**********************************************************************
@@ -1260,6 +1262,7 @@ plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
bool *isnull)
{
FmgrInfo tmp;
+ Oid funcid;
/* we might recurse */
check_stack_depth();
@@ -1283,6 +1286,8 @@ plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
/* must call typinput in case it wants to reject NULL */
return InputFunctionCall(finfo, NULL, typioparam, typmod);
}
+ else if ((funcid = get_transform_tosql(typid, current_call_data->prodesc->lang_oid)))
+ return OidFunctionCall1(funcid, PointerGetDatum(sv));
else if (SvROK(sv))
{
/* handle references */
@@ -1395,6 +1400,7 @@ plperl_ref_from_pg_array(Datum arg, Oid typid)
typdelim;
Oid typioparam;
Oid typoutputfunc;
+ Oid transform_funcid;
int i,
nitems,
*dims;
@@ -1402,14 +1408,17 @@ plperl_ref_from_pg_array(Datum arg, Oid typid)
SV *av;
HV *hv;
- info = palloc(sizeof(plperl_array_info));
+ info = palloc0(sizeof(plperl_array_info));
/* get element type information, including output conversion function */
get_type_io_data(elementtype, IOFunc_output,
&typlen, &typbyval, &typalign,
&typdelim, &typioparam, &typoutputfunc);
- perm_fmgr_info(typoutputfunc, &info->proc);
+ if ((transform_funcid = get_transform_fromsql(elementtype, current_call_data->prodesc->lang_oid)))
+ perm_fmgr_info(transform_funcid, &info->transform_proc);
+ else
+ perm_fmgr_info(typoutputfunc, &info->proc);
info->elem_is_rowtype = type_is_rowtype(elementtype);
@@ -1490,8 +1499,10 @@ make_array_ref(plperl_array_info *info, int first, int last)
{
Datum itemvalue = info->elements[i];
- /* Handle composite type elements */
- if (info->elem_is_rowtype)
+ if (info->transform_proc.fn_oid)
+ av_push(result, (SV *) DatumGetPointer(FunctionCall1(&info->transform_proc, itemvalue)));
+ else if (info->elem_is_rowtype)
+ /* Handle composite type elements */
av_push(result, plperl_hash_from_datum(itemvalue));
else
{
@@ -1778,6 +1789,7 @@ plperl_inline_handler(PG_FUNCTION_ARGS)
desc.proname = "inline_code_block";
desc.fn_readonly = false;
+ desc.lang_oid = codeblock->langOid;
desc.lanpltrusted = codeblock->langIsTrusted;
desc.fn_retistuple = false;
@@ -2035,6 +2047,8 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
SV *retval;
int i;
int count;
+ Oid *argtypes = NULL;
+ int nargs = 0;
ENTER;
SAVETMPS;
@@ -2042,6 +2056,9 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
PUSHMARK(SP);
EXTEND(sp, desc->nargs);
+ if (fcinfo->flinfo->fn_oid)
+ get_func_signature(fcinfo->flinfo->fn_oid, &argtypes, &nargs);
+
for (i = 0; i < desc->nargs; i++)
{
if (fcinfo->argnull[i])
@@ -2055,9 +2072,12 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
else
{
SV *sv;
+ Oid funcid;
if (OidIsValid(desc->arg_arraytype[i]))
sv = plperl_ref_from_pg_array(fcinfo->arg[i], desc->arg_arraytype[i]);
+ else if ((funcid = get_transform_fromsql(argtypes[i], current_call_data->prodesc->lang_oid)))
+ sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, fcinfo->arg[i]));
else
{
char *tmp;
@@ -2536,6 +2556,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
procStruct->prolang);
}
langStruct = (Form_pg_language) GETSTRUCT(langTup);
+ prodesc->lang_oid = HeapTupleGetOid(langTup);
prodesc->lanpltrusted = langStruct->lanpltrusted;
ReleaseSysCache(langTup);
@@ -2768,9 +2789,12 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
else
{
SV *sv;
+ Oid funcid;
if (OidIsValid(get_base_element_type(tupdesc->attrs[i]->atttypid)))
sv = plperl_ref_from_pg_array(attr, tupdesc->attrs[i]->atttypid);
+ else if ((funcid = get_transform_fromsql(tupdesc->attrs[i]->atttypid, current_call_data->prodesc->lang_oid)))
+ sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, attr));
else
{
char *outputstr;
diff --git a/src/pl/plperl/plperl_helpers.h b/src/pl/plperl/plperl_helpers.h
index 3e8aa7c..53ff66a 100644
--- a/src/pl/plperl/plperl_helpers.h
+++ b/src/pl/plperl/plperl_helpers.h
@@ -1,6 +1,8 @@
#ifndef PL_PERL_HELPERS_H
#define PL_PERL_HELPERS_H
+#include "mb/pg_wchar.h"
+
/*
* convert from utf8 to database encoding
*
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 3fe8e4a..d595c5c 100644
--- a/src/pl/plpython/Makefile
+++ b/src/pl/plpython/Makefile
@@ -115,54 +115,22 @@ all: all-lib
install: all install-lib install-data
installdirs: installdirs-lib
- $(MKDIR_P) '$(DESTDIR)$(datadir)/extension'
+ $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)'
uninstall: uninstall-lib uninstall-data
install-data: installdirs
$(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
+ $(INSTALL_DATA) $(srcdir)/plpython.h $(srcdir)/plpy_util.h '$(DESTDIR)$(includedir_server)'
uninstall-data:
rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA)))
+ rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, plpython.h plpy_util.h)
.PHONY: install-data uninstall-data
-ifeq ($(python_majorversion),3)
-# Adjust regression tests for Python 3 compatibility
-#
-# Mention those regression test files that need to be mangled in the
-# variable REGRESS_PLPYTHON3_MANGLE. They will be copied to a
-# subdirectory python3/ and have their Python syntax and other bits
-# adjusted to work with Python 3.
-
-# Note that the order of the tests needs to be preserved in this
-# expression.
-REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_MANGLE)),python3/$(test),$(test)))
-
-.PHONY: pgregress-python3-mangle
-pgregress-python3-mangle:
- $(MKDIR_P) sql/python3 expected/python3 results/python3
- for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \
- sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \
- -e "s/<type 'exceptions\.\([[:alpha:]]*\)'>/<class '\1'>/g" \
- -e "s/<type 'long'>/<class 'int'>/g" \
- -e "s/\([0-9][0-9]*\)L/\1/g" \
- -e 's/\([ [{]\)u"/\1"/g' \
- -e "s/\([ [{]\)u'/\1'/g" \
- -e "s/def next/def __next__/g" \
- -e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \
- -e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \
- -e "s/EXTENSION plpythonu/EXTENSION plpython3u/g" \
- -e "s/EXTENSION plpython2u/EXTENSION plpython3u/g" \
- $$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \
- done
-
-check installcheck: pgregress-python3-mangle
-
-pg_regress_clean_files += sql/python3/ expected/python3/ results/python3/
-
-endif # Python 3
+include $(srcdir)/regress-python3-mangle.mk
check: all submake
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 0dad843..10cb623 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -283,6 +283,7 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
MemSet(&proc, 0, sizeof(PLyProcedure));
proc.pyname = PLy_strdup("__plpython_inline_block");
+ proc.langid = codeblock->langOid;
proc.result.out.d.typoid = VOIDOID;
/*
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index d278d6e..7f55d20 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -13,6 +13,7 @@
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "utils/hsearch.h"
+#include "utils/inval.h"
#include "utils/syscache.h"
#include "plpython.h"
@@ -26,6 +27,7 @@
static HTAB *PLy_procedure_cache = NULL;
static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
+static void invalidate_procedure_caches(Datum arg, int cacheid, uint32 hashvalue);
static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
static char *PLy_procedure_munge_source(const char *name, const char *src);
@@ -42,6 +44,29 @@ init_procedure_caches(void)
hash_ctl.hash = tag_hash;
PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
HASH_ELEM | HASH_FUNCTION);
+ CacheRegisterSyscacheCallback(TRFTYPELANG,
+ invalidate_procedure_caches,
+ (Datum) 0);
+}
+
+static void
+invalidate_procedure_caches(Datum arg, int cacheid, uint32 hashvalue)
+{
+ HASH_SEQ_STATUS status;
+ PLyProcedureEntry *hentry;
+
+ Assert(PLy_procedure_cache != NULL);
+
+ /* flush all entries */
+ hash_seq_init(&status, PLy_procedure_cache);
+
+ while ((hentry = (PLyProcedureEntry *) hash_seq_search(&status)))
+ {
+ if (hash_search(PLy_procedure_cache,
+ (void *) &hentry->key,
+ HASH_REMOVE, NULL) == NULL)
+ elog(ERROR, "hash table corrupted");
+ }
}
/*
@@ -166,6 +191,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
for (i = 0; i < FUNC_MAX_ARGS; i++)
PLy_typeinfo_init(&proc->args[i]);
proc->nargs = 0;
+ proc->langid = procStruct->prolang;
proc->code = proc->statics = NULL;
proc->globals = NULL;
proc->is_setof = procStruct->proretset;
@@ -220,7 +246,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
else
{
/* do the real work */
- PLy_output_datum_func(&proc->result, rvTypeTup);
+ PLy_output_datum_func(&proc->result, rvTypeTup, proc->langid);
}
ReleaseSysCache(rvTypeTup);
@@ -294,7 +320,8 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
default:
PLy_input_datum_func(&(proc->args[pos]),
types[i],
- argTypeTup);
+ argTypeTup,
+ proc->langid);
break;
}
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h
index f1c8510..9776e7d 100644
--- a/src/pl/plpython/plpy_procedure.h
+++ b/src/pl/plpython/plpy_procedure.h
@@ -27,6 +27,7 @@ typedef struct PLyProcedure
char **argnames; /* Argument names */
PLyTypeInfo args[FUNC_MAX_ARGS];
int nargs;
+ Oid langid; /* OID of plpython pg_language entry */
PyObject *code; /* compiled procedure code */
PyObject *statics; /* data saved across calls, local scope */
PyObject *globals; /* data saved across calls, global scope */
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index 982bf84..dbae6b6 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -76,6 +76,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PG_TRY();
{
int i;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
/*
* the other loop might throw an exception, if PLyTypeInfo member
@@ -131,7 +132,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
plan->types[i] = typeId;
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
if (typeStruct->typtype != TYPTYPE_COMPOSITE)
- PLy_output_datum_func(&plan->args[i], typeTup);
+ PLy_output_datum_func(&plan->args[i], typeTup, exec_ctx->curr_proc->langid);
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index caccbf9..33187db 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -29,8 +29,8 @@
/* I/O function caching */
-static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup);
-static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup);
+static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid);
+static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid);
/* conversion from Datums to Python objects */
static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
@@ -43,6 +43,7 @@ static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d);
static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d);
static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
/* conversion from Python objects to Datums */
@@ -50,6 +51,7 @@ static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
+static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
/* conversion from Python objects to composite Datums (used by triggers and SRFs) */
@@ -102,27 +104,28 @@ PLy_typeinfo_dealloc(PLyTypeInfo *arg)
* PostgreSQL, and vice versa.
*/
void
-PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup)
+PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid)
{
if (arg->is_rowtype > 0)
elog(ERROR, "PLyTypeInfo struct is initialized for Tuple");
arg->is_rowtype = 0;
- PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup);
+ PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup, langid);
}
void
-PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup)
+PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid)
{
if (arg->is_rowtype > 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple");
arg->is_rowtype = 0;
- PLy_output_datum_func2(&(arg->out.d), typeTup);
+ PLy_output_datum_func2(&(arg->out.d), typeTup, langid);
}
void
PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
int i;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
if (arg->is_rowtype == 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
@@ -181,7 +184,8 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
PLy_input_datum_func2(&(arg->in.r.atts[i]),
desc->attrs[i]->atttypid,
- typeTup);
+ typeTup,
+ exec_ctx->curr_proc->langid);
ReleaseSysCache(typeTup);
}
@@ -191,6 +195,7 @@ void
PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
int i;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
if (arg->is_rowtype == 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
@@ -243,7 +248,7 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
elog(ERROR, "cache lookup failed for type %u",
desc->attrs[i]->atttypid);
- PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup);
+ PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup, exec_ctx->curr_proc->langid);
ReleaseSysCache(typeTup);
}
@@ -362,10 +367,12 @@ PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
}
static void
-PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
+PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid)
{
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
Oid element_type;
+ Oid base_type;
+ Oid funcid;
perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
arg->typoid = HeapTupleGetOid(typeTup);
@@ -374,12 +381,24 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
arg->typbyval = typeStruct->typbyval;
element_type = get_element_type(arg->typoid);
+ base_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.
+ * datums.
*/
- switch (getBaseType(element_type ? element_type : arg->typoid))
+
+ if ((funcid = get_transform_tosql(base_type, langid)))
+ {
+ arg->func = PLyObject_ToTransform;
+ perm_fmgr_info(funcid, &arg->typtransform);
+ }
+ else if (typeStruct->typtype == TYPTYPE_COMPOSITE)
+ {
+ arg->func = PLyObject_ToComposite;
+ }
+ else
+ switch (base_type)
{
case BOOLOID:
arg->func = PLyObject_ToBool;
@@ -392,12 +411,6 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
break;
}
- /* Composite types need their own input routine, though */
- if (typeStruct->typtype == TYPTYPE_COMPOSITE)
- {
- arg->func = PLyObject_ToComposite;
- }
-
if (element_type)
{
char dummy_delim;
@@ -412,6 +425,7 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
arg->elm = PLy_malloc0(sizeof(*arg->elm));
arg->elm->func = arg->func;
+ arg->elm->typtransform = arg->typtransform;
arg->func = PLySequence_ToArray;
arg->elm->typoid = element_type;
@@ -424,10 +438,12 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
}
static void
-PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
+PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid)
{
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
- Oid element_type = get_element_type(typeOid);
+ Oid element_type;
+ Oid base_type;
+ Oid funcid;
/* Get the type's conversion information */
perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
@@ -439,7 +455,17 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
arg->typalign = typeStruct->typalign;
/* Determine which kind of Python object we will convert to */
- switch (getBaseType(element_type ? element_type : typeOid))
+
+ element_type = get_element_type(typeOid);
+ base_type = getBaseType(element_type ? element_type : typeOid);
+
+ if ((funcid = get_transform_fromsql(base_type, langid)))
+ {
+ arg->func = PLyObject_FromTransform;
+ perm_fmgr_info(funcid, &arg->typtransform);
+ }
+ else
+ switch (base_type)
{
case BOOLOID:
arg->func = PLyBool_FromBool;
@@ -480,6 +506,7 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
arg->elm = PLy_malloc0(sizeof(*arg->elm));
arg->elm->func = arg->func;
+ arg->elm->typtransform = arg->typtransform;
arg->func = PLyList_FromArray;
arg->elm->typoid = element_type;
arg->elm->typmod = -1;
@@ -591,14 +618,22 @@ PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d)
static PyObject *
PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
{
- char *x = OutputFunctionCall(&arg->typfunc, d);
- PyObject *r = PyString_FromString(x);
+ char *x;
+ PyObject *r;
+ x = OutputFunctionCall(&arg->typfunc, d);
+ r = PyString_FromString(x);
pfree(x);
return r;
}
static PyObject *
+PLyObject_FromTransform(PLyDatumToOb *arg, Datum d)
+{
+ return (PyObject *) DatumGetPointer(FunctionCall1(&arg->typtransform, d));
+}
+
+static PyObject *
PLyList_FromArray(PLyDatumToOb *arg, Datum d)
{
ArrayType *array = DatumGetArrayTypeP(d);
@@ -747,16 +782,15 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
/*
- * Generic conversion function: Convert PyObject to cstring and
- * cstring into PostgreSQL type.
+ * Convert Python object to C string in server encoding.
*/
-static Datum
-PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+char *
+PLyObject_AsString(PyObject *plrv)
{
- PyObject *volatile plrv_bo = NULL;
- Datum rv;
-
- Assert(plrv != Py_None);
+ PyObject *plrv_bo;
+ char *plrv_sc;
+ size_t plen;
+ size_t slen;
if (PyUnicode_Check(plrv))
plrv_bo = PLyUnicode_Bytes(plrv);
@@ -774,36 +808,47 @@ PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
if (!plrv_bo)
PLy_elog(ERROR, "could not create string representation of Python object");
- PG_TRY();
- {
- char *plrv_sc = PyBytes_AsString(plrv_bo);
- size_t plen = PyBytes_Size(plrv_bo);
- size_t slen = strlen(plrv_sc);
-
- if (slen < plen)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
- else if (slen > plen)
- elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
- pg_verifymbstr(plrv_sc, slen, false);
- rv = InputFunctionCall(&arg->typfunc,
- plrv_sc,
- arg->typioparam,
- typmod);
- }
- PG_CATCH();
- {
- Py_XDECREF(plrv_bo);
- PG_RE_THROW();
- }
- PG_END_TRY();
+ plrv_sc = pstrdup(PyBytes_AsString(plrv_bo));
+ plen = PyBytes_Size(plrv_bo);
+ slen = strlen(plrv_sc);
Py_XDECREF(plrv_bo);
- return rv;
+ if (slen < plen)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
+ else if (slen > plen)
+ elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
+ pg_verifymbstr(plrv_sc, slen, false);
+
+ return plrv_sc;
}
+
+/*
+ * Generic conversion function: Convert PyObject to cstring and
+ * cstring into PostgreSQL type.
+ */
+static Datum
+PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+ Assert(plrv != Py_None);
+
+ return InputFunctionCall(&arg->typfunc,
+ PLyObject_AsString(plrv),
+ arg->typioparam,
+ typmod);
+}
+
+
+static Datum
+PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+ return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv));
+}
+
+
static Datum
PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
{
@@ -853,12 +898,13 @@ static Datum
PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
{
HeapTuple typeTup;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid));
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid);
- PLy_output_datum_func2(&info->out.d, typeTup);
+ PLy_output_datum_func2(&info->out.d, typeTup, exec_ctx->curr_proc->langid);
ReleaseSysCache(typeTup);
ReleaseTupleDesc(desc);
diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h
index 82e472a..b869aaa 100644
--- a/src/pl/plpython/plpy_typeio.h
+++ b/src/pl/plpython/plpy_typeio.h
@@ -17,6 +17,7 @@ typedef struct PLyDatumToOb
{
PLyDatumToObFunc func;
FmgrInfo typfunc; /* The type's output function */
+ FmgrInfo typtransform; /* from-SQL transform */
Oid typoid; /* The OID of the type */
int32 typmod; /* The typmod of the type */
Oid typioparam;
@@ -48,6 +49,7 @@ typedef struct PLyObToDatum
{
PLyObToDatumFunc func;
FmgrInfo typfunc; /* The type's input function */
+ FmgrInfo typtransform; /* to-SQL transform */
Oid typoid; /* The OID of the type */
int32 typmod; /* The typmod of the type */
Oid typioparam;
@@ -91,8 +93,8 @@ typedef struct PLyTypeInfo
extern void PLy_typeinfo_init(PLyTypeInfo *arg);
extern void PLy_typeinfo_dealloc(PLyTypeInfo *arg);
-extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup);
-extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup);
+extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid);
+extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid);
extern void PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
@@ -105,4 +107,7 @@ extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObj
/* conversion from heap tuples to Python dictionaries */
extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc);
+/* conversion from Python objects to C strings */
+extern char *PLyObject_AsString(PyObject *plrv);
+
#endif /* PLPY_TYPEIO_H */
diff --git a/src/pl/plpython/plpy_util.c b/src/pl/plpython/plpy_util.c
index 95cbba5..bfa09d8 100644
--- a/src/pl/plpython/plpy_util.c
+++ b/src/pl/plpython/plpy_util.c
@@ -144,22 +144,33 @@ PLyUnicode_AsString(PyObject *unicode)
* unicode object. Reference ownership is passed to the caller.
*/
PyObject *
-PLyUnicode_FromString(const char *s)
+PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size)
{
char *utf8string;
PyObject *o;
utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s,
- strlen(s),
+ size,
GetDatabaseEncoding(),
PG_UTF8);
- o = PyUnicode_FromString(utf8string);
-
- if (utf8string != s)
+ if (utf8string == s)
+ {
+ o = PyUnicode_FromStringAndSize(s, size);
+ }
+ else
+ {
+ o = PyUnicode_FromString(utf8string);
pfree(utf8string);
+ }
return o;
}
+PyObject *
+PLyUnicode_FromString(const char *s)
+{
+ return PLyUnicode_FromStringAndSize(s, strlen(s));
+}
+
#endif /* PY_MAJOR_VERSION >= 3 */
diff --git a/src/pl/plpython/plpy_util.h b/src/pl/plpython/plpy_util.h
index f93e837..4c29f9a 100644
--- a/src/pl/plpython/plpy_util.h
+++ b/src/pl/plpython/plpy_util.h
@@ -16,6 +16,7 @@ extern char *PLyUnicode_AsString(PyObject *unicode);
#if PY_MAJOR_VERSION >= 3
extern PyObject *PLyUnicode_FromString(const char *s);
+extern PyObject *PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size);
#endif
#endif /* PLPY_UTIL_H */
diff --git a/src/pl/plpython/plpython.h b/src/pl/plpython/plpython.h
index 795231c..632b09a 100644
--- a/src/pl/plpython/plpython.h
+++ b/src/pl/plpython/plpython.h
@@ -91,6 +91,7 @@ typedef int Py_ssize_t;
#define PyString_Check(x) 0
#define PyString_AsString(x) PLyUnicode_AsString(x)
#define PyString_FromString(x) PLyUnicode_FromString(x)
+#define PyString_FromStringAndSize(x, size) PLyUnicode_FromStringAndSize(x, size)
#endif
/*
diff --git a/src/pl/plpython/regress-python3-mangle.mk b/src/pl/plpython/regress-python3-mangle.mk
new file mode 100644
index 0000000..d2c7490
--- /dev/null
+++ b/src/pl/plpython/regress-python3-mangle.mk
@@ -0,0 +1,35 @@
+ifeq ($(python_majorversion),3)
+# Adjust regression tests for Python 3 compatibility
+#
+# Mention those regression test files that need to be mangled in the
+# variable REGRESS_PLPYTHON3_MANGLE. They will be copied to a
+# subdirectory python3/ and have their Python syntax and other bits
+# adjusted to work with Python 3.
+
+# Note that the order of the tests needs to be preserved in this
+# expression.
+REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_MANGLE)),python3/$(test),$(test)))
+
+.PHONY: pgregress-python3-mangle
+pgregress-python3-mangle:
+ $(MKDIR_P) sql/python3 expected/python3 results/python3
+ for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \
+ sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \
+ -e "s/<type 'exceptions\.\([[:alpha:]]*\)'>/<class '\1'>/g" \
+ -e "s/<type 'long'>/<class 'int'>/g" \
+ -e "s/\([0-9][0-9]*\)L/\1/g" \
+ -e 's/\([ [{]\)u"/\1"/g' \
+ -e "s/\([ [{]\)u'/\1'/g" \
+ -e "s/def next/def __next__/g" \
+ -e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \
+ -e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \
+ -e "s/EXTENSION \([^ ]*_\)*plpythonu/EXTENSION \1plpython3u/g" \
+ -e "s/EXTENSION \([^ ]*_\)*plpython2u/EXTENSION \1plpython3u/g" \
+ $$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \
+ done
+
+check installcheck: pgregress-python3-mangle
+
+pg_regress_clean_files += sql/python3/ expected/python3/ results/python3/
+
+endif # Python 3
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index a62a3e3..46ddf2d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -127,6 +127,7 @@ pg_shdescription|t
pg_shseclabel|t
pg_statistic|t
pg_tablespace|t
+pg_transform|t
pg_trigger|t
pg_ts_config|t
pg_ts_config_map|t
--
1.8.4.2