[PATCH] Add transforms feature
| От | Peter Eisentraut |
|---|---|
| Тема | [PATCH] Add transforms feature |
| Дата | |
| Msg-id | 1371179501.15555.12.camel@vanquo.pezone.net обсуждение исходный текст |
| Ответ на | Re: transforms (Peter Eisentraut <peter_e@gmx.net>) |
| Ответы |
Re: [PATCH] Add transforms feature
Re: [PATCH] Add transforms feature Re: [PATCH] Add transforms feature Re: [PATCH] Add transforms feature |
| Список | pgsql-hackers |
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.
---
Continued from 2013-01 commit fest. All known open issues have been fixed.
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 | 170 ++++++++++++
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 | 120 ++++++++
contrib/hstore_plpython/.gitignore | 4 +
contrib/hstore_plpython/Makefile | 30 ++
.../hstore_plpython/expected/hstore_plpython.out | 137 +++++++++
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 | 106 +++++++
contrib/ltree_plpython/.gitignore | 4 +
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 | 187 +++++++++++++
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 | 76 ++++-
src/backend/catalog/pg_proc.c | 20 ++
src/backend/commands/dropcmds.c | 6 +
src/backend/commands/event_trigger.c | 3 +
src/backend/commands/functioncmds.c | 293 ++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 17 ++
src/backend/nodes/equalfuncs.c | 15 +
src/backend/parser/gram.y | 82 +++++-
src/backend/parser/parse_func.c | 5 +
src/backend/tcop/utility.c | 16 ++
src/backend/utils/adt/ruleutils.c | 11 +-
src/backend/utils/cache/lsyscache.c | 83 ++++++
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 | 4 +
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 | 6 +-
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 | 3 +-
91 files changed, 2891 insertions(+), 144 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
FUNCTIONplperl_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..ce31ab8
--- /dev/null
+++ b/contrib/hstore_plperl/expected/hstore_plperl.out
@@ -0,0 +1,170 @@
+CREATE EXTENSION hstore_plperl;
+CREATE EXTENSION hstore_plperlu;
+-- test hstore -> perl
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plperlu
+AS $$
+use Data::Dumper;
+elog(INFO, Dumper($_[0]));
+return scalar(keys %{$_[0]});
+$$;
+SELECT test1('aa=>bb, cc=>NULL'::hstore);
+INFO: $VAR1 = {
+ 'cc' => undef,
+ 'aa' => 'bb'
+ };
+
+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;
+elog(INFO, Dumper($_[0]->[0], $_[0]->[1]));
+return scalar(keys %{$_[0]});
+$$;
+SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
+INFO: $VAR1 = {
+ 'cc' => undef,
+ 'aa' => 'bb'
+ };
+$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;
+
+$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 = {
+ 'cc' => undef,
+ 'aa' => 'bb'
+ };
+
+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;
+
+$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 = {
+ 'cc' => undef,
+ 'aa' => 'bb'
+ };
+
+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;
+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' => {
+ 'cc' => undef,
+ 'aa' => 'bb'
+ }
+ };
+
+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
FUNCTIONplperl_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..4796050
--- /dev/null
+++ b/contrib/hstore_plperl/sql/hstore_plperl.sql
@@ -0,0 +1,120 @@
+CREATE EXTENSION hstore_plperl;
+CREATE EXTENSION hstore_plperlu;
+
+
+-- test hstore -> perl
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plperlu
+AS $$
+use Data::Dumper;
+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;
+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;
+
+$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;
+
+$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;
+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..5dcb3ff
--- /dev/null
+++ b/contrib/hstore_plpython/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/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..1bc59dd
--- /dev/null
+++ b/contrib/hstore_plpython/expected/hstore_plpython.out
@@ -0,0 +1,137 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION hstore_plpython2u;
+-- test hstore -> python
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plpythonu
+AS $$
+plpy.info(repr(val))
+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 $$
+plpy.info(repr(val))
+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: %r" % TD["new"])
+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..566cdc5
--- /dev/null
+++ b/contrib/hstore_plpython/sql/hstore_plpython.sql
@@ -0,0 +1,106 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION hstore_plpython2u;
+
+
+-- test hstore -> python
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plpythonu
+AS $$
+plpy.info(repr(val))
+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 $$
+plpy.info(repr(val))
+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: %r" % TD["new"])
+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..5dcb3ff
--- /dev/null
+++ b/contrib/ltree_plpython/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/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 e638a8f..c4b3b42 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>
@@ -5722,6 +5727,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 73c421d..a8f1537 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 f5a0ac9..9f80d75 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 2dbba0c..f0fad1e 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 e94dd4b..7aad302 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..2f10cee
--- /dev/null
+++ b/doc/src/sgml/ref/create_transform.sgml
@@ -0,0 +1,187 @@
+<!-- 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 the type and have
+ <literal>USAGE</literal> privilege on both the type and the language.
+ </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 2a0c7a9..3ab9b82 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 c4d3f3c..f02ea60 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 69171f8..52aae48 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 @@ static bool stack_address_present_add_flags(const ObjectAddress *object,
RemoveEventTriggerById(object->objectId);
break;
+ case OCLASS_TRANSFORM:
+ DropTransformById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
@@ -2308,6 +2313,9 @@ static bool stack_address_present_add_flags(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 215eaf5..dd267c1 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 @@
true
},
{
+ TransformRelationId,
+ TransformOidIndexId,
+ TRFOID,
+ InvalidAttrNumber
+ },
+ {
TriggerRelationId,
TriggerOidIndexId,
-1,
@@ -589,6 +596,29 @@ static void getRelationTypeDescription(StringInfo buffer, Oid relid,
address.objectSubId = 0;
}
break;
+ case OBJECT_TRANSFORM:
+ {
+ TypeName *typename = (TypeName *) linitial(objname);
+ char *langname = (char *) linitial(objargs);
+ Oid typeid = typenameTypeId(NULL, typename);
+ Oid langid;
+ HeapTuple tuple;
+
+ tuple = SearchSysCache1(LANGNAME, PointerGetDatum(langname));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("language \"%s\" does not exist", langname)));
+
+ langid = HeapTupleGetOid(tuple);
+ ReleaseSysCache(tuple);
+
+ address.classId = TransformRelationId;
+ address.objectId =
+ get_transform_oid(typeid, langid, missing_ok);
+ address.objectSubId = 0;
+ }
+ break;
case OBJECT_TSPARSER:
address.classId = TSParserRelationId;
address.objectId = get_ts_parser_oid(objname, missing_ok);
@@ -1234,6 +1264,16 @@ static void getRelationTypeDescription(StringInfo buffer, Oid relid,
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(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
+ format_type_be(typeid));
+ }
+ break;
case OBJECT_TABLESPACE:
if (!pg_tablespace_ownercheck(address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
@@ -1667,19 +1707,10 @@ static void getRelationTypeDescription(StringInfo buffer, Oid relid,
}
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 +1898,27 @@ static void getRelationTypeDescription(StringInfo buffer, Oid relid,
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 2a98ca9..96aabb1 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -23,6 +23,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 "executor/functions.h"
#include "funcapi.h"
@@ -116,6 +117,7 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
ObjectAddress myself,
referenced;
int i;
+ Oid trfid;
/*
* sanity checks
@@ -624,6 +626,15 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ /* dependency on transform used by return type, if any */
+ if ((trfid = get_transform(returnType, languageObjectId)))
+ {
+ 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 +642,15 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
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(allParams[i], languageObjectId)))
+ {
+ 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 @@ static void does_not_exist_skipping(ObjectType objtype,
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 @@
{"SERVER", true},
{"TABLE", true},
{"TABLESPACE", false},
+ {"TRANSFORM", true},
{"TRIGGER", true},
{"TEXT SEARCH CONFIGURATION", true},
{"TEXT SEARCH DICTIONARY", true},
@@ -937,6 +938,7 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname,
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 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname,
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 c776758..17ba792 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"
@@ -1618,6 +1619,298 @@
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()))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be owner of type %s",
+ format_type_be(typeid))));
+
+ aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_TYPE,
+ format_type_be(typeid));
+
+ /*
+ * Get the language
+ */
+ tuple = SearchSysCache1(LANGNAME, PointerGetDatum(stmt->lang));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("language \"%s\" does not exist", stmt->lang)));
+
+ langid = HeapTupleGetOid(tuple);
+ ReleaseSysCache(tuple);
+
+ 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);
+
+ 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);
+
+ 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 typeid, Oid langid, bool missing_ok)
+{
+ Oid oid;
+
+ oid = GetSysCacheOid2(TRFTYPELANG,
+ ObjectIdGetDatum(typeid),
+ ObjectIdGetDatum(langid));
+ if (!OidIsValid(oid) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("transform for type %s language \"%s\" does not exist",
+ format_type_be(typeid),
+ get_language_name(langid, 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,
+ SnapshotNow, 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 b5b8d63..4dd60ea 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3476,6 +3476,20 @@
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)
{
@@ -4377,6 +4391,9 @@
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 3f96595..44f4b8f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1721,6 +1721,18 @@
}
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);
@@ -2847,6 +2859,9 @@
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 5094226..afc33e3 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
@@ -344,6 +344,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
@@ -578,12 +579,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
@@ -753,6 +754,7 @@ stmt :
| CreateSeqStmt
| CreateStmt
| CreateTableSpaceStmt
+ | CreateTransformStmt
| CreateTrigStmt
| CreateEventTrigStmt
| CreateRoleStmt
@@ -777,6 +779,7 @@ stmt :
| DropRuleStmt
| DropStmt
| DropTableSpaceStmt
+ | DropTransformStmt
| DropTrigStmt
| DropRoleStmt
| DropUserStmt
@@ -3847,6 +3850,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);
@@ -5274,6 +5287,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);
@@ -6744,6 +6766,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]
@@ -12851,6 +12923,7 @@ unreserved_keyword:
| SHOW
| SIMPLE
| SNAPSHOT
+ | SQL_P
| STABLE
| STANDALONE_P
| START
@@ -12870,6 +12943,7 @@ unreserved_keyword:
| TEMPORARY
| TEXT_P
| TRANSACTION
+ | TRANSFORM
| TRIGGER
| TRUNCATE
| TRUSTED
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index ae7d195..7c99938 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -306,6 +306,11 @@ static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
parser_errposition(pstate, location)));
}
+ if (rettype == INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot call function returning type \"internal\"")));
+
/*
* If there are default arguments, we have to include their types in
* actual_arg_types for the purpose of checking generic type consistency.
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c940897..3466b9c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -214,6 +214,7 @@ static void ProcessUtilitySlow(Node *parsetree,
case T_CreateTableAsStmt:
case T_RefreshMatViewStmt:
case T_CreateTableSpaceStmt:
+ case T_CreateTransformStmt:
case T_CreateTrigStmt:
case T_CompositeTypeStmt:
case T_CreateEnumStmt:
@@ -1290,6 +1291,10 @@ static void ProcessUtilitySlow(Node *parsetree,
DefineOpFamily((CreateOpFamilyStmt *) parsetree);
break;
+ case T_CreateTransformStmt:
+ CreateTransform((CreateTransformStmt *) parsetree);
+ break;
+
case T_AlterOpFamilyStmt:
AlterOpFamily((AlterOpFamilyStmt *) parsetree);
break;
@@ -1946,6 +1951,9 @@ static void ProcessUtilitySlow(Node *parsetree,
case OBJECT_OPFAMILY:
tag = "DROP OPERATOR FAMILY";
break;
+ case OBJECT_TRANSFORM:
+ tag = "DROP TRANSFORM";
+ break;
default:
tag = "???";
}
@@ -2194,6 +2202,10 @@ static void ProcessUtilitySlow(Node *parsetree,
}
break;
+ case T_CreateTransformStmt:
+ tag = "CREATE TRANSFORM";
+ break;
+
case T_CreateTrigStmt:
tag = "CREATE TRIGGER";
break;
@@ -2809,6 +2821,10 @@ static void ProcessUtilitySlow(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 a1ed781..f50f374 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1869,9 +1869,7 @@ static char *generate_function_name(Oid funcid, int nargs,
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 @@ static char *generate_function_name(Oid funcid, int nargs,
(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 @@ static char *generate_function_name(Oid funcid, int nargs,
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 @@ static char *generate_function_name(Oid funcid, int nargs,
appendStringInfoString(&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..fd08a7d 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 @@
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,63 @@
}
+/* ---------- TRANSFORM CACHE ---------- */
+
+Oid
+get_transform(Oid typid, Oid langid)
+{
+ HeapTuple tup;
+
+ tup = SearchSysCache2(TRFTYPELANG, typid, langid);
+ if (HeapTupleIsValid(tup))
+ {
+ Oid id;
+
+ id = HeapTupleGetOid(tup);
+ ReleaseSysCache(tup);
+ return id;
+ }
+ else
+ return InvalidOid;
+}
+
+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 ecb0f96..6e6cef4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_rewrite.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"
@@ -646,6 +647,28 @@ struct cachedesc
},
16
},
+ {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 58322dc..212d4d7 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -89,6 +89,7 @@ static void findParentsByOid(TableInfo *self,
int numRules;
int numProcLangs;
int numCasts;
+ int numTransforms;
int numOpclasses;
int numOpfamilies;
int numConversions;
@@ -199,6 +200,10 @@ static void findParentsByOid(TableInfo *self,
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 ec956ad..c4da9e5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -182,6 +182,7 @@ static int findSecLabels(Archive *fout, Oid classoid, Oid objoid,
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);
@@ -6097,6 +6098,110 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer,
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
@@ -7707,6 +7812,9 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer,
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);
@@ -10098,6 +10206,127 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer,
}
/*
+ * 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
*/
@@ -14895,6 +15124,7 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer,
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 5582538..5add50e 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,
@@ -406,6 +407,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
{
@@ -558,6 +568,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 @@
* 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 @@
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 @@
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 @@ static void describeDumpableObject(DumpableObject *obj,
((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 d46fe9e..283d345 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201306121
+#define CATALOG_VERSION_NO 201306132
#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 19268fb..86c7dba 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -226,6 +226,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 fa9f41f..deac301 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -49,10 +49,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 typeid, Oid langid, bool missing_ok);
/* commands/operatorcmds.c */
extern Oid DefineOperator(List *names, List *parameters);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 0d5c007..1c082e9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -362,6 +362,7 @@ typedef enum NodeTag
T_CreateEventTrigStmt,
T_AlterEventTrigStmt,
T_RefreshMatViewStmt,
+ T_CreateTransformStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6723647..f028068 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1145,6 +1145,7 @@ typedef enum ObjectType
OBJECT_SEQUENCE,
OBJECT_TABLE,
OBJECT_TABLESPACE,
+ OBJECT_TRANSFORM,
OBJECT_TRIGGER,
OBJECT_TSCONFIGURATION,
OBJECT_TSDICTIONARY,
@@ -2569,6 +2570,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 68a13b7..88e9ece 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -345,6 +345,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)
@@ -372,6 +373,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..9ff411f 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,9 @@ 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(Oid typid, Oid langid);
+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 d1d8abe..ee482f5 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 8258ce2..a6f3af5 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 @@
{"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 @@
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 @@
bool *nulls;
int *nelems;
FmgrInfo proc;
+ FmgrInfo transform_proc;
} plperl_array_info;
/**********************************************************************
@@ -1260,6 +1262,7 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
bool *isnull)
{
FmgrInfo tmp;
+ Oid funcid;
/* we might recurse */
check_stack_depth();
@@ -1283,6 +1286,8 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
/* 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 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
typdelim;
Oid typioparam;
Oid typoutputfunc;
+ Oid transform_funcid;
int i,
nitems,
*dims;
@@ -1402,14 +1408,17 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
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 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
{
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 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
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 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
SV *retval;
int i;
int count;
+ Oid *argtypes = NULL;
+ int nargs = 0;
ENTER;
SAVETMPS;
@@ -2042,6 +2056,9 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
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 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
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 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
procStruct->prolang);
}
langStruct = (Form_pg_language) GETSTRUCT(langTup);
+ prodesc->lang_oid = HeapTupleGetOid(langTup);
prodesc->lanpltrusted = langStruct->lanpltrusted;
ReleaseSysCache(langTup);
@@ -2768,9 +2789,12 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc,
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 @@
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 5007e77..f8ee305 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -166,6 +166,7 @@
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 +221,7 @@
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 +295,8 @@
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 c9182eb..2fa255d 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -76,6 +76,7 @@
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 @@
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 8f2367d..f670ebe 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -28,8 +28,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);
@@ -42,6 +42,7 @@
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 */
@@ -49,6 +50,7 @@
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) */
@@ -101,27 +103,28 @@
* 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");
@@ -180,7 +183,8 @@
PLy_input_datum_func2(&(arg->in.r.atts[i]),
desc->attrs[i]->atttypid,
- typeTup);
+ typeTup,
+ exec_ctx->curr_proc->langid);
ReleaseSysCache(typeTup);
}
@@ -190,6 +194,7 @@
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");
@@ -242,7 +247,7 @@
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);
}
@@ -361,10 +366,12 @@
}
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);
@@ -373,12 +380,24 @@
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;
@@ -391,12 +410,6 @@
break;
}
- /* Composite types need their own input routine, though */
- if (typeStruct->typtype == TYPTYPE_COMPOSITE)
- {
- arg->func = PLyObject_ToComposite;
- }
-
if (element_type)
{
char dummy_delim;
@@ -411,6 +424,7 @@
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;
@@ -423,10 +437,12 @@
}
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);
@@ -438,7 +454,17 @@
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;
@@ -479,6 +505,7 @@
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;
@@ -569,14 +596,22 @@
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);
@@ -725,16 +760,15 @@
/*
- * 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);
@@ -752,36 +786,47 @@
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
containnull 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
nullbytes")));
+ 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)
{
@@ -831,12 +876,13 @@
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 @@
* 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 432d39a..85fb3f7 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -126,6 +126,7 @@ SELECT relname, relhasindex
pg_shseclabel | t
pg_statistic | t
pg_tablespace | t
+ pg_transform | t
pg_trigger | t
pg_ts_config | t
pg_ts_config_map | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
timetz_tbl | f
tinterval_tbl | f
varchar_tbl | f
-(155 rows)
+(156 rows)
--
-- another sanity check: every system catalog that has OIDs should have
В списке pgsql-hackers по дате отправления: