Add transforms feature
authorPeter Eisentraut <[email protected]>
Sun, 26 Apr 2015 14:33:14 +0000 (10:33 -0400)
committerPeter Eisentraut <[email protected]>
Sun, 26 Apr 2015 14:33:14 +0000 (10:33 -0400)
This provides a mechanism for specifying conversions between SQL data
types and procedural languages.  As examples, there are transforms
for hstore and ltree for PL/Perl and PL/Python.

reviews by Pavel StÄ›hule and Andres Freund

101 files changed:
contrib/Makefile
contrib/hstore_plperl/.gitignore [new file with mode: 0644]
contrib/hstore_plperl/Makefile [new file with mode: 0644]
contrib/hstore_plperl/expected/create_transform.out [new file with mode: 0644]
contrib/hstore_plperl/expected/hstore_plperl.out [new file with mode: 0644]
contrib/hstore_plperl/hstore_plperl--1.0.sql [new file with mode: 0644]
contrib/hstore_plperl/hstore_plperl.c [new file with mode: 0644]
contrib/hstore_plperl/hstore_plperl.control [new file with mode: 0644]
contrib/hstore_plperl/hstore_plperlu--1.0.sql [new file with mode: 0644]
contrib/hstore_plperl/hstore_plperlu.control [new file with mode: 0644]
contrib/hstore_plperl/sql/create_transform.sql [new file with mode: 0644]
contrib/hstore_plperl/sql/hstore_plperl.sql [new file with mode: 0644]
contrib/hstore_plpython/.gitignore [new file with mode: 0644]
contrib/hstore_plpython/Makefile [new file with mode: 0644]
contrib/hstore_plpython/expected/hstore_plpython.out [new file with mode: 0644]
contrib/hstore_plpython/hstore_plpython.c [new file with mode: 0644]
contrib/hstore_plpython/hstore_plpython2u--1.0.sql [new file with mode: 0644]
contrib/hstore_plpython/hstore_plpython2u.control [new file with mode: 0644]
contrib/hstore_plpython/hstore_plpython3u--1.0.sql [new file with mode: 0644]
contrib/hstore_plpython/hstore_plpython3u.control [new file with mode: 0644]
contrib/hstore_plpython/hstore_plpythonu--1.0.sql [new file with mode: 0644]
contrib/hstore_plpython/hstore_plpythonu.control [new file with mode: 0644]
contrib/hstore_plpython/sql/hstore_plpython.sql [new file with mode: 0644]
contrib/ltree_plpython/.gitignore [new file with mode: 0644]
contrib/ltree_plpython/Makefile [new file with mode: 0644]
contrib/ltree_plpython/expected/ltree_plpython.out [new file with mode: 0644]
contrib/ltree_plpython/ltree_plpython.c [new file with mode: 0644]
contrib/ltree_plpython/ltree_plpython2u--1.0.sql [new file with mode: 0644]
contrib/ltree_plpython/ltree_plpython2u.control [new file with mode: 0644]
contrib/ltree_plpython/ltree_plpython3u--1.0.sql [new file with mode: 0644]
contrib/ltree_plpython/ltree_plpython3u.control [new file with mode: 0644]
contrib/ltree_plpython/ltree_plpythonu--1.0.sql [new file with mode: 0644]
contrib/ltree_plpython/ltree_plpythonu.control [new file with mode: 0644]
contrib/ltree_plpython/sql/ltree_plpython.sql [new file with mode: 0644]
doc/src/sgml/catalogs.sgml
doc/src/sgml/hstore.sgml
doc/src/sgml/information_schema.sgml
doc/src/sgml/ltree.sgml
doc/src/sgml/ref/allfiles.sgml
doc/src/sgml/ref/alter_extension.sgml
doc/src/sgml/ref/comment.sgml
doc/src/sgml/ref/create_function.sgml
doc/src/sgml/ref/create_transform.sgml [new file with mode: 0644]
doc/src/sgml/ref/drop_transform.sgml [new file with mode: 0644]
doc/src/sgml/reference.sgml
src/Makefile.shlib
src/backend/catalog/Makefile
src/backend/catalog/dependency.c
src/backend/catalog/information_schema.sql
src/backend/catalog/objectaddress.c
src/backend/catalog/pg_aggregate.c
src/backend/catalog/pg_proc.c
src/backend/commands/dropcmds.c
src/backend/commands/event_trigger.c
src/backend/commands/functioncmds.c
src/backend/commands/proclang.c
src/backend/commands/typecmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/tcop/utility.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/cache/lsyscache.c
src/backend/utils/cache/syscache.c
src/backend/utils/fmgr/funcapi.c
src/bin/pg_dump/common.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/pg_dump/pg_dump_sort.c
src/include/catalog/catversion.h
src/include/catalog/dependency.h
src/include/catalog/indexing.h
src/include/catalog/pg_class.h
src/include/catalog/pg_proc.h
src/include/catalog/pg_proc_fn.h
src/include/catalog/pg_transform.h [new file with mode: 0644]
src/include/commands/defrem.h
src/include/funcapi.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/parser/kwlist.h
src/include/utils/lsyscache.h
src/include/utils/syscache.h
src/interfaces/ecpg/preproc/ecpg.tokens
src/interfaces/ecpg/preproc/ecpg.trailer
src/interfaces/ecpg/preproc/ecpg_keywords.c
src/pl/plperl/GNUmakefile
src/pl/plperl/plperl.c
src/pl/plperl/plperl_helpers.h
src/pl/plpython/Makefile
src/pl/plpython/plpy_main.c
src/pl/plpython/plpy_procedure.c
src/pl/plpython/plpy_procedure.h
src/pl/plpython/plpy_spi.c
src/pl/plpython/plpy_typeio.c
src/pl/plpython/plpy_typeio.h
src/pl/plpython/plpy_util.c
src/pl/plpython/plpy_util.h
src/pl/plpython/plpython.h
src/pl/plpython/regress-python3-mangle.mk [new file with mode: 0644]
src/test/regress/expected/sanity_check.out

index e2c4e274930b83f2f9fa4125af3469ae4007cab9..f84e684972dacc72c5432bcb5f01384e8b0c2f01 100644 (file)
@@ -71,6 +71,18 @@ else
 ALWAYS_SUBDIRS += sepgsql
 endif
 
+ifeq ($(with_perl),yes)
+SUBDIRS += hstore_plperl
+else
+ALWAYS_SUBDIRS += hstore_plperl
+endif
+
+ifeq ($(with_python),yes)
+SUBDIRS += hstore_plpython ltree_plpython
+else
+ALWAYS_SUBDIRS += hstore_plpython ltree_plpython
+endif
+
 # Missing:
 #              start-scripts   \ (does not have a makefile)
 
diff --git a/contrib/hstore_plperl/.gitignore b/contrib/hstore_plperl/.gitignore
new file mode 100644 (file)
index 0000000..5dcb3ff
--- /dev/null
@@ -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 (file)
index 0000000..70089b7
--- /dev/null
@@ -0,0 +1,24 @@
+# contrib/hstore_plperl/Makefile
+
+MODULE_big = hstore_plperl
+OBJS = hstore_plperl.o
+
+PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl -I$(perl_archlibexp)/CORE -I$(top_srcdir)/contrib/hstore
+
+EXTENSION = hstore_plperl hstore_plperlu
+DATA = hstore_plperl--1.0.sql hstore_plperlu--1.0.sql
+
+REGRESS = hstore_plperl create_transform
+REGRESS_OPTS = --load-extension=hstore --load-extension=plperl --load-extension=plperlu
+EXTRA_INSTALL = contrib/hstore
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/hstore_plperl
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/hstore_plperl/expected/create_transform.out b/contrib/hstore_plperl/expected/create_transform.out
new file mode 100644 (file)
index 0000000..1661d48
--- /dev/null
@@ -0,0 +1,74 @@
+-- general regression test for transforms
+DROP EXTENSION IF EXISTS hstore CASCADE;
+NOTICE:  extension "hstore" does not exist, skipping
+DROP EXTENSION IF EXISTS plperl CASCADE;
+NOTICE:  extension "plperl" does not exist, skipping
+DROP EXTENSION IF EXISTS hstore_plperl CASCADE;
+NOTICE:  extension "hstore_plperl" does not exist, skipping
+CREATE EXTENSION hstore;
+CREATE EXTENSION plperl;
+CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
+LANGUAGE C STRICT IMMUTABLE
+AS '$libdir/hstore_plperl';
+CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
+LANGUAGE C STRICT IMMUTABLE
+AS '$libdir/hstore_plperl';
+CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+ERROR:  type "foo" does not exist
+CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+ERROR:  language "foo" does not exist
+CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+ERROR:  return data type of FROM SQL function must be "internal"
+CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+ERROR:  first argument of transform function must be type "internal"
+CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- ok
+CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+ERROR:  transform for type hstore language plperl already exists
+CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- ok
+CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal));  -- ok
+CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- ok
+DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl;
+NOTICE:  type "fake_type" does not exist, skipping
+DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang;
+NOTICE:  transform for type hstore language fake_lang does not exist, skipping
+DROP TRANSFORM FOR foo LANGUAGE plperl;
+ERROR:  type "foo" does not exist
+DROP TRANSFORM FOR hstore LANGUAGE foo;
+ERROR:  language "foo" does not exist
+DROP TRANSFORM FOR hstore LANGUAGE plperl;
+DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl;
+NOTICE:  transform for type hstore language plperl does not exist, skipping
+DROP FUNCTION hstore_to_plperl(val internal);
+DROP FUNCTION plperl_to_hstore(val internal);
+CREATE EXTENSION hstore_plperl;
+\dx+ hstore_plperl
+ Objects in extension "hstore_plperl"
+          Object Description          
+--------------------------------------
+ function hstore_to_plperl(internal)
+ function plperl_to_hstore(internal)
+ transform for hstore language plperl
+(3 rows)
+
+ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl;
+\dx+ hstore_plperl
+Objects in extension "hstore_plperl"
+         Object Description          
+-------------------------------------
+ function hstore_to_plperl(internal)
+ function plperl_to_hstore(internal)
+(2 rows)
+
+ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl;
+\dx+ hstore_plperl
+ Objects in extension "hstore_plperl"
+          Object Description          
+--------------------------------------
+ function hstore_to_plperl(internal)
+ function plperl_to_hstore(internal)
+ transform for hstore language plperl
+(3 rows)
+
+DROP EXTENSION hstore CASCADE;
+NOTICE:  drop cascades to extension hstore_plperl
+DROP EXTENSION plperl CASCADE;
diff --git a/contrib/hstore_plperl/expected/hstore_plperl.out b/contrib/hstore_plperl/expected/hstore_plperl.out
new file mode 100644 (file)
index 0000000..cdae609
--- /dev/null
@@ -0,0 +1,213 @@
+CREATE EXTENSION hstore_plperl;
+CREATE EXTENSION hstore_plperlu;
+SELECT transforms.udt_schema, transforms.udt_name,
+       routine_schema, routine_name,
+       group_name, transform_type
+FROM information_schema.transforms JOIN information_schema.routines
+     USING (specific_catalog, specific_schema, specific_name)
+ORDER BY 1, 2, 5, 6;
+ udt_schema | udt_name | routine_schema |   routine_name    | group_name | transform_type 
+------------+----------+----------------+-------------------+------------+----------------
+ public     | hstore   | public         | hstore_to_plperl  | plperl     | FROM SQL
+ public     | hstore   | public         | plperl_to_hstore  | plperl     | TO SQL
+ public     | hstore   | public         | hstore_to_plperlu | plperlu    | FROM SQL
+ public     | hstore   | public         | plperlu_to_hstore | plperlu    | TO SQL
+(4 rows)
+
+-- test hstore -> perl
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_[0]));
+return scalar(keys %{$_[0]});
+$$;
+SELECT test1('aa=>bb, cc=>NULL'::hstore);
+INFO:  $VAR1 = {
+          'aa' => 'bb',
+          'cc' => undef
+        };
+
+CONTEXT:  PL/Perl function "test1"
+ test1 
+-------
+     2
+(1 row)
+
+CREATE FUNCTION test1none(val hstore) RETURNS int
+LANGUAGE plperlu
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_[0]));
+return scalar(keys %{$_[0]});
+$$;
+SELECT test1none('aa=>bb, cc=>NULL'::hstore);
+INFO:  $VAR1 = '"aa"=>"bb", "cc"=>NULL';
+
+CONTEXT:  PL/Perl function "test1none"
+ test1none 
+-----------
+         0
+(1 row)
+
+CREATE FUNCTION test1list(val hstore) RETURNS int
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_[0]));
+return scalar(keys %{$_[0]});
+$$;
+SELECT test1list('aa=>bb, cc=>NULL'::hstore);
+INFO:  $VAR1 = {
+          'aa' => 'bb',
+          'cc' => undef
+        };
+
+CONTEXT:  PL/Perl function "test1list"
+ test1list 
+-----------
+         2
+(1 row)
+
+-- test hstore[] -> perl
+CREATE FUNCTION test1arr(val hstore[]) RETURNS int
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_[0]->[0], $_[0]->[1]));
+return scalar(keys %{$_[0]});
+$$;
+SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
+INFO:  $VAR1 = {
+          'aa' => 'bb',
+          'cc' => undef
+        };
+$VAR2 = {
+          'dd' => 'ee'
+        };
+
+CONTEXT:  PL/Perl function "test1arr"
+ test1arr 
+----------
+        2
+(1 row)
+
+-- test perl -> hstore
+CREATE FUNCTION test2() RETURNS hstore
+LANGUAGE plperl
+TRANSFORM FOR TYPE hstore
+AS $$
+$val = {a => 1, b => 'boo', c => undef};
+return $val;
+$$;
+SELECT test2();
+              test2              
+---------------------------------
+ "a"=>"1", "b"=>"boo", "c"=>NULL
+(1 row)
+
+-- test perl -> hstore[]
+CREATE FUNCTION test2arr() RETURNS hstore[]
+LANGUAGE plperl
+TRANSFORM FOR TYPE hstore
+AS $$
+$val = [{a => 1, b => 'boo', c => undef}, {d => 2}];
+return $val;
+$$;
+SELECT test2arr();
+                           test2arr                           
+--------------------------------------------------------------
+ {"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
+(1 row)
+
+-- test as part of prepare/execute
+CREATE FUNCTION test3() RETURNS void
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+
+$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1});
+elog(INFO, Dumper($rv->{rows}[0]->{col1}));
+
+$val = {a => 1, b => 'boo', c => undef};
+$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore");
+$rv = spi_exec_prepared($plan, {}, $val);
+elog(INFO, Dumper($rv->{rows}[0]->{col1}));
+$$;
+SELECT test3();
+INFO:  $VAR1 = {
+          'aa' => 'bb',
+          'cc' => undef
+        };
+
+CONTEXT:  PL/Perl function "test3"
+INFO:  $VAR1 = '"a"=>"1", "b"=>"boo", "c"=>NULL';
+
+CONTEXT:  PL/Perl function "test3"
+ test3 
+-------
+(1 row)
+
+-- test trigger
+CREATE TABLE test1 (a int, b hstore);
+INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
+SELECT * FROM test1;
+ a |           b            
+---+------------------------
+ 1 | "aa"=>"bb", "cc"=>NULL
+(1 row)
+
+CREATE FUNCTION test4() RETURNS trigger
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_TD->{new}));
+if ($_TD->{new}{a} == 1) {
+    $_TD->{new}{b} = {a => 1, b => 'boo', c => undef};
+}
+
+return "MODIFY";
+$$;
+CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
+UPDATE test1 SET a = a;
+INFO:  $VAR1 = {
+          'a' => '1',
+          'b' => {
+                 'aa' => 'bb',
+                 'cc' => undef
+               }
+        };
+
+CONTEXT:  PL/Perl function "test4"
+SELECT * FROM test1;
+ a |                b                
+---+---------------------------------
+ 1 | "a"=>"1", "b"=>"boo", "c"=>NULL
+(1 row)
+
+DROP TABLE test1;
+DROP FUNCTION test1(hstore);
+DROP FUNCTION test1none(hstore);
+DROP FUNCTION test1list(hstore);
+DROP FUNCTION test1arr(hstore[]);
+DROP FUNCTION test2();
+DROP FUNCTION test2arr();
+DROP FUNCTION test3();
+DROP FUNCTION test4();
+DROP EXTENSION hstore_plperl;
+DROP EXTENSION hstore_plperlu;
+DROP EXTENSION hstore;
+DROP EXTENSION plperl;
+DROP EXTENSION plperlu;
diff --git a/contrib/hstore_plperl/hstore_plperl--1.0.sql b/contrib/hstore_plperl/hstore_plperl--1.0.sql
new file mode 100644 (file)
index 0000000..ea0ad76
--- /dev/null
@@ -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 (file)
index 0000000..cdc224c
--- /dev/null
@@ -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 (file)
index 0000000..16277f6
--- /dev/null
@@ -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 (file)
index 0000000..46ad35c
--- /dev/null
@@ -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 (file)
index 0000000..c8d43b4
--- /dev/null
@@ -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 (file)
index 0000000..40477e3
--- /dev/null
@@ -0,0 +1,47 @@
+-- general regression test for transforms
+
+DROP EXTENSION IF EXISTS hstore CASCADE;
+DROP EXTENSION IF EXISTS plperl CASCADE;
+DROP EXTENSION IF EXISTS hstore_plperl CASCADE;
+
+CREATE EXTENSION hstore;
+CREATE EXTENSION plperl;
+
+CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
+LANGUAGE C STRICT IMMUTABLE
+AS '$libdir/hstore_plperl';
+
+CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
+LANGUAGE C STRICT IMMUTABLE
+AS '$libdir/hstore_plperl';
+
+CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+
+CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- ok
+CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- fail
+CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- ok
+CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal));  -- ok
+CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal));  -- ok
+
+DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl;
+DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang;
+DROP TRANSFORM FOR foo LANGUAGE plperl;
+DROP TRANSFORM FOR hstore LANGUAGE foo;
+DROP TRANSFORM FOR hstore LANGUAGE plperl;
+DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl;
+
+DROP FUNCTION hstore_to_plperl(val internal);
+DROP FUNCTION plperl_to_hstore(val internal);
+
+CREATE EXTENSION hstore_plperl;
+\dx+ hstore_plperl
+ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl;
+\dx+ hstore_plperl
+ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl;
+\dx+ hstore_plperl
+
+DROP EXTENSION hstore CASCADE;
+DROP EXTENSION plperl CASCADE;
diff --git a/contrib/hstore_plperl/sql/hstore_plperl.sql b/contrib/hstore_plperl/sql/hstore_plperl.sql
new file mode 100644 (file)
index 0000000..d42311b
--- /dev/null
@@ -0,0 +1,148 @@
+CREATE EXTENSION hstore_plperl;
+CREATE EXTENSION hstore_plperlu;
+
+SELECT transforms.udt_schema, transforms.udt_name,
+       routine_schema, routine_name,
+       group_name, transform_type
+FROM information_schema.transforms JOIN information_schema.routines
+     USING (specific_catalog, specific_schema, specific_name)
+ORDER BY 1, 2, 5, 6;
+
+
+-- test hstore -> perl
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_[0]));
+return scalar(keys %{$_[0]});
+$$;
+
+SELECT test1('aa=>bb, cc=>NULL'::hstore);
+
+CREATE FUNCTION test1none(val hstore) RETURNS int
+LANGUAGE plperlu
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_[0]));
+return scalar(keys %{$_[0]});
+$$;
+
+SELECT test1none('aa=>bb, cc=>NULL'::hstore);
+
+CREATE FUNCTION test1list(val hstore) RETURNS int
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_[0]));
+return scalar(keys %{$_[0]});
+$$;
+
+SELECT test1list('aa=>bb, cc=>NULL'::hstore);
+
+
+-- test hstore[] -> perl
+CREATE FUNCTION test1arr(val hstore[]) RETURNS int
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_[0]->[0], $_[0]->[1]));
+return scalar(keys %{$_[0]});
+$$;
+
+SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
+
+
+-- test perl -> hstore
+CREATE FUNCTION test2() RETURNS hstore
+LANGUAGE plperl
+TRANSFORM FOR TYPE hstore
+AS $$
+$val = {a => 1, b => 'boo', c => undef};
+return $val;
+$$;
+
+SELECT test2();
+
+
+-- test perl -> hstore[]
+CREATE FUNCTION test2arr() RETURNS hstore[]
+LANGUAGE plperl
+TRANSFORM FOR TYPE hstore
+AS $$
+$val = [{a => 1, b => 'boo', c => undef}, {d => 2}];
+return $val;
+$$;
+
+SELECT test2arr();
+
+
+-- test as part of prepare/execute
+CREATE FUNCTION test3() RETURNS void
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+
+$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1});
+elog(INFO, Dumper($rv->{rows}[0]->{col1}));
+
+$val = {a => 1, b => 'boo', c => undef};
+$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore");
+$rv = spi_exec_prepared($plan, {}, $val);
+elog(INFO, Dumper($rv->{rows}[0]->{col1}));
+$$;
+
+SELECT test3();
+
+
+-- test trigger
+CREATE TABLE test1 (a int, b hstore);
+INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
+SELECT * FROM test1;
+
+CREATE FUNCTION test4() RETURNS trigger
+LANGUAGE plperlu
+TRANSFORM FOR TYPE hstore
+AS $$
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+elog(INFO, Dumper($_TD->{new}));
+if ($_TD->{new}{a} == 1) {
+    $_TD->{new}{b} = {a => 1, b => 'boo', c => undef};
+}
+
+return "MODIFY";
+$$;
+
+CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
+
+UPDATE test1 SET a = a;
+SELECT * FROM test1;
+
+
+DROP TABLE test1;
+
+DROP FUNCTION test1(hstore);
+DROP FUNCTION test1none(hstore);
+DROP FUNCTION test1list(hstore);
+DROP FUNCTION test1arr(hstore[]);
+DROP FUNCTION test2();
+DROP FUNCTION test2arr();
+DROP FUNCTION test3();
+DROP FUNCTION test4();
+
+
+DROP EXTENSION hstore_plperl;
+DROP EXTENSION hstore_plperlu;
+DROP EXTENSION hstore;
+DROP EXTENSION plperl;
+DROP EXTENSION plperlu;
diff --git a/contrib/hstore_plpython/.gitignore b/contrib/hstore_plpython/.gitignore
new file mode 100644 (file)
index 0000000..ce6fab9
--- /dev/null
@@ -0,0 +1,6 @@
+# Generated subdirectories
+/expected/python3/
+/log/
+/results/
+/sql/python3/
+/tmp_check/
diff --git a/contrib/hstore_plpython/Makefile b/contrib/hstore_plpython/Makefile
new file mode 100644 (file)
index 0000000..f5a6a88
--- /dev/null
@@ -0,0 +1,31 @@
+# contrib/hstore_plpython/Makefile
+
+MODULE_big = hstore_plpython$(python_majorversion)
+OBJS = hstore_plpython.o
+
+PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/hstore
+
+EXTENSION = hstore_plpythonu hstore_plpython2u hstore_plpython3u
+DATA = hstore_plpythonu--1.0.sql hstore_plpython2u--1.0.sql hstore_plpython3u--1.0.sql
+
+REGRESS = hstore_plpython
+REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/hstore_plpython
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+REGRESS_OPTS = --load-extension=hstore
+ifeq ($(python_majorversion),2)
+REGRESS_OPTS += --load-extension=plpythonu --load-extension=hstore_plpythonu
+endif
+EXTRA_INSTALL = contrib/hstore
+
+include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk
diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out b/contrib/hstore_plpython/expected/hstore_plpython.out
new file mode 100644 (file)
index 0000000..488f01d
--- /dev/null
@@ -0,0 +1,132 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION hstore_plpython2u;
+-- test hstore -> python
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+assert isinstance(val, dict)
+plpy.info(sorted(val.items()))
+return len(val)
+$$;
+SELECT test1('aa=>bb, cc=>NULL'::hstore);
+INFO:  [('aa', 'bb'), ('cc', None)]
+CONTEXT:  PL/Python function "test1"
+ test1 
+-------
+     2
+(1 row)
+
+-- the same with the versioned language name
+CREATE FUNCTION test1n(val hstore) RETURNS int
+LANGUAGE plpython2u
+TRANSFORM FOR TYPE hstore
+AS $$
+assert isinstance(val, dict)
+plpy.info(sorted(val.items()))
+return len(val)
+$$;
+SELECT test1n('aa=>bb, cc=>NULL'::hstore);
+INFO:  [('aa', 'bb'), ('cc', None)]
+CONTEXT:  PL/Python function "test1n"
+ test1n 
+--------
+      2
+(1 row)
+
+-- test hstore[] -> python
+CREATE FUNCTION test1arr(val hstore[]) RETURNS int
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+plpy.info(repr(val))
+return len(val)
+$$;
+SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
+INFO:  [{'aa': 'bb', 'cc': None}, {'dd': 'ee'}]
+CONTEXT:  PL/Python function "test1arr"
+ test1arr 
+----------
+        2
+(1 row)
+
+-- test python -> hstore
+CREATE FUNCTION test2() RETURNS hstore
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+val = {'a': 1, 'b': 'boo', 'c': None}
+return val
+$$;
+SELECT test2();
+              test2              
+---------------------------------
+ "a"=>"1", "b"=>"boo", "c"=>NULL
+(1 row)
+
+-- test python -> hstore[]
+CREATE FUNCTION test2arr() RETURNS hstore[]
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
+return val
+$$;
+ SELECT test2arr();
+                           test2arr                           
+--------------------------------------------------------------
+ {"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
+(1 row)
+
+-- test as part of prepare/execute
+CREATE FUNCTION test3() RETURNS void
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1")
+plpy.info(repr(rv[0]["col1"]))
+
+val = {'a': 1, 'b': 'boo', 'c': None}
+plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"])
+rv = plpy.execute(plan, [val])
+plpy.info(repr(rv[0]["col1"]))
+$$;
+SELECT test3();
+INFO:  {'aa': 'bb', 'cc': None}
+CONTEXT:  PL/Python function "test3"
+INFO:  '"a"=>"1", "b"=>"boo", "c"=>NULL'
+CONTEXT:  PL/Python function "test3"
+ test3 
+-------
+(1 row)
+
+-- test trigger
+CREATE TABLE test1 (a int, b hstore);
+INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
+SELECT * FROM test1;
+ a |           b            
+---+------------------------
+ 1 | "aa"=>"bb", "cc"=>NULL
+(1 row)
+
+CREATE FUNCTION test4() RETURNS trigger
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+plpy.info("Trigger row: {'a': %r, 'b': %r}" % (TD["new"]["a"], TD["new"]["b"]))
+if TD["new"]["a"] == 1:
+    TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None}
+
+return "MODIFY"
+$$;
+CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
+UPDATE test1 SET a = a;
+INFO:  Trigger row: {'a': 1, 'b': {'aa': 'bb', 'cc': None}}
+CONTEXT:  PL/Python function "test4"
+SELECT * FROM test1;
+ a |                b                
+---+---------------------------------
+ 1 | "a"=>"1", "b"=>"boo", "c"=>NULL
+(1 row)
+
diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c
new file mode 100644 (file)
index 0000000..92cd4f8
--- /dev/null
@@ -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 (file)
index 0000000..c998de5
--- /dev/null
@@ -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 (file)
index 0000000..ed90567
--- /dev/null
@@ -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 (file)
index 0000000..61d0e47
--- /dev/null
@@ -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 (file)
index 0000000..d86f38e
--- /dev/null
@@ -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 (file)
index 0000000..6acb97a
--- /dev/null
@@ -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 (file)
index 0000000..8e9b35e
--- /dev/null
@@ -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 (file)
index 0000000..2d8aab1
--- /dev/null
@@ -0,0 +1,103 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION hstore_plpython2u;
+
+
+-- test hstore -> python
+CREATE FUNCTION test1(val hstore) RETURNS int
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+assert isinstance(val, dict)
+plpy.info(sorted(val.items()))
+return len(val)
+$$;
+
+SELECT test1('aa=>bb, cc=>NULL'::hstore);
+
+
+-- the same with the versioned language name
+CREATE FUNCTION test1n(val hstore) RETURNS int
+LANGUAGE plpython2u
+TRANSFORM FOR TYPE hstore
+AS $$
+assert isinstance(val, dict)
+plpy.info(sorted(val.items()))
+return len(val)
+$$;
+
+SELECT test1n('aa=>bb, cc=>NULL'::hstore);
+
+
+-- test hstore[] -> python
+CREATE FUNCTION test1arr(val hstore[]) RETURNS int
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+plpy.info(repr(val))
+return len(val)
+$$;
+
+SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
+
+
+-- test python -> hstore
+CREATE FUNCTION test2() RETURNS hstore
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+val = {'a': 1, 'b': 'boo', 'c': None}
+return val
+$$;
+
+SELECT test2();
+
+
+-- test python -> hstore[]
+CREATE FUNCTION test2arr() RETURNS hstore[]
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
+return val
+$$;
+
+ SELECT test2arr();
+
+
+-- test as part of prepare/execute
+CREATE FUNCTION test3() RETURNS void
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1")
+plpy.info(repr(rv[0]["col1"]))
+
+val = {'a': 1, 'b': 'boo', 'c': None}
+plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"])
+rv = plpy.execute(plan, [val])
+plpy.info(repr(rv[0]["col1"]))
+$$;
+
+SELECT test3();
+
+
+-- test trigger
+CREATE TABLE test1 (a int, b hstore);
+INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
+SELECT * FROM test1;
+
+CREATE FUNCTION test4() RETURNS trigger
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+plpy.info("Trigger row: {'a': %r, 'b': %r}" % (TD["new"]["a"], TD["new"]["b"]))
+if TD["new"]["a"] == 1:
+    TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None}
+
+return "MODIFY"
+$$;
+
+CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
+
+UPDATE test1 SET a = a;
+SELECT * FROM test1;
diff --git a/contrib/ltree_plpython/.gitignore b/contrib/ltree_plpython/.gitignore
new file mode 100644 (file)
index 0000000..ce6fab9
--- /dev/null
@@ -0,0 +1,6 @@
+# Generated subdirectories
+/expected/python3/
+/log/
+/results/
+/sql/python3/
+/tmp_check/
diff --git a/contrib/ltree_plpython/Makefile b/contrib/ltree_plpython/Makefile
new file mode 100644 (file)
index 0000000..c358cfa
--- /dev/null
@@ -0,0 +1,31 @@
+# contrib/ltree_plpython/Makefile
+
+MODULE_big = ltree_plpython$(python_majorversion)
+OBJS = ltree_plpython.o
+
+PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/ltree
+
+EXTENSION = ltree_plpythonu ltree_plpython2u ltree_plpython3u
+DATA = ltree_plpythonu--1.0.sql ltree_plpython2u--1.0.sql ltree_plpython3u--1.0.sql
+
+REGRESS = ltree_plpython
+REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/ltree_plpython
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+REGRESS_OPTS = --load-extension=ltree
+ifeq ($(python_majorversion),2)
+REGRESS_OPTS += --load-extension=plpythonu --load-extension=ltree_plpythonu
+endif
+EXTRA_INSTALL = contrib/ltree
+
+include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk
diff --git a/contrib/ltree_plpython/expected/ltree_plpython.out b/contrib/ltree_plpython/expected/ltree_plpython.out
new file mode 100644 (file)
index 0000000..934529e
--- /dev/null
@@ -0,0 +1,45 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION ltree_plpython2u;
+CREATE FUNCTION test1(val ltree) RETURNS int
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE ltree
+AS $$
+plpy.info(repr(val))
+return len(val)
+$$;
+SELECT test1('aa.bb.cc'::ltree);
+INFO:  ['aa', 'bb', 'cc']
+CONTEXT:  PL/Python function "test1"
+ test1 
+-------
+     3
+(1 row)
+
+CREATE FUNCTION test1n(val ltree) RETURNS int
+LANGUAGE plpython2u
+TRANSFORM FOR TYPE ltree
+AS $$
+plpy.info(repr(val))
+return len(val)
+$$;
+SELECT test1n('aa.bb.cc'::ltree);
+INFO:  ['aa', 'bb', 'cc']
+CONTEXT:  PL/Python function "test1n"
+ test1n 
+--------
+      3
+(1 row)
+
+CREATE FUNCTION test2() RETURNS ltree
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE ltree
+AS $$
+return ['foo', 'bar', 'baz']
+$$;
+-- plpython to ltree is not yet implemented, so this will fail,
+-- because it will try to parse the Python list as an ltree input
+-- string.
+SELECT test2();
+ERROR:  syntax error at position 0
+CONTEXT:  while creating return value
+PL/Python function "test2"
diff --git a/contrib/ltree_plpython/ltree_plpython.c b/contrib/ltree_plpython/ltree_plpython.c
new file mode 100644 (file)
index 0000000..111e3e3
--- /dev/null
@@ -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 (file)
index 0000000..29a12d4
--- /dev/null
@@ -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 (file)
index 0000000..bedfd0a
--- /dev/null
@@ -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 (file)
index 0000000..1300a78
--- /dev/null
@@ -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 (file)
index 0000000..96c9764
--- /dev/null
@@ -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 (file)
index 0000000..1d1af28
--- /dev/null
@@ -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 (file)
index 0000000..b03c89a
--- /dev/null
@@ -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 (file)
index 0000000..f08ff6a
--- /dev/null
@@ -0,0 +1,37 @@
+CREATE EXTENSION plpython2u;
+CREATE EXTENSION ltree_plpython2u;
+
+
+CREATE FUNCTION test1(val ltree) RETURNS int
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE ltree
+AS $$
+plpy.info(repr(val))
+return len(val)
+$$;
+
+SELECT test1('aa.bb.cc'::ltree);
+
+
+CREATE FUNCTION test1n(val ltree) RETURNS int
+LANGUAGE plpython2u
+TRANSFORM FOR TYPE ltree
+AS $$
+plpy.info(repr(val))
+return len(val)
+$$;
+
+SELECT test1n('aa.bb.cc'::ltree);
+
+
+CREATE FUNCTION test2() RETURNS ltree
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE ltree
+AS $$
+return ['foo', 'bar', 'baz']
+$$;
+
+-- plpython to ltree is not yet implemented, so this will fail,
+-- because it will try to parse the Python list as an ltree input
+-- string.
+SELECT test2();
index 4e6fd0e06fbc23d255b759d472c83a69e567c10f..898865eea19b127905e3aa2bfd6bcc30fdaf5d46 100644 (file)
       <entry>tablespaces within this database cluster</entry>
      </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>
       </entry>
      </row>
 
+     <row>
+      <entry><structfield>protrftypes</structfield></entry>
+      <entry><type>oid[]</type></entry>
+      <entry></entry>
+      <entry>
+       Data type OIDs for which to apply transforms.
+      </entry>
+     </row>
+
      <row>
       <entry><structfield>prosrc</structfield></entry>
       <entry><type>text</type></entry>
  </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>
 
index fbe9543dfea1be82a21538147a1abaa658e16bfa..94f01f8dfe7e863449435072c030bcec11db85ab 100644 (file)
@@ -596,6 +596,25 @@ ALTER TABLE tablename ALTER hstorecol TYPE hstore USING hstorecol || '';
 
  </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 transforms and specify them when creating a
+   function, <type>hstore</type> values are 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 use them, <type>hstore</type> values are mapped to
+   Python dictionaries.
+  </para>
+ </sect2>
+
  <sect2>
   <title>Authors</title>
 
index 22f43c8a5bf27c1dbc89a47507e9b6349821cb9e..ca1f20b3385e7f90480c8d357ef06f62cb87a4a1 100644 (file)
@@ -5519,6 +5519,91 @@ ORDER BY c.ordinal_position;
   </table>
  </sect1>
 
+ <sect1 id="infoschema-transforms">
+  <title><literal>transforms</literal></title>
+
+  <para>
+   The view <literal>transforms</literal> contains information about the
+   transforms defined in the current database.  More precisely, it contains a
+   row for each function contained in a transform (the <quote>from SQL</quote>
+   or <quote>to SQL</quote> function).
+  </para>
+
+  <table>
+   <title><literal>transforms</literal> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Data Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><literal>udt_catalog</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the database that contains the type the transform is for (always the current database)</entry>
+     </row>
+
+     <row>
+      <entry><literal>udt_schema</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the schema that contains the type the transform is for</entry>
+     </row>
+
+     <row>
+      <entry><literal>udt_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the type the transform is for</entry>
+     </row>
+
+     <row>
+      <entry><literal>specific_catalog</literal></entry>
+      <entry><literal>sql_identifier</literal></entry>
+      <entry>Name of the database containing the function (always the current database)</entry>
+     </row>
+
+     <row>
+      <entry><literal>specific_schema</literal></entry>
+      <entry><literal>sql_identifier</literal></entry>
+      <entry>Name of the schema containing the function</entry>
+     </row>
+
+     <row>
+      <entry><literal>specific_name</literal></entry>
+      <entry><literal>sql_identifier</literal></entry>
+      <entry>
+       The <quote>specific name</quote> of the function.  See <xref
+       linkend="infoschema-routines"> for more information.
+      </entry>
+     </row>
+
+     <row>
+      <entry><literal>group_name</literal></entry>
+      <entry><literal>sql_identifier</literal></entry>
+      <entry>
+       The SQL standard allows defining transforms in <quote>groups</quote>,
+       and selecting a group at run time.  PostgreSQL does not support this.
+       Instead, transforms are specific to a language.  As a compromise, this
+       field contains the language the transform is for.
+      </entry>
+     </row>
+
+     <row>
+      <entry><literal>transform_type</literal></entry>
+      <entry><type>character_data</type></entry>
+      <entry>
+       <literal>FROM SQL</literal> or <literal>TO SQL</literal>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="infoschema-triggered-update-columns">
   <title><literal>triggered_update_columns</literal></title>
 
index cd8a061c94398122a52a93403cf9b620b570d559..3f87319bf96813edfdc0cd736effa01f6a4491fc 100644 (file)
@@ -664,6 +664,21 @@ ltreetest=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   </para>
  </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 these transforms and specify them when
+   creating a function, <type>ltree</type> values are mapped to Python lists.
+   (The reverse is currently not supported, however.)
+  </para>
+ </sect2>
+
  <sect2>
   <title>Authors</title>
 
index f09fc250f0862733ca7d8ac7df5720da4875078b..bf95453b6c6d597ad8c66c8ccb04c35b5f68fd24 100644 (file)
@@ -79,6 +79,7 @@ Complete list of usable sgml source files in this directory.
 <!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">
@@ -120,6 +121,7 @@ Complete list of usable sgml source files in this directory.
 <!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">
index 0d479c8ca2d03cc2dbcdb23ceeabc18812bf8e90..7141ee352eb5b3796ee5d6edd4d9196c4e99e002 100644 (file)
@@ -52,6 +52,7 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
   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>
 
@@ -259,6 +260,26 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
       </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>
index 62e1968c08d7ee7dc1a2648483be9f5b40a9bda1..656f5aae5babac6a38f28abcca6cf60d0a12fb24 100644 (file)
@@ -55,6 +55,7 @@ COMMENT ON
   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>
@@ -225,6 +226,26 @@ COMMENT ON
      </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>
@@ -305,6 +326,7 @@ COMMENT ON TEXT SEARCH CONFIGURATION my_config IS 'Special word filtering';
 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';
index 2001921535199af851e5311c3a1d0b00d862a681..c5beb166cfaf291c4967ff962f262ae9ff0904b8 100644 (file)
@@ -25,6 +25,7 @@ CREATE [ OR REPLACE ] FUNCTION
     [ RETURNS <replaceable class="parameter">rettype</replaceable>
       | RETURNS TABLE ( <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">column_type</replaceable> [, ...] ) ]
   { LANGUAGE <replaceable class="parameter">lang_name</replaceable>
+    | TRANSFORM { FOR TYPE <replaceable class="parameter">type_name</replaceable> } [, ... ]
     | WINDOW
     | IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF
     | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
@@ -260,6 +261,23 @@ CREATE [ OR REPLACE ] FUNCTION
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>TRANSFORM { FOR TYPE <replaceable class="parameter">type_name</replaceable> } [, ... ] }</literal></term>
+
+     <listitem>
+      <para>
+       Lists which transforms a call to the function should apply.  Transforms
+       convert between SQL types and language-specific data types;
+       see <xref linkend="sql-createtransform">.  Procedural language
+       implementations usually have hardcoded knowledge of the built-in types,
+       so those don't need to be listed here.  If a procedural language
+       implementation does not know how to handle a type and no transform is
+       supplied, it will fall back to a default behavior for converting data
+       types, but this depends on the implementation.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><literal>WINDOW</literal></term>
 
diff --git a/doc/src/sgml/ref/create_transform.sgml b/doc/src/sgml/ref/create_transform.sgml
new file mode 100644 (file)
index 0000000..d321dad
--- /dev/null
@@ -0,0 +1,207 @@
+<!-- doc/src/sgml/ref/create_transform.sgml -->
+
+<refentry id="SQL-CREATETRANSFORM">
+ <indexterm zone="sql-createtransform">
+  <primary>CREATE TRANSFORM</primary>
+ </indexterm>
+
+ <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>
+
+ <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 <type>hstore</type> type, PL/Python has no prior knowledge how to
+   present <type>hstore</type> 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.
+  </para>
+
+  <para>
+   A transform specifies two functions:
+   <itemizedlist>
+    <listitem>
+     <para>
+      A <quote>from SQL</quote> function that converts the type from the SQL
+      environment to the language.  This function will be invoked on the
+      arguments of a function written in the language.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      A <quote>to SQL</quote> function that converts the type from the
+      language to the SQL environment.  This function will be invoked on the
+      return value of a function written in the language.
+     </para>
+    </listitem>
+   </itemizedlist>
+   It is not necessary to provide both of these functions.  If one is not
+   specified, the language-specific default behavior will be used if
+   necessary.  (To prevent a transformation in a certain direction from
+   happening at all, you could also write a transform function that always
+   errors out.)
+  </para>
+
+  <para>
+   To be able to create a transform, you must own and
+   have <literal>USAGE</literal> privilege on the type, have
+   <literal>USAGE</literal> privilege on the language, and own and
+   have <literal>EXECUTE</literal> privilege on the from-SQL and to-SQL
+   functions, if specified.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+   <variablelist>
+    <varlistentry>
+     <term><replaceable>type_name</replaceable></term>
+
+     <listitem>
+      <para>
+       The name of the data type of the transform.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>lang_name</replaceable></term>
+
+     <listitem>
+      <para>
+       The name of the language of the transform.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>from_sql_function_name</replaceable>(<replaceable>argument_type</replaceable> [, ...])</term>
+
+     <listitem>
+      <para>
+       The name of the function for converting the type from the SQL
+       environment to the language.  It must take one argument of
+       type <type>internal</type> and return type <type>internal</type>.  The
+       actual argument will be of the type for the transform, and the function
+       should be coded as if it were.  (But it is not allowed to declare an
+       SQL-level function function returning <type>internal</type> without at
+       least one argument of type <type>internal</type>.)  The actual return
+       value will be something specific to the language implementation.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>to_sql_function_name</replaceable>(<replaceable>argument_type</replaceable> [, ...])</term>
+
+     <listitem>
+      <para>
+       The name of the function for converting the type from the language to
+       the SQL environment.  It must take one argument of type
+       <type>internal</type> and return the type that is the type for the
+       transform.  The actual argument value will be something specific to the
+       language implementation.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ </refsect1>
+
+ <refsect1 id="sql-createtransform-notes">
+  <title>Notes</title>
+
+  <para>
+   Use <xref linkend="sql-droptransform"> to remove transforms.
+  </para>
+ </refsect1>
+
+ <refsect1 id="sql-createtransform-examples">
+  <title>Examples</title>
+
+  <para>
+   To create a transform for type <type>hstore</type> and language
+   <literal>plpythonu</literal>, first set up the type and the language:
+<programlisting>
+CREATE TYPE hstore ...;
+
+CREATE LANGUAGE plpythonu ...;
+</programlisting>
+   Then create the necessary functions:
+<programlisting>
+CREATE FUNCTION hstore_to_plpython(val internal) RETURNS internal
+LANGUAGE C STRICT IMMUTABLE
+AS ...;
+
+CREATE FUNCTION plpython_to_hstore(val internal) RETURNS hstore
+LANGUAGE C STRICT IMMUTABLE
+AS ...;
+</programlisting>
+   And finally create the transform to connect them all together:
+<programlisting>
+CREATE TRANSFORM FOR hstore LANGUAGE plpythonu (
+    FROM SQL WITH FUNCTION hstore_to_plpython(internal),
+    TO SQL WITH FUNCTION plpython_to_hstore(internal)
+);
+</programlisting>
+   In practice, these commands would be wrapped up in extensions.
+  </para>
+
+  <para>
+   The <filename>contrib</filename> section contains a number of extensions
+   that provide transforms, which can serve as real-world examples.
+  </para>
+ </refsect1>
+
+ <refsect1 id="sql-createtransform-compat">
+  <title>Compatibility</title>
+
+  <para>
+   This form of <command>CREATE TRANSFORM</command> is a
+   <productname>PostgreSQL</productname> extension.  There is a <command>CREATE
+   TRANSFORM</command> command in the <acronym>SQL</acronym> standard, but it
+   is for adapting data types to client languages.  That usage is not supported
+   by <productname>PostgreSQL</productname>.
+  </para>
+ </refsect1>
+
+ <refsect1 id="sql-createtransform-seealso">
+  <title>See Also</title>
+
+  <para>
+   <xref linkend="sql-createfunction">,
+   <xref linkend="sql-createlanguage">,
+   <xref linkend="sql-createtype">,
+   <xref linkend="sql-droptransform">
+  </para>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_transform.sgml b/doc/src/sgml/ref/drop_transform.sgml
new file mode 100644 (file)
index 0000000..59ff87c
--- /dev/null
@@ -0,0 +1,123 @@
+<!-- doc/src/sgml/ref/drop_transform.sgml -->
+
+<refentry id="SQL-DROPTRANSFORM">
+ <indexterm zone="sql-droptransform">
+  <primary>DROP TRANSFORM</primary>
+ </indexterm>
+
+ <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>
+
+ <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>
index c52eb28c8c4ffc0318a5b6b4ad9a81b3b3eb2493..03020dfec42ace90b4ad1695b66f7c475ad3ca46 100644 (file)
    &createTSDictionary;
    &createTSParser;
    &createTSTemplate;
+   &createTransform;
    &createTrigger;
    &createType;
    &createUser;
    &dropTSDictionary;
    &dropTSParser;
    &dropTSTemplate;
+   &dropTransform;
    &dropTrigger;
    &dropType;
    &dropUser;
index f96c7092d8a34464cd7217c3d6220462e01a049b..50c3805eec02f23fc2683ed2e05b9d875412f1e0 100644 (file)
@@ -127,7 +127,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)
index a403c643600b82201ff6e8f0f7e310db6054839a..c73f20d6a5ea6764b7f20e0cabe39752735d44bb 100644 (file)
@@ -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_policy.h \
        pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
+       pg_transform.h \
        toasting.h indexing.h \
     )
 
index bacb242e4e14c434717899c5451c6d46d4938bfa..6271f8f5a036c0cf2174d241ecfd044153d1ace3 100644 (file)
@@ -47,6 +47,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"
@@ -1265,6 +1266,10 @@ doDeletion(const ObjectAddress *object, int flags)
                        RemovePolicyById(object->objectId);
                        break;
 
+               case OCLASS_TRANSFORM:
+                       DropTransformById(object->objectId);
+                       break;
+
                default:
                        elog(ERROR, "unrecognized object class: %u",
                                 object->classId);
@@ -2373,6 +2378,9 @@ getObjectClass(const ObjectAddress *object)
 
                case PolicyRelationId:
                        return OCLASS_POLICY;
+
+               case TransformRelationId:
+                       return OCLASS_TRANSFORM;
        }
 
        /* shouldn't get here */
index 3c6a049b6dae11cabf55f76aba5052e3113b9997..6e1b241956a5fa2575a2af6e5794f1aff158dfa5 100644 (file)
@@ -1928,7 +1928,39 @@ GRANT SELECT ON tables TO PUBLIC;
  * TRANSFORMS view
  */
 
--- feature not supported
+CREATE VIEW transforms AS
+    SELECT CAST(current_database() AS sql_identifier) AS udt_catalog,
+           CAST(nt.nspname AS sql_identifier) AS udt_schema,
+           CAST(t.typname AS sql_identifier) AS udt_name,
+           CAST(current_database() AS sql_identifier) AS specific_catalog,
+           CAST(np.nspname AS sql_identifier) AS specific_schema,
+           CAST(p.proname || '_' || CAST(p.oid AS text) AS sql_identifier) AS specific_name,
+           CAST(l.lanname AS sql_identifier) AS group_name,
+           CAST('FROM SQL' AS character_data) AS transform_type
+    FROM pg_type t JOIN pg_transform x ON t.oid = x.trftype
+         JOIN pg_language l ON x.trflang = l.oid
+         JOIN pg_proc p ON x.trffromsql = p.oid
+         JOIN pg_namespace nt ON t.typnamespace = nt.oid
+         JOIN pg_namespace np ON p.pronamespace = np.oid
+
+  UNION
+
+    SELECT CAST(current_database() AS sql_identifier) AS udt_catalog,
+           CAST(nt.nspname AS sql_identifier) AS udt_schema,
+           CAST(t.typname AS sql_identifier) AS udt_name,
+           CAST(current_database() AS sql_identifier) AS specific_catalog,
+           CAST(np.nspname AS sql_identifier) AS specific_schema,
+           CAST(p.proname || '_' || CAST(p.oid AS text) AS sql_identifier) AS specific_name,
+           CAST(l.lanname AS sql_identifier) AS group_name,
+           CAST('TO SQL' AS character_data) AS transform_type
+    FROM pg_type t JOIN pg_transform x ON t.oid = x.trftype
+         JOIN pg_language l ON x.trflang = l.oid
+         JOIN pg_proc p ON x.trftosql = p.oid
+         JOIN pg_namespace nt ON t.typnamespace = nt.oid
+         JOIN pg_namespace np ON p.pronamespace = np.oid
+
+  ORDER BY udt_catalog, udt_schema, udt_name, group_name, transform_type  -- some sensible grouping for interactive use
+;
 
 
 /*
index 30cb6990726945d4aa81e6424a0c51f7d5db5526..5e1bda4ed2cf47b93d54000b770eafa343f51093 100644 (file)
@@ -45,6 +45,7 @@
 #include "catalog/pg_policy.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"
@@ -334,6 +335,12 @@ static const ObjectPropertyType ObjectProperty[] =
                ACL_KIND_TABLESPACE,
                true
        },
+       {
+               TransformRelationId,
+               TransformOidIndexId,
+               TRFOID,
+               InvalidAttrNumber
+       },
        {
                TriggerRelationId,
                TriggerOidIndexId,
@@ -760,6 +767,19 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
                                        address.objectSubId = 0;
                                }
                                break;
+                       case OBJECT_TRANSFORM:
+                               {
+                                       TypeName   *typename = (TypeName *) linitial(objname);
+                                       char       *langname = (char *) linitial(objargs);
+                                       Oid                     type_id = LookupTypeNameOid(NULL, typename, missing_ok);
+                                       Oid                     lang_id = get_language_oid(langname, missing_ok);
+
+                                       address.classId = TransformRelationId;
+                                       address.objectId =
+                                               get_transform_oid(type_id, lang_id, missing_ok);
+                                       address.objectSubId = 0;
+                               }
+                               break;
                        case OBJECT_TSPARSER:
                                address.classId = TSParserRelationId;
                                address.objectId = get_ts_parser_oid(objname, missing_ok);
@@ -2006,6 +2026,15 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
                                                                        format_type_be(targettypeid))));
                        }
                        break;
+               case OBJECT_TRANSFORM:
+                       {
+                               TypeName   *typename = (TypeName *) linitial(objname);
+                               Oid                     typeid = typenameTypeId(NULL, typename);
+
+                               if (!pg_type_ownercheck(typeid, roleid))
+                                       aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
+                       }
+                       break;
                case OBJECT_TABLESPACE:
                        if (!pg_tablespace_ownercheck(address.objectId, roleid))
                                aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
@@ -2467,19 +2496,10 @@ getObjectDescription(const ObjectAddress *object)
                        }
 
                case OCLASS_LANGUAGE:
-                       {
-                               HeapTuple       langTup;
+                       appendStringInfo(&buffer, _("language %s"),
+                                                        get_language_name(object->objectId, false));
+                       break;
 
-                               langTup = SearchSysCache1(LANGOID,
-                                                                                 ObjectIdGetDatum(object->objectId));
-                               if (!HeapTupleIsValid(langTup))
-                                       elog(ERROR, "cache lookup failed for language %u",
-                                                object->objectId);
-                               appendStringInfo(&buffer, _("language %s"),
-                                 NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname));
-                               ReleaseSysCache(langTup);
-                               break;
-                       }
                case OCLASS_LARGEOBJECT:
                        appendStringInfo(&buffer, _("large object %u"),
                                                         object->objectId);
@@ -2667,6 +2687,27 @@ getObjectDescription(const ObjectAddress *object)
                                break;
                        }
 
+               case OCLASS_TRANSFORM:
+                       {
+                               HeapTuple       trfTup;
+                               Form_pg_transform trfForm;
+
+                               trfTup = SearchSysCache1(TRFOID,
+                                                                                 ObjectIdGetDatum(object->objectId));
+                               if (!HeapTupleIsValid(trfTup))
+                                       elog(ERROR, "could not find tuple for transform %u",
+                                                object->objectId);
+
+                               trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
+
+                               appendStringInfo(&buffer, _("transform for %s language %s"),
+                                                                format_type_be(trfForm->trftype),
+                                                                get_language_name(trfForm->trflang, false));
+
+                               ReleaseSysCache(trfTup);
+                               break;
+                       }
+
                case OCLASS_TRIGGER:
                        {
                                Relation        trigDesc;
index df7bdfb03ca80a2d7b01d7a30e09041c475d1baa..5f211dacde2bd7a090cbc0bfe689258d789c19e1 100644 (file)
@@ -545,6 +545,7 @@ AggregateCreate(const char *aggName,
                                                         parameterModes,        /* parameterModes */
                                                         parameterNames,        /* parameterNames */
                                                         parameterDefaults, /* parameterDefaults */
+                                                        PointerGetDatum(NULL), /* trftypes */
                                                         PointerGetDatum(NULL),         /* proconfig */
                                                         1, /* procost */
                                                         0);    /* prorows */
index fd5060aa7c176b9222be50bcad3e880235a15042..122982951e2e43365c1a6999a594fe08e810e74f 100644 (file)
@@ -23,7 +23,9 @@
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_proc_fn.h"
+#include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "executor/functions.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
@@ -59,7 +61,7 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
 /* ----------------------------------------------------------------
  *             ProcedureCreate
  *
- * Note: allParameterTypes, parameterModes, parameterNames, and proconfig
+ * Note: allParameterTypes, parameterModes, parameterNames, trftypes, and proconfig
  * are either arrays of the proper types or NULL.  We declare them Datum,
  * not "ArrayType *", to avoid importing array.h into pg_proc_fn.h.
  * ----------------------------------------------------------------
@@ -86,6 +88,7 @@ ProcedureCreate(const char *procedureName,
                                Datum parameterModes,
                                Datum parameterNames,
                                List *parameterDefaults,
+                               Datum trftypes,
                                Datum proconfig,
                                float4 procost,
                                float4 prorows)
@@ -116,6 +119,7 @@ ProcedureCreate(const char *procedureName,
        ObjectAddress myself,
                                referenced;
        int                     i;
+       Oid                     trfid;
 
        /*
         * sanity checks
@@ -360,6 +364,10 @@ ProcedureCreate(const char *procedureName,
                values[Anum_pg_proc_proargdefaults - 1] = CStringGetTextDatum(nodeToString(parameterDefaults));
        else
                nulls[Anum_pg_proc_proargdefaults - 1] = true;
+       if (trftypes != PointerGetDatum(NULL))
+               values[Anum_pg_proc_protrftypes - 1] = trftypes;
+       else
+               nulls[Anum_pg_proc_protrftypes - 1] = true;
        values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
        if (probin)
                values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
@@ -624,6 +632,15 @@ ProcedureCreate(const char *procedureName,
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
+       /* dependency on transform used by return type, if any */
+       if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
+       {
+               referenced.classId = TransformRelationId;
+               referenced.objectId = trfid;
+               referenced.objectSubId = 0;
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+       }
+
        /* dependency on parameter types */
        for (i = 0; i < allParamCount; i++)
        {
@@ -631,6 +648,15 @@ ProcedureCreate(const char *procedureName,
                referenced.objectId = allParams[i];
                referenced.objectSubId = 0;
                recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+               /* dependency on transform used by parameter type, if any */
+               if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
+               {
+                       referenced.classId = TransformRelationId;
+                       referenced.objectId = trfid;
+                       referenced.objectSubId = 0;
+                       recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+               }
        }
 
        /* dependency on parameter default expressions */
@@ -1128,3 +1154,21 @@ fail:
        *newcursorpos = newcp;
        return false;
 }
+
+List *
+oid_array_to_list(Datum datum)
+{
+       ArrayType *array = DatumGetArrayTypeP(datum);
+       Datum *values;
+       int nelems;
+       int i;
+       List *result = NIL;
+
+       deconstruct_array(array,
+                                         OIDOID,
+                                         sizeof(Oid), true, 'i',
+                                         &values, NULL, &nelems);
+       for (i = 0; i < nelems; i++)
+               result = lappend_oid(result, values[i]);
+       return result;
+}
index a1b0d4d2fa2ad4a2a57bb6138b0519a1a7c40769..80a0eb6e9e787a4d3cca0f8dfe1e81a6d47e4001 100644 (file)
@@ -366,6 +366,14 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
                                }
                        }
                        break;
+               case OBJECT_TRANSFORM:
+                       if (!type_in_list_does_not_exist_skipping(objname, &msg, &name))
+                       {
+                               msg = gettext_noop("transform for type %s language %s does not exist, skipping");
+                               name = TypeNameToString((TypeName *) linitial(objname));
+                               args = (char *) linitial(objargs);
+                       }
+                       break;
                case OBJECT_TRIGGER:
                        if (!owningrel_does_not_exist_skipping(objname, &msg, &name))
                        {
index f07fd06337b79b3744b09a75fd63668bc15f3f46..0110b0603d3ce12c47e295a5a808898f588398cc 100644 (file)
@@ -98,6 +98,7 @@ static event_trigger_support_data event_trigger_support[] = {
        {"SERVER", true},
        {"TABLE", true},
        {"TABLESPACE", false},
+       {"TRANSFORM", true},
        {"TRIGGER", true},
        {"TEXT SEARCH CONFIGURATION", true},
        {"TEXT SEARCH DICTIONARY", true},
@@ -1090,6 +1091,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
                case OBJECT_SEQUENCE:
                case OBJECT_TABCONSTRAINT:
                case OBJECT_TABLE:
+               case OBJECT_TRANSFORM:
                case OBJECT_TRIGGER:
                case OBJECT_TSCONFIGURATION:
                case OBJECT_TSDICTIONARY:
@@ -1137,6 +1139,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
                case OCLASS_REWRITE:
                case OCLASS_TRIGGER:
                case OCLASS_SCHEMA:
+               case OCLASS_TRANSFORM:
                case OCLASS_TSPARSER:
                case OCLASS_TSDICT:
                case OCLASS_TSTEMPLATE:
index f4725056da0d365307df5644eca8d8797dec5c3d..9a92fdcff7d2b9ff87bdc1c173009c81e32c76b8 100644 (file)
@@ -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"
@@ -583,6 +584,7 @@ static void
 compute_attributes_sql_style(List *options,
                                                         List **as,
                                                         char **language,
+                                                        Node **transform,
                                                         bool *windowfunc_p,
                                                         char *volatility_p,
                                                         bool *strict_p,
@@ -595,6 +597,7 @@ compute_attributes_sql_style(List *options,
        ListCell   *option;
        DefElem    *as_item = NULL;
        DefElem    *language_item = NULL;
+       DefElem    *transform_item = NULL;
        DefElem    *windowfunc_item = NULL;
        DefElem    *volatility_item = NULL;
        DefElem    *strict_item = NULL;
@@ -624,6 +627,14 @@ compute_attributes_sql_style(List *options,
                                                 errmsg("conflicting or redundant options")));
                        language_item = defel;
                }
+               else if (strcmp(defel->defname, "transform") == 0)
+               {
+                       if (transform_item)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       transform_item = defel;
+               }
                else if (strcmp(defel->defname, "window") == 0)
                {
                        if (windowfunc_item)
@@ -671,6 +682,8 @@ compute_attributes_sql_style(List *options,
        }
 
        /* process optional items */
+       if (transform_item)
+               *transform = transform_item->arg;
        if (windowfunc_item)
                *windowfunc_p = intVal(windowfunc_item->arg);
        if (volatility_item)
@@ -807,7 +820,6 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
 }
 
 
-
 /*
  * CreateFunction
  *      Execute a CREATE FUNCTION utility statement.
@@ -822,6 +834,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
        char       *language;
        Oid                     languageOid;
        Oid                     languageValidator;
+       Node       *transformDefElem = NULL;
        char       *funcname;
        Oid                     namespaceId;
        AclResult       aclresult;
@@ -831,6 +844,8 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
        ArrayType  *parameterNames;
        List       *parameterDefaults;
        Oid                     variadicArgType;
+       List       *trftypes_list = NIL;
+       ArrayType  *trftypes;
        Oid                     requiredResultType;
        bool            isWindowFunc,
                                isStrict,
@@ -866,7 +881,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
 
        /* override attributes from explicit list */
        compute_attributes_sql_style(stmt->options,
-                                                                &as_clause, &language,
+                                                                &as_clause, &language, &transformDefElem,
                                                                 &isWindowFunc, &volatility,
                                                                 &isStrict, &security, &isLeakProof,
                                                                 &proconfig, &procost, &prorows);
@@ -915,6 +930,23 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                                 errmsg("only superuser can define a leakproof function")));
 
+       if (transformDefElem)
+       {
+               ListCell *lc;
+
+               Assert(IsA(transformDefElem, List));
+
+               foreach (lc, (List *) transformDefElem)
+               {
+                       Oid typeid = typenameTypeId(NULL, lfirst(lc));
+                       Oid elt = get_base_element_type(typeid);
+                       typeid = elt ? elt : typeid;
+
+                       get_transform_oid(typeid, languageOid, false);
+                       trftypes_list = lappend_oid(trftypes_list, typeid);
+               }
+       }
+
        /*
         * Convert remaining parameters of CREATE to form wanted by
         * ProcedureCreate.
@@ -958,6 +990,25 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
                returnsSet = false;
        }
 
+       if (list_length(trftypes_list) > 0)
+       {
+               ListCell *lc;
+               Datum *arr;
+               int i;
+
+               arr = palloc(list_length(trftypes_list) * sizeof(Datum));
+               i = 0;
+               foreach (lc, trftypes_list)
+                       arr[i++] = ObjectIdGetDatum(lfirst_oid(lc));
+               trftypes = construct_array(arr, list_length(trftypes_list),
+                                                                  OIDOID, sizeof(Oid), true, 'i');
+       }
+       else
+       {
+               /* store SQL NULL instead of emtpy array */
+               trftypes = NULL;
+       }
+
        compute_attributes_with_style(stmt->withClause, &isStrict, &volatility);
 
        interpret_AS_clause(languageOid, language, funcname, as_clause,
@@ -1014,6 +1065,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
                                                   PointerGetDatum(parameterModes),
                                                   PointerGetDatum(parameterNames),
                                                   parameterDefaults,
+                                                  PointerGetDatum(trftypes),
                                                   PointerGetDatum(proconfig),
                                                   procost,
                                                   prorows);
@@ -1653,6 +1705,293 @@ DropCastById(Oid castOid)
        heap_close(relation, RowExclusiveLock);
 }
 
+
+static void
+check_transform_function(Form_pg_proc procstruct)
+{
+       if (procstruct->provolatile == PROVOLATILE_VOLATILE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                errmsg("transform function must not be volatile")));
+       if (procstruct->proisagg)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                errmsg("transform function must not be an aggregate function")));
+       if (procstruct->proiswindow)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                errmsg("transform function must not be a window function")));
+       if (procstruct->proretset)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                errmsg("transform function must not return a set")));
+       if (procstruct->pronargs != 1)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                errmsg("transform function must take one argument")));
+       if (procstruct->proargtypes.values[0] != INTERNALOID)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                errmsg("first argument of transform function must be type \"internal\"")));
+}
+
+
+/*
+ * CREATE TRANSFORM
+ */
+Oid
+CreateTransform(CreateTransformStmt *stmt)
+{
+       Oid                     typeid;
+       char            typtype;
+       Oid                     langid;
+       Oid                     fromsqlfuncid;
+       Oid                     tosqlfuncid;
+       AclResult       aclresult;
+       Form_pg_proc procstruct;
+       Datum           values[Natts_pg_transform];
+       bool            nulls[Natts_pg_transform];
+       bool            replaces[Natts_pg_transform];
+       Oid                     transformid;
+       HeapTuple       tuple;
+       HeapTuple       newtuple;
+       Relation        relation;
+       ObjectAddress myself,
+                               referenced;
+       bool            is_replace;
+
+       /*
+        * Get the type
+        */
+       typeid = typenameTypeId(NULL, stmt->type_name);
+       typtype = get_typtype(typeid);
+
+       if (typtype == TYPTYPE_PSEUDO)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("data type %s is a pseudo-type",
+                                               TypeNameToString(stmt->type_name))));
+
+       if (typtype == TYPTYPE_DOMAIN)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("data type %s is a domain",
+                                               TypeNameToString(stmt->type_name))));
+
+       if (!pg_type_ownercheck(typeid, GetUserId()))
+               aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
+
+       aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE);
+       if (aclresult != ACLCHECK_OK)
+               aclcheck_error_type(aclresult, typeid);
+
+       /*
+        * Get the language
+        */
+       langid = get_language_oid(stmt->lang, false);
+
+       aclresult = pg_language_aclcheck(langid, GetUserId(), ACL_USAGE);
+       if (aclresult != ACLCHECK_OK)
+               aclcheck_error(aclresult, ACL_KIND_LANGUAGE, stmt->lang);
+
+       /*
+        * Get the functions
+        */
+       if (stmt->fromsql)
+       {
+               fromsqlfuncid = LookupFuncNameTypeNames(stmt->fromsql->funcname, stmt->fromsql->funcargs, false);
+
+               if (!pg_proc_ownercheck(fromsqlfuncid, GetUserId()))
+                       aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
+
+               aclresult = pg_proc_aclcheck(fromsqlfuncid, GetUserId(), ACL_EXECUTE);
+               if (aclresult != ACLCHECK_OK)
+                       aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
+
+               tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fromsqlfuncid));
+               if (!HeapTupleIsValid(tuple))
+                       elog(ERROR, "cache lookup failed for function %u", fromsqlfuncid);
+               procstruct = (Form_pg_proc) GETSTRUCT(tuple);
+               if (procstruct->prorettype != INTERNALOID)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("return data type of FROM SQL function must be \"internal\"")));
+               check_transform_function(procstruct);
+               ReleaseSysCache(tuple);
+       }
+       else
+               fromsqlfuncid = InvalidOid;
+
+       if (stmt->tosql)
+       {
+               tosqlfuncid = LookupFuncNameTypeNames(stmt->tosql->funcname, stmt->tosql->funcargs, false);
+
+               if (!pg_proc_ownercheck(tosqlfuncid, GetUserId()))
+                       aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
+
+               aclresult = pg_proc_aclcheck(tosqlfuncid, GetUserId(), ACL_EXECUTE);
+               if (aclresult != ACLCHECK_OK)
+                       aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
+
+               tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(tosqlfuncid));
+               if (!HeapTupleIsValid(tuple))
+                       elog(ERROR, "cache lookup failed for function %u", tosqlfuncid);
+               procstruct = (Form_pg_proc) GETSTRUCT(tuple);
+               if (procstruct->prorettype != typeid)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("return data type of TO SQL function must be the transform data type")));
+               check_transform_function(procstruct);
+               ReleaseSysCache(tuple);
+       }
+       else
+               tosqlfuncid = InvalidOid;
+
+       /*
+        * Ready to go
+        */
+       values[Anum_pg_transform_trftype - 1] = ObjectIdGetDatum(typeid);
+       values[Anum_pg_transform_trflang - 1] = ObjectIdGetDatum(langid);
+       values[Anum_pg_transform_trffromsql - 1] = ObjectIdGetDatum(fromsqlfuncid);
+       values[Anum_pg_transform_trftosql - 1] = ObjectIdGetDatum(tosqlfuncid);
+
+       MemSet(nulls, false, sizeof(nulls));
+
+       relation = heap_open(TransformRelationId, RowExclusiveLock);
+
+       tuple = SearchSysCache2(TRFTYPELANG,
+                                                       ObjectIdGetDatum(typeid),
+                                                       ObjectIdGetDatum(langid));
+       if (HeapTupleIsValid(tuple))
+       {
+               if (!stmt->replace)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                        errmsg("transform for type %s language %s already exists",
+                                                       format_type_be(typeid),
+                                                       stmt->lang)));
+
+               MemSet(replaces, false, sizeof(replaces));
+               replaces[Anum_pg_transform_trffromsql - 1] = true;
+               replaces[Anum_pg_transform_trftosql - 1] = true;
+
+               newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);
+               simple_heap_update(relation, &newtuple->t_self, newtuple);
+
+               transformid = HeapTupleGetOid(tuple);
+               ReleaseSysCache(tuple);
+               is_replace = true;
+       }
+       else
+       {
+               newtuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
+               transformid = simple_heap_insert(relation, newtuple);
+               is_replace = false;
+       }
+
+       CatalogUpdateIndexes(relation, newtuple);
+
+       if (is_replace)
+               deleteDependencyRecordsFor(TransformRelationId, transformid, true);
+
+       /* make dependency entries */
+       myself.classId = TransformRelationId;
+       myself.objectId = transformid;
+       myself.objectSubId = 0;
+
+       /* dependency on language */
+       referenced.classId = LanguageRelationId;
+       referenced.objectId = langid;
+       referenced.objectSubId = 0;
+       recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+       /* dependency on type */
+       referenced.classId = TypeRelationId;
+       referenced.objectId = typeid;
+       referenced.objectSubId = 0;
+       recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+       /* dependencies on functions */
+       if (OidIsValid(fromsqlfuncid))
+       {
+               referenced.classId = ProcedureRelationId;
+               referenced.objectId = fromsqlfuncid;
+               referenced.objectSubId = 0;
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+       }
+       if (OidIsValid(tosqlfuncid))
+       {
+               referenced.classId = ProcedureRelationId;
+               referenced.objectId = tosqlfuncid;
+               referenced.objectSubId = 0;
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+       }
+
+       /* dependency on extension */
+       recordDependencyOnCurrentExtension(&myself, is_replace);
+
+       /* Post creation hook for new transform */
+       InvokeObjectPostCreateHook(TransformRelationId, transformid, 0);
+
+       heap_freetuple(newtuple);
+
+       heap_close(relation, RowExclusiveLock);
+
+       return transformid;
+}
+
+
+/*
+ * get_transform_oid - given type OID and language OID, look up a transform OID
+ *
+ * If missing_ok is false, throw an error if the transform is not found.  If
+ * true, just return InvalidOid.
+ */
+Oid
+get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok)
+{
+       Oid                     oid;
+
+       oid = GetSysCacheOid2(TRFTYPELANG,
+                                                 ObjectIdGetDatum(type_id),
+                                                 ObjectIdGetDatum(lang_id));
+       if (!OidIsValid(oid) && !missing_ok)
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                errmsg("transform for type %s language \"%s\" does not exist",
+                                               format_type_be(type_id),
+                                               get_language_name(lang_id, false))));
+       return oid;
+}
+
+
+void
+DropTransformById(Oid transformOid)
+{
+       Relation        relation;
+       ScanKeyData scankey;
+       SysScanDesc scan;
+       HeapTuple       tuple;
+
+       relation = heap_open(TransformRelationId, RowExclusiveLock);
+
+       ScanKeyInit(&scankey,
+                               ObjectIdAttributeNumber,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(transformOid));
+       scan = systable_beginscan(relation, TransformOidIndexId, true,
+                                                         NULL, 1, &scankey);
+
+       tuple = systable_getnext(scan);
+       if (!HeapTupleIsValid(tuple))
+               elog(ERROR, "could not find tuple for transform %u", transformOid);
+       simple_heap_delete(relation, &tuple->t_self);
+
+       systable_endscan(scan);
+       heap_close(relation, RowExclusiveLock);
+}
+
+
 /*
  * Subroutine for ALTER FUNCTION/AGGREGATE SET SCHEMA/RENAME
  *
index 11e6213e80f4d9a98e5d8a52a17c7de57d0f4ac2..3995a575e4ef157bbbd7d6670bcce0076f8c43bc 100644 (file)
@@ -141,6 +141,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
                                                                          PointerGetDatum(NULL),
                                                                          NIL,
                                                                          PointerGetDatum(NULL),
+                                                                         PointerGetDatum(NULL),
                                                                          1,
                                                                          0);
                        handlerOid = tmpAddr.objectId;
@@ -179,6 +180,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
                                                                                  PointerGetDatum(NULL),
                                                                                  NIL,
                                                                                  PointerGetDatum(NULL),
+                                                                                 PointerGetDatum(NULL),
                                                                                  1,
                                                                                  0);
                                inlineOid = tmpAddr.objectId;
@@ -220,6 +222,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
                                                                                  PointerGetDatum(NULL),
                                                                                  NIL,
                                                                                  PointerGetDatum(NULL),
+                                                                                 PointerGetDatum(NULL),
                                                                                  1,
                                                                                  0);
                                valOid = tmpAddr.objectId;
index 7d8561022a52f3d2db87f6110bc020ff0117bbf4..ab13be225c8f7ba31d94a145316547dc35eccdb1 100644 (file)
@@ -1616,6 +1616,7 @@ makeRangeConstructors(const char *name, Oid namespace,
                                                                 PointerGetDatum(NULL), /* parameterModes */
                                                                 PointerGetDatum(NULL), /* parameterNames */
                                                                 NIL,   /* parameterDefaults */
+                                                                PointerGetDatum(NULL),         /* trftypes */
                                                                 PointerGetDatum(NULL), /* proconfig */
                                                                 1.0,   /* procost */
                                                                 0.0);  /* prorows */
index 59c755d7a5952ead543a79cc36b9b6be00637a51..1685efe254ba5ff56c3ae7df3c7144e440278021 100644 (file)
@@ -3625,6 +3625,20 @@ _copyImportForeignSchemaStmt(const ImportForeignSchemaStmt *from)
        return newnode;
 }
 
+static CreateTransformStmt *
+_copyCreateTransformStmt(const CreateTransformStmt *from)
+{
+       CreateTransformStmt *newnode = makeNode(CreateTransformStmt);
+
+       COPY_SCALAR_FIELD(replace);
+       COPY_NODE_FIELD(type_name);
+       COPY_STRING_FIELD(lang);
+       COPY_NODE_FIELD(fromsql);
+       COPY_NODE_FIELD(tosql);
+
+       return newnode;
+}
+
 static CreateTrigStmt *
 _copyCreateTrigStmt(const CreateTrigStmt *from)
 {
@@ -4568,6 +4582,9 @@ copyObject(const void *from)
                case T_ImportForeignSchemaStmt:
                        retval = _copyImportForeignSchemaStmt(from);
                        break;
+               case T_CreateTransformStmt:
+                       retval = _copyCreateTransformStmt(from);
+                       break;
                case T_CreateTrigStmt:
                        retval = _copyCreateTrigStmt(from);
                        break;
index 3bc81762af5fca909d35ec3fce8e2f589e786238..578ead54632aa70cdb07d9e5fa281baca4c3077f 100644 (file)
@@ -1779,6 +1779,18 @@ _equalImportForeignSchemaStmt(const ImportForeignSchemaStmt *a, const ImportFore
        return true;
 }
 
+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)
 {
@@ -2991,6 +3003,9 @@ equal(const void *a, const void *b)
                case T_ImportForeignSchemaStmt:
                        retval = _equalImportForeignSchemaStmt(a, b);
                        break;
+               case T_CreateTransformStmt:
+                       retval = _equalCreateTransformStmt(a, b);
+                       break;
                case T_CreateTrigStmt:
                        retval = _equalCreateTrigStmt(a, b);
                        break;
index 5818858a295e6a2be52f162883e020b464cfb578..962a69d22a6ca98699bf5dd11168cd6f3a112bdf 100644 (file)
@@ -241,12 +241,13 @@ 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 CreatePolicyStmt
                CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
                DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
                DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
                DropPolicyStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
+               DropTransformStmt
                DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
                GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
                ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
@@ -366,6 +367,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 transform_type_list
 
 %type <list>   opt_fdw_options fdw_options
 %type <defelt> fdw_option
@@ -611,12 +613,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 SKIP SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
+       SHOW SIMILAR SIMPLE SKIP 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
@@ -790,6 +792,7 @@ stmt :
                        | CreateSeqStmt
                        | CreateStmt
                        | CreateTableSpaceStmt
+                       | CreateTransformStmt
                        | CreateTrigStmt
                        | CreateEventTrigStmt
                        | CreateRoleStmt
@@ -815,6 +818,7 @@ stmt :
                        | DropRuleStmt
                        | DropStmt
                        | DropTableSpaceStmt
+                       | DropTransformStmt
                        | DropTrigStmt
                        | DropRoleStmt
                        | DropUserStmt
@@ -4083,6 +4087,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 Typename
                                {
                                        AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
@@ -5736,6 +5750,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);
@@ -7015,6 +7038,10 @@ createfunc_opt_item:
                                {
                                        $$ = makeDefElem("language", (Node *)makeString($2));
                                }
+                       | TRANSFORM transform_type_list
+                               {
+                                       $$ = makeDefElem("transform", (Node *)$2);
+                               }
                        | WINDOW
                                {
                                        $$ = makeDefElem("window", (Node *)makeInteger(TRUE));
@@ -7032,6 +7059,11 @@ func_as: Sconst                                          { $$ = list_make1(makeString($1)); }
                                }
                ;
 
+transform_type_list:
+                       FOR TYPE_P Typename { $$ = list_make1($3); }
+                       | transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
+               ;
+
 opt_definition:
                        WITH definition                                                 { $$ = $2; }
                        | /*EMPTY*/                                                             { $$ = NIL; }
@@ -7297,6 +7329,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:
@@ -13460,6 +13542,7 @@ unreserved_keyword:
                        | SIMPLE
                        | SKIP
                        | SNAPSHOT
+                       | SQL_P
                        | STABLE
                        | STANDALONE_P
                        | START
@@ -13479,6 +13562,7 @@ unreserved_keyword:
                        | TEMPORARY
                        | TEXT_P
                        | TRANSACTION
+                       | TRANSFORM
                        | TRIGGER
                        | TRUNCATE
                        | TRUSTED
index fd09d3ad9ed84950653a0a11966cccfc78ff7215..31e9d4cf8b4b90580978263b7972ed643a23d741 100644 (file)
@@ -174,6 +174,7 @@ check_xact_readonly(Node *parsetree)
                case T_CreateTableAsStmt:
                case T_RefreshMatViewStmt:
                case T_CreateTableSpaceStmt:
+               case T_CreateTransformStmt:
                case T_CreateTrigStmt:
                case T_CompositeTypeStmt:
                case T_CreateEnumStmt:
@@ -1314,6 +1315,10 @@ ProcessUtilitySlow(Node *parsetree,
                                DefineOpFamily((CreateOpFamilyStmt *) parsetree);
                                break;
 
+                       case T_CreateTransformStmt:
+                               CreateTransform((CreateTransformStmt *) parsetree);
+                               break;
+
                        case T_AlterOpFamilyStmt:
                                AlterOpFamily((AlterOpFamilyStmt *) parsetree);
                                break;
@@ -2004,6 +2009,9 @@ CreateCommandTag(Node *parsetree)
                                case OBJECT_POLICY:
                                        tag = "DROP POLICY";
                                        break;
+                               case OBJECT_TRANSFORM:
+                                       tag = "DROP TRANSFORM";
+                                       break;
                                default:
                                        tag = "???";
                        }
@@ -2263,6 +2271,10 @@ CreateCommandTag(Node *parsetree)
                        }
                        break;
 
+               case T_CreateTransformStmt:
+                       tag = "CREATE TRANSFORM";
+                       break;
+
                case T_CreateTrigStmt:
                        tag = "CREATE TRIGGER";
                        break;
@@ -2888,6 +2900,10 @@ GetCommandLogLevel(Node *parsetree)
                        lev = LOGSTMT_DDL;
                        break;
 
+               case T_CreateTransformStmt:
+                       lev = LOGSTMT_DDL;
+                       break;
+
                case T_AlterOpFamilyStmt:
                        lev = LOGSTMT_DDL;
                        break;
index 29b5b1b8945f37cecbd30418d24686894f7e3ae5..5ffb712472ba489acd789fe8be824521e3a78395 100644 (file)
@@ -306,6 +306,7 @@ static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
 static int print_function_arguments(StringInfo buf, HeapTuple proctup,
                                                 bool print_table_args, bool print_defaults);
 static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
 static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
                                 Bitmapset *rels_used);
 static bool refname_is_unique(char *refname, deparse_namespace *dpns,
@@ -1912,9 +1913,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
        StringInfoData buf;
        StringInfoData dq;
        HeapTuple       proctup;
-       HeapTuple       langtup;
        Form_pg_proc proc;
-       Form_pg_language lang;
        Datum           tmp;
        bool            isnull;
        const char *prosrc;
@@ -1937,12 +1936,6 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                                 errmsg("\"%s\" is an aggregate function", name)));
 
-       /* Need its pg_language tuple for the language name */
-       langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
-       if (!HeapTupleIsValid(langtup))
-               elog(ERROR, "cache lookup failed for language %u", proc->prolang);
-       lang = (Form_pg_language) GETSTRUCT(langtup);
-
        /*
         * We always qualify the function name, to ensure the right function gets
         * replaced.
@@ -1953,8 +1946,11 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
        (void) print_function_arguments(&buf, proctup, false, true);
        appendStringInfoString(&buf, ")\n RETURNS ");
        print_function_rettype(&buf, proctup);
+
+       print_function_trftypes(&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;
@@ -2074,7 +2070,6 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
 
        appendStringInfoChar(&buf, '\n');
 
-       ReleaseSysCache(langtup);
        ReleaseSysCache(proctup);
 
        PG_RETURN_TEXT_P(string_to_text(buf.data));
@@ -2350,6 +2345,30 @@ is_input_argument(int nth, const char *argmodes)
                        || argmodes[nth] == PROARGMODE_VARIADIC);
 }
 
+/*
+ * Append used transformated types to specified buffer
+ */
+static void
+print_function_trftypes(StringInfo buf, HeapTuple proctup)
+{
+       Oid     *trftypes;
+       int     ntypes;
+
+       ntypes = get_func_trftypes(proctup, &trftypes);
+       if (ntypes > 0)
+       {
+               int     i;
+
+               appendStringInfoString(buf, "\n TRANSFORM ");
+               for (i = 0; i < ntypes; i++)
+               {
+                       if (i != 0)
+                               appendStringInfoString(buf, ", ");
+                               appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i]));
+               }
+       }
+}
+
 /*
  * Get textual representation of a function argument's default value.  The
  * second argument of this function is the argument number among all arguments
index 6a3986389065f4b6f8d146b598ddec82b22591a7..1dc293297d93edb933b743dc302fb7bcad757290 100644 (file)
 #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"
@@ -977,6 +979,30 @@ get_constraint_name(Oid conoid)
                return NULL;
 }
 
+/*                             ---------- LANGUAGE CACHE ----------                                     */
+
+char *
+get_language_name(Oid langoid, bool missing_ok)
+{
+       HeapTuple       tp;
+
+       tp = SearchSysCache1(LANGOID, ObjectIdGetDatum(langoid));
+       if (HeapTupleIsValid(tp))
+       {
+               Form_pg_language lantup = (Form_pg_language) GETSTRUCT(tp);
+               char       *result;
+
+               result = pstrdup(NameStr(lantup->lanname));
+               ReleaseSysCache(tp);
+               return result;
+       }
+
+       if (!missing_ok)
+               elog(ERROR, "cache lookup failed for language %u",
+                        langoid);
+       return NULL;
+}
+
 /*                             ---------- OPCLASS CACHE ----------                                              */
 
 /*
@@ -1743,6 +1769,51 @@ get_rel_tablespace(Oid relid)
 }
 
 
+/*                             ---------- TRANSFORM CACHE ----------                                            */
+
+Oid
+get_transform_fromsql(Oid typid, Oid langid, List *trftypes)
+{
+       HeapTuple       tup;
+
+       if (!list_member_oid(trftypes, typid))
+               return InvalidOid;
+
+       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, List *trftypes)
+{
+       HeapTuple       tup;
+
+       if (!list_member_oid(trftypes, typid))
+               return InvalidOid;
+
+       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 ----------                                                 */
 
 /*
index bd271680e5033fe27ffe9e61e042e1604373511b..644bbcc167c835aa81b05989a269907039400e86 100644 (file)
@@ -56,6 +56,7 @@
 #include "catalog/pg_shseclabel.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
+#include "catalog/pg_transform.h"
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_config_map.h"
 #include "catalog/pg_ts_dict.h"
@@ -653,6 +654,28 @@ static const struct cachedesc cacheinfo[] = {
                },
                4
        },
+       {TransformRelationId,           /* TRFOID */
+        TransformOidIndexId,
+        1,
+        {
+                ObjectIdAttributeNumber,
+                0,
+                0,
+                0,
+        },
+        16
+       },
+       {TransformRelationId,           /* TRFTYPELANG */
+        TransformTypeLangIndexId,
+        2,
+        {
+                Anum_pg_transform_trftype,
+                Anum_pg_transform_trflang,
+                0,
+                0,
+        },
+        16
+       },
        {TSConfigMapRelationId,         /* TSCONFIGMAP */
                TSConfigMapIndexId,
                3,
index 204e1243554fdd5c9582c5cee0a94aceebb5f447..ebd7ddd3873c926acc8f6937daf8ff73822d543b 100644 (file)
@@ -877,6 +877,50 @@ get_func_arg_info(HeapTuple procTup,
        return numargs;
 }
 
+/*
+ * get_func_trftypes
+ *
+ * Returns a number of transformated types used by function.
+ */
+int
+get_func_trftypes(HeapTuple procTup,
+                                 Oid **p_trftypes)
+{
+
+       Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+       Datum           protrftypes;
+       ArrayType  *arr;
+       int                     nelems;
+       bool                    isNull;
+
+       protrftypes = SysCacheGetAttr(PROCOID, procTup,
+                                                                        Anum_pg_proc_protrftypes,
+                                                                        &isNull);
+       if (!isNull)
+       {
+               /*
+                * We expect the arrays to be 1-D arrays of the right types; verify
+                * that.  For the OID and char arrays, we don't need to use
+                * deconstruct_array() since the array data is just going to look like
+                * a C array of values.
+                */
+               arr = DatumGetArrayTypeP(protrftypes);          /* ensure not toasted */
+               nelems = ARR_DIMS(arr)[0];
+               if (ARR_NDIM(arr) != 1 ||
+                       nelems < 0 ||
+                       ARR_HASNULL(arr) ||
+                       ARR_ELEMTYPE(arr) != OIDOID)
+                       elog(ERROR, "protrftypes is not a 1-D Oid array");
+               Assert(nelems >= procStruct->pronargs);
+               *p_trftypes = (Oid *) palloc(nelems * sizeof(Oid));
+               memcpy(*p_trftypes, ARR_DATA_PTR(arr),
+                          nelems * sizeof(Oid));
+
+               return nelems;
+       }
+       else
+               return 0;
+}
 
 /*
  * get_func_input_arg_names
index 1a0a587413285e27f53778cb7c3ffab25bd94e38..d100514cc21277423d9b1e786b5e1028772b6131 100644 (file)
@@ -92,6 +92,7 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
        int                     numRules;
        int                     numProcLangs;
        int                     numCasts;
+       int                     numTransforms;
        int                     numOpclasses;
        int                     numOpfamilies;
        int                     numConversions;
@@ -201,6 +202,10 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
                write_msg(NULL, "reading type casts\n");
        getCasts(fout, dopt, &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);
index fe08c1b15d2e96ef26bf46d4b9a381317d650f66..977b72e344917d58a0095ffc447734a1092dd614 100644 (file)
@@ -165,6 +165,7 @@ static void dumpShellType(Archive *fout, DumpOptions *dopt, ShellTypeInfo *stinf
 static void dumpProcLang(Archive *fout, DumpOptions *dopt, ProcLangInfo *plang);
 static void dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo);
 static void dumpCast(Archive *fout, DumpOptions *dopt, CastInfo *cast);
+static void dumpTransform(Archive *fout, DumpOptions *dopt, TransformInfo *transform);
 static void dumpOpr(Archive *fout, DumpOptions *dopt, OprInfo *oprinfo);
 static void dumpOpclass(Archive *fout, DumpOptions *dopt, OpclassInfo *opcinfo);
 static void dumpOpfamily(Archive *fout, DumpOptions *dopt, OpfamilyInfo *opfinfo);
@@ -6566,6 +6567,110 @@ getCasts(Archive *fout, DumpOptions *dopt, int *numCasts)
        return castinfo;
 }
 
+static char *
+get_language_name(Archive *fout, Oid langid)
+{
+       PQExpBuffer query;
+       PGresult   *res;
+       char       *lanname;
+
+       query = createPQExpBuffer();
+       appendPQExpBuffer(query, "SELECT lanname FROM pg_language WHERE oid = %u", langid);
+       res = ExecuteSqlQueryForSingleRow(fout, query->data);
+       lanname = pg_strdup(fmtId(PQgetvalue(res, 0, 0)));
+       destroyPQExpBuffer(query);
+       PQclear(res);
+
+       return lanname;
+}
+
+/*
+ * getTransforms
+ *       get basic information about every transform in the system
+ *
+ * numTransforms is set to the number of transforms read in
+ */
+TransformInfo *
+getTransforms(Archive *fout, int *numTransforms)
+{
+       PGresult   *res;
+       int                     ntups;
+       int                     i;
+       PQExpBuffer query = createPQExpBuffer();
+       TransformInfo   *transforminfo;
+       int                     i_tableoid;
+       int                     i_oid;
+       int                     i_trftype;
+       int                     i_trflang;
+       int                     i_trffromsql;
+       int                     i_trftosql;
+
+       /* Transforms didn't exist pre-9.5 */
+       if (fout->remoteVersion < 90500)
+       {
+               *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
@@ -8182,6 +8287,9 @@ dumpDumpableObject(Archive *fout, DumpOptions *dopt, DumpableObject *dobj)
                case DO_CAST:
                        dumpCast(fout, dopt, (CastInfo *) dobj);
                        break;
+               case DO_TRANSFORM:
+                       dumpTransform(fout, dopt, (TransformInfo *) dobj);
+                       break;
                case DO_TABLE_DATA:
                        if (((TableDataInfo *) dobj)->tdtable->relkind == RELKIND_SEQUENCE)
                                dumpSequenceData(fout, (TableDataInfo *) dobj);
@@ -9989,6 +10097,7 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
        char       *proallargtypes;
        char       *proargmodes;
        char       *proargnames;
+       char       *protrftypes;
        char       *proiswindow;
        char       *provolatile;
        char       *proisstrict;
@@ -10021,10 +10130,28 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
        selectSourceSchema(fout, finfo->dobj.namespace->dobj.name);
 
        /* Fetch function-specific details */
-       if (fout->remoteVersion >= 90200)
+       if (fout->remoteVersion >= 90500)
        {
                /*
-                * proleakproof was added at v9.2
+                * protrftypes was added in 9.5
+                */
+               appendPQExpBuffer(query,
+                                                 "SELECT proretset, prosrc, probin, "
+                                       "pg_catalog.pg_get_function_arguments(oid) AS funcargs, "
+                 "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs, "
+                                        "pg_catalog.pg_get_function_result(oid) AS funcresult, "
+                                                 "array_to_string(protrftypes, ' ') AS protrftypes, "
+                                                 "proiswindow, provolatile, proisstrict, prosecdef, "
+                                                 "proleakproof, proconfig, procost, prorows, "
+                                                 "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
+                                                 "FROM pg_catalog.pg_proc "
+                                                 "WHERE oid = '%u'::pg_catalog.oid",
+                                                 finfo->dobj.catId.oid);
+       }
+       else if (fout->remoteVersion >= 90200)
+       {
+               /*
+                * proleakproof was added in 9.2
                 */
                appendPQExpBuffer(query,
                                                  "SELECT proretset, prosrc, probin, "
@@ -10173,6 +10300,10 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
                proargnames = PQgetvalue(res, 0, PQfnumber(res, "proargnames"));
                funcargs = funciargs = funcresult = NULL;
        }
+       if (PQfnumber(res, "protrftypes") != -1)
+               protrftypes = PQgetvalue(res, 0, PQfnumber(res, "protrftypes"));
+       else
+               protrftypes = NULL;
        proiswindow = PQgetvalue(res, 0, PQfnumber(res, "proiswindow"));
        provolatile = PQgetvalue(res, 0, PQfnumber(res, "provolatile"));
        proisstrict = PQgetvalue(res, 0, PQfnumber(res, "proisstrict"));
@@ -10316,6 +10447,22 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
 
        appendPQExpBuffer(q, "\n    LANGUAGE %s", fmtId(lanname));
 
+       if (protrftypes != NULL && strcmp(protrftypes, "") != 0)
+       {
+               Oid *typeids = palloc(FUNC_MAX_ARGS * sizeof(Oid));
+               int i;
+
+               appendPQExpBufferStr(q, " TRANSFORM ");
+               parseOidArray(protrftypes, typeids, FUNC_MAX_ARGS);
+               for (i = 0; typeids[i]; i++)
+               {
+                       if (i != 0)
+                               appendPQExpBufferStr(q, ", ");
+                       appendPQExpBuffer(q, "FOR TYPE %s",
+                                                         getFormattedTypeName(fout, typeids[i], zeroAsNone));
+               }
+       }
+
        if (proiswindow[0] == 't')
                appendPQExpBufferStr(q, " WINDOW");
 
@@ -10539,6 +10686,127 @@ dumpCast(Archive *fout, DumpOptions *dopt, CastInfo *cast)
        destroyPQExpBuffer(labelq);
 }
 
+/*
+ * Dump a transform
+ */
+static void
+dumpTransform(Archive *fout, DumpOptions *dopt, 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 || dopt->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 (dopt->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, dopt, 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
@@ -15658,6 +15926,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
                        case DO_TSCONFIG:
                        case DO_FDW:
                        case DO_FOREIGN_SERVER:
+                       case DO_TRANSFORM:
                        case DO_BLOB:
                                /* Pre-data objects: must come before the pre-data boundary */
                                addObjectDependency(preDataBound, dobj->dumpId);
index a9d3c1016be7083a05657e79fa55714d83360e17..4c796ad6a769fb7437acfb626cd6365cfa561df8 100644 (file)
@@ -70,6 +70,7 @@ typedef enum
        DO_FDW,
        DO_FOREIGN_SERVER,
        DO_DEFAULT_ACL,
+       DO_TRANSFORM,
        DO_BLOB,
        DO_BLOB_DATA,
        DO_PRE_DATA_BOUNDARY,
@@ -376,6 +377,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
 {
@@ -534,6 +544,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, DumpOptions *dopt, int *numCasts);
+extern TransformInfo *getTransforms(Archive *fout, int *numTransforms);
 extern void getTableAttrs(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo, int numTables);
 extern bool shouldPrintColumn(DumpOptions *dopt, TableInfo *tbinfo, int colno);
 extern TSParserInfo *getTSParsers(Archive *fout, int *numTSParsers);
index c5ed593e02ef7f4f1824393087b69d17ad2ef6d4..aa3e20a2d5f466d834b3af803a37c9c35cb73456 100644 (file)
@@ -28,7 +28,7 @@ static const char *modulename = gettext_noop("sorter");
  * by OID.  (This is a relatively crude hack to provide semi-reasonable
  * behavior for old databases without full dependency info.)  Note: collations,
  * extensions, text search, foreign-data, materialized view, event trigger,
- * policies, and default ACL objects can't really happen here, so the rather
+ * policies, 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
@@ -67,6 +67,7 @@ static const int oldObjectTypePriority[] =
        4,                                                      /* DO_FDW */
        4,                                                      /* DO_FOREIGN_SERVER */
        19,                                                     /* DO_DEFAULT_ACL */
+       4,                                                      /* DO_TRANSFORM */
        9,                                                      /* DO_BLOB */
        12,                                                     /* DO_BLOB_DATA */
        10,                                                     /* DO_PRE_DATA_BOUNDARY */
@@ -116,6 +117,7 @@ static const int newObjectTypePriority[] =
        16,                                                     /* DO_FDW */
        17,                                                     /* DO_FOREIGN_SERVER */
        31,                                                     /* DO_DEFAULT_ACL */
+       3,                                                      /* DO_TRANSFORM */
        21,                                                     /* DO_BLOB */
        24,                                                     /* DO_BLOB_DATA */
        22,                                                     /* DO_PRE_DATA_BOUNDARY */
@@ -1400,6 +1402,13 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
                                         ((CastInfo *) obj)->casttarget,
                                         obj->dumpId, obj->catId.oid);
                        return;
+               case DO_TRANSFORM:
+                       snprintf(buf, bufsize,
+                                        "TRANSFORM %u lang %u  (ID %d OID %u)",
+                                        ((TransformInfo *) obj)->trftype,
+                                        ((TransformInfo *) obj)->trflang,
+                                        obj->dumpId, obj->catId.oid);
+                       return;
                case DO_TABLE_DATA:
                        snprintf(buf, bufsize,
                                         "TABLE DATA %s  (ID %d OID %u)",
index 86d14025d5aae91ac1ba8589380c7dffd4393615..b36e4edd843dc8e7a505cfa2090086df1e325122 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201504171
+#define CATALOG_VERSION_NO     201504261
 
 #endif
index 6481ac84ce01e219dd3db02eaa4f4949015366ad..5da18c25444f88ee2a13b88945096e442b68a7e6 100644 (file)
@@ -148,6 +148,7 @@ typedef enum ObjectClass
        OCLASS_EXTENSION,                       /* pg_extension */
        OCLASS_EVENT_TRIGGER,           /* pg_event_trigger */
        OCLASS_POLICY,                          /* pg_policy */
+       OCLASS_TRANSFORM,                       /* pg_transform */
        MAX_OCLASS                                      /* MUST BE LAST */
 } ObjectClass;
 
index a68022960cff34302d0f3fd1d47270176a48e8cf..a234bde293c796c034ec8d1107a7217dcf9604b2 100644 (file)
@@ -219,6 +219,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, 3574, on pg_transform using btree(oid oid_ops));
+#define TransformOidIndexId    3574
+DECLARE_UNIQUE_INDEX(pg_transform_type_lang_index, 3575, on pg_transform using btree(trftype oid_ops, trflang oid_ops));
+#define TransformTypeLangIndexId  3575
+
 DECLARE_INDEX(pg_trigger_tgconstraint_index, 2699, on pg_trigger using btree(tgconstraint oid_ops));
 #define TriggerConstraintIndexId  2699
 DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using btree(tgrelid oid_ops, tgname name_ops));
index 8b4c35c21c7f9765dea34f1bd5f309089ab38953..48a726289574708921f948cbe5bbc284ed8f0cd7 100644 (file)
@@ -144,7 +144,7 @@ DATA(insert OID = 1247 (  pg_type           PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t
 DESCR("");
 DATA(insert OID = 1249 (  pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc              PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc              PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
 DATA(insert OID = 1259 (  pg_class             PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
index a3cc91b417902b820286ca5cdc7f1896a95e14c3..e97e6b19440c9347007ca82232b8881d27e1e3b0 100644 (file)
@@ -66,6 +66,7 @@ CATALOG(pg_proc,1255) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81) BKI_SCHEMA_MACRO
        text            proargnames[1]; /* parameter names (NULL if no names) */
        pg_node_tree proargdefaults;/* list of expression trees for argument
                                                                 * defaults (NULL if none) */
+       Oid                     protrftypes[1]; /* types for which to apply transforms */
        text            prosrc BKI_FORCE_NOT_NULL; /* procedure source text */
        text            probin;                 /* secondary procedure info (can be NULL) */
        text            proconfig[1];   /* procedure-local GUC settings */
@@ -84,7 +85,7 @@ typedef FormData_pg_proc *Form_pg_proc;
  *             compiler constants for pg_proc
  * ----------------
  */
-#define Natts_pg_proc                                  27
+#define Natts_pg_proc                                  28
 #define Anum_pg_proc_proname                   1
 #define Anum_pg_proc_pronamespace              2
 #define Anum_pg_proc_proowner                  3
@@ -108,10 +109,11 @@ typedef FormData_pg_proc *Form_pg_proc;
 #define Anum_pg_proc_proargmodes               21
 #define Anum_pg_proc_proargnames               22
 #define Anum_pg_proc_proargdefaults            23
-#define Anum_pg_proc_prosrc                            24
-#define Anum_pg_proc_probin                            25
-#define Anum_pg_proc_proconfig                 26
-#define Anum_pg_proc_proacl                            27
+#define Anum_pg_proc_protrftypes               24
+#define Anum_pg_proc_prosrc                            25
+#define Anum_pg_proc_probin                            26
+#define Anum_pg_proc_proconfig                 27
+#define Anum_pg_proc_proacl                            28
 
 /* ----------------
  *             initial contents of pg_proc
@@ -141,5064 +143,5064 @@ typedef FormData_pg_proc *Form_pg_proc;
 
 /* OIDS 1 - 99 */
 
-DATA(insert OID = 1242 (  boolin                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2275" _null_ _null_ _null_ _null_ boolin _null_ _null_ _null_ ));
+DATA(insert OID = 1242 (  boolin                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2275" _null_ _null_ _null_ _null_ _null_ boolin _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID = 1243 (  boolout                 PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "16" _null_ _null_ _null_ _null_ boolout _null_ _null_ _null_ ));
+DATA(insert OID = 1243 (  boolout                 PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "16" _null_ _null_ _null_ _null_ _null_ boolout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID = 1244 (  byteain                 PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 17 "2275" _null_ _null_ _null_ _null_ byteain _null_ _null_ _null_ ));
+DATA(insert OID = 1244 (  byteain                 PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 17 "2275" _null_ _null_ _null_ _null_ _null_ byteain _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  31 (  byteaout                 PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "17" _null_ _null_ _null_ _null_ byteaout _null_ _null_ _null_ ));
+DATA(insert OID =  31 (  byteaout                 PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "17" _null_ _null_ _null_ _null_ _null_ byteaout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID = 1245 (  charin                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 18 "2275" _null_ _null_ _null_ _null_ charin _null_ _null_ _null_ ));
+DATA(insert OID = 1245 (  charin                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 18 "2275" _null_ _null_ _null_ _null_ _null_ charin _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  33 (  charout                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "18" _null_ _null_ _null_ _null_ charout _null_ _null_ _null_ ));
+DATA(insert OID =  33 (  charout                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "18" _null_ _null_ _null_ _null_ _null_ charout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  34 (  namein                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 19 "2275" _null_ _null_ _null_ _null_ namein _null_ _null_ _null_ ));
+DATA(insert OID =  34 (  namein                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 19 "2275" _null_ _null_ _null_ _null_ _null_ namein _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  35 (  nameout                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "19" _null_ _null_ _null_ _null_ nameout _null_ _null_ _null_ ));
+DATA(insert OID =  35 (  nameout                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "19" _null_ _null_ _null_ _null_ _null_ nameout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  38 (  int2in                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "2275" _null_ _null_ _null_ _null_ int2in _null_ _null_ _null_ ));
+DATA(insert OID =  38 (  int2in                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "2275" _null_ _null_ _null_ _null_ _null_ int2in _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  39 (  int2out                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "21" _null_ _null_ _null_ _null_ int2out _null_ _null_ _null_ ));
+DATA(insert OID =  39 (  int2out                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "21" _null_ _null_ _null_ _null_ _null_ int2out _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  40 (  int2vectorin     PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 22 "2275" _null_ _null_ _null_ _null_ int2vectorin _null_ _null_ _null_ ));
+DATA(insert OID =  40 (  int2vectorin     PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 22 "2275" _null_ _null_ _null_ _null_ _null_ int2vectorin _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  41 (  int2vectorout    PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "22" _null_ _null_ _null_ _null_ int2vectorout _null_ _null_ _null_ ));
+DATA(insert OID =  41 (  int2vectorout    PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "22" _null_ _null_ _null_ _null_ _null_ int2vectorout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  42 (  int4in                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "2275" _null_ _null_ _null_ _null_ int4in _null_ _null_ _null_ ));
+DATA(insert OID =  42 (  int4in                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "2275" _null_ _null_ _null_ _null_ _null_ int4in _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  43 (  int4out                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "23" _null_ _null_ _null_ _null_ int4out _null_ _null_ _null_ ));
+DATA(insert OID =  43 (  int4out                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "23" _null_ _null_ _null_ _null_ _null_ int4out _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  44 (  regprocin                PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 24 "2275" _null_ _null_ _null_ _null_ regprocin _null_ _null_ _null_ ));
+DATA(insert OID =  44 (  regprocin                PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 24 "2275" _null_ _null_ _null_ _null_ _null_ regprocin _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  45 (  regprocout               PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 2275 "24" _null_ _null_ _null_ _null_ regprocout _null_ _null_ _null_ ));
+DATA(insert OID =  45 (  regprocout               PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 2275 "24" _null_ _null_ _null_ _null_ _null_ regprocout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID = 3494 (  to_regproc           PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 24 "2275" _null_ _null_ _null_ _null_ to_regproc _null_ _null_ _null_ ));
+DATA(insert OID = 3494 (  to_regproc           PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 24 "2275" _null_ _null_ _null_ _null_ _null_ to_regproc _null_ _null_ _null_ ));
 DESCR("convert proname to regproc");
-DATA(insert OID = 3479 (  to_regprocedure      PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 2202 "2275" _null_ _null_ _null_ _null_ to_regprocedure _null_ _null_ _null_ ));
+DATA(insert OID = 3479 (  to_regprocedure      PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 2202 "2275" _null_ _null_ _null_ _null_ _null_ to_regprocedure _null_ _null_ _null_ ));
 DESCR("convert proname to regprocedure");
-DATA(insert OID =  46 (  textin                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "2275" _null_ _null_ _null_ _null_ textin _null_ _null_ _null_ ));
+DATA(insert OID =  46 (  textin                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "2275" _null_ _null_ _null_ _null_ _null_ textin _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  47 (  textout                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "25" _null_ _null_ _null_ _null_ textout _null_ _null_ _null_ ));
+DATA(insert OID =  47 (  textout                  PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "25" _null_ _null_ _null_ _null_ _null_ textout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  48 (  tidin                    PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 27 "2275" _null_ _null_ _null_ _null_ tidin _null_ _null_ _null_ ));
+DATA(insert OID =  48 (  tidin                    PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 27 "2275" _null_ _null_ _null_ _null_ _null_ tidin _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  49 (  tidout                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "27" _null_ _null_ _null_ _null_ tidout _null_ _null_ _null_ ));
+DATA(insert OID =  49 (  tidout                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "27" _null_ _null_ _null_ _null_ _null_ tidout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  50 (  xidin                    PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 28 "2275" _null_ _null_ _null_ _null_ xidin _null_ _null_ _null_ ));
+DATA(insert OID =  50 (  xidin                    PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 28 "2275" _null_ _null_ _null_ _null_ _null_ xidin _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  51 (  xidout                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "28" _null_ _null_ _null_ _null_ xidout _null_ _null_ _null_ ));
+DATA(insert OID =  51 (  xidout                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "28" _null_ _null_ _null_ _null_ _null_ xidout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  52 (  cidin                    PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 29 "2275" _null_ _null_ _null_ _null_ cidin _null_ _null_ _null_ ));
+DATA(insert OID =  52 (  cidin                    PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 29 "2275" _null_ _null_ _null_ _null_ _null_ cidin _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  53 (  cidout                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "29" _null_ _null_ _null_ _null_ cidout _null_ _null_ _null_ ));
+DATA(insert OID =  53 (  cidout                           PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "29" _null_ _null_ _null_ _null_ _null_ cidout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  54 (  oidvectorin      PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 30 "2275" _null_ _null_ _null_ _null_ oidvectorin _null_ _null_ _null_ ));
+DATA(insert OID =  54 (  oidvectorin      PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 30 "2275" _null_ _null_ _null_ _null_ _null_ oidvectorin _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  55 (  oidvectorout     PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "30" _null_ _null_ _null_ _null_ oidvectorout _null_ _null_ _null_ ));
+DATA(insert OID =  55 (  oidvectorout     PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "30" _null_ _null_ _null_ _null_ _null_ oidvectorout _null_ _null_ _null_ ));
 DESCR("I/O");
-DATA(insert OID =  56 (  boollt                           PGNSP PGUID 12 1 0 0 0 f f f t t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boollt _null_ _null_ _null_ ));
-DATA(insert OID =  57 (  boolgt                           PGNSP PGUID 12 1 0 0 0 f f f t t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolgt _null_ _null_ _null_ ));
-DATA(insert OID =  60 (  booleq                           PGNSP PGUID 12 1 0 0 0 f f f t t f i 2 0 16 "16 16"