From 304d88f5120efbbfb6b14163f17e6b8f59de09b8 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 13 Sep 2013 21:10:34 -0400 Subject: [PATCH] Add transforms feature A transform is an SQL object that supplies to functions for converting between data types and procedural languages. For example, a transform could arrange that hstore is converted to an appropriate hash or dictionary object in PL/Perl or PL/Python. Externally visible changes: - new SQL commands CREATE TRANSFORM and DROP TRANSFORM - system catalog pg_transform - transform support in PL/Perl and PL/Python - PL/Perl and PL/Python install their header files for use by external types - transforms contrib modules hstore_plperl, hstore_plpython, ltree_plpython - regression test mangling for Python 3 was moved to a separate makefile, for use by extensions The regression tests for general CREATE TRANSFORM functionality are under the hstore_plperl module in order to be able to test it on a real transform implementation, instead of having to create an entire fake procedural language under src/test/regress/, say. Dynamic module linking on OS X was changed to allow undefined symbols at build time. This is necessary so that a transform module can use symbols from the type and the language modules, if necessary. Other platforms already behaved this way, but the default on OS X was different. --- contrib/Makefile | 12 + contrib/hstore_plperl/.gitignore | 4 + contrib/hstore_plperl/Makefile | 23 ++ .../hstore_plperl/expected/create_transform.out | 70 +++++ contrib/hstore_plperl/expected/hstore_plperl.out | 170 ++++++++++++ contrib/hstore_plperl/hstore_plperl--1.0.sql | 17 ++ contrib/hstore_plperl/hstore_plperl.c | 90 +++++++ contrib/hstore_plperl/hstore_plperl.control | 6 + contrib/hstore_plperl/hstore_plperlu--1.0.sql | 17 ++ contrib/hstore_plperl/hstore_plperlu.control | 6 + contrib/hstore_plperl/sql/create_transform.sql | 45 ++++ contrib/hstore_plperl/sql/hstore_plperl.sql | 120 +++++++++ contrib/hstore_plpython/.gitignore | 6 + contrib/hstore_plpython/Makefile | 30 +++ .../hstore_plpython/expected/hstore_plpython.out | 139 ++++++++++ contrib/hstore_plpython/hstore_plpython.c | 116 +++++++++ contrib/hstore_plpython/hstore_plpython2u--1.0.sql | 19 ++ contrib/hstore_plpython/hstore_plpython2u.control | 6 + contrib/hstore_plpython/hstore_plpython3u--1.0.sql | 19 ++ contrib/hstore_plpython/hstore_plpython3u.control | 6 + contrib/hstore_plpython/hstore_plpythonu--1.0.sql | 19 ++ contrib/hstore_plpython/hstore_plpythonu.control | 6 + contrib/hstore_plpython/sql/hstore_plpython.sql | 108 ++++++++ contrib/ltree_plpython/.gitignore | 6 + contrib/ltree_plpython/Makefile | 30 +++ contrib/ltree_plpython/expected/ltree_plpython.out | 42 +++ contrib/ltree_plpython/ltree_plpython.c | 32 +++ contrib/ltree_plpython/ltree_plpython2u--1.0.sql | 12 + contrib/ltree_plpython/ltree_plpython2u.control | 6 + contrib/ltree_plpython/ltree_plpython3u--1.0.sql | 12 + contrib/ltree_plpython/ltree_plpython3u.control | 6 + contrib/ltree_plpython/ltree_plpythonu--1.0.sql | 12 + contrib/ltree_plpython/ltree_plpythonu.control | 6 + contrib/ltree_plpython/sql/ltree_plpython.sql | 34 +++ doc/src/sgml/catalogs.sgml | 73 ++++++ doc/src/sgml/hstore.sgml | 18 ++ doc/src/sgml/ltree.sgml | 15 ++ doc/src/sgml/ref/allfiles.sgml | 2 + doc/src/sgml/ref/alter_extension.sgml | 21 ++ doc/src/sgml/ref/comment.sgml | 22 ++ doc/src/sgml/ref/create_transform.sgml | 190 ++++++++++++++ doc/src/sgml/ref/drop_transform.sgml | 123 +++++++++ doc/src/sgml/reference.sgml | 2 + src/Makefile.shlib | 2 +- src/backend/catalog/Makefile | 1 + src/backend/catalog/dependency.c | 8 + src/backend/catalog/objectaddress.c | 65 ++++- src/backend/catalog/pg_proc.c | 21 ++ src/backend/commands/dropcmds.c | 6 + src/backend/commands/event_trigger.c | 3 + src/backend/commands/functioncmds.c | 288 +++++++++++++++++++++ src/backend/nodes/copyfuncs.c | 17 ++ src/backend/nodes/equalfuncs.c | 15 ++ src/backend/parser/gram.y | 82 +++++- src/backend/tcop/utility.c | 16 ++ src/backend/utils/adt/ruleutils.c | 11 +- src/backend/utils/cache/lsyscache.c | 65 +++++ src/backend/utils/cache/syscache.c | 23 ++ src/bin/pg_dump/common.c | 5 + src/bin/pg_dump/pg_dump.c | 230 ++++++++++++++++ src/bin/pg_dump/pg_dump.h | 11 + src/bin/pg_dump/pg_dump_sort.c | 13 +- src/include/catalog/catversion.h | 2 +- src/include/catalog/dependency.h | 1 + src/include/catalog/indexing.h | 5 + src/include/catalog/pg_transform.h | 47 ++++ src/include/commands/defrem.h | 3 + src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 15 ++ src/include/parser/kwlist.h | 2 + src/include/utils/lsyscache.h | 3 + src/include/utils/syscache.h | 2 + src/interfaces/ecpg/preproc/ecpg.tokens | 2 +- src/interfaces/ecpg/preproc/ecpg.trailer | 5 +- src/interfaces/ecpg/preproc/ecpg_keywords.c | 2 - src/pl/plperl/GNUmakefile | 4 +- src/pl/plperl/plperl.c | 32 ++- src/pl/plperl/plperl_helpers.h | 2 + src/pl/plpython/Makefile | 40 +-- src/pl/plpython/plpy_main.c | 1 + src/pl/plpython/plpy_procedure.c | 31 ++- src/pl/plpython/plpy_procedure.h | 1 + src/pl/plpython/plpy_spi.c | 3 +- src/pl/plpython/plpy_typeio.c | 158 +++++++---- src/pl/plpython/plpy_typeio.h | 9 +- src/pl/plpython/plpy_util.c | 21 +- src/pl/plpython/plpy_util.h | 1 + src/pl/plpython/plpython.h | 1 + src/pl/plpython/regress-python3-mangle.mk | 35 +++ src/test/regress/expected/sanity_check.out | 3 +- 90 files changed, 2888 insertions(+), 144 deletions(-) create mode 100644 contrib/hstore_plperl/.gitignore create mode 100644 contrib/hstore_plperl/Makefile create mode 100644 contrib/hstore_plperl/expected/create_transform.out create mode 100644 contrib/hstore_plperl/expected/hstore_plperl.out create mode 100644 contrib/hstore_plperl/hstore_plperl--1.0.sql create mode 100644 contrib/hstore_plperl/hstore_plperl.c create mode 100644 contrib/hstore_plperl/hstore_plperl.control create mode 100644 contrib/hstore_plperl/hstore_plperlu--1.0.sql create mode 100644 contrib/hstore_plperl/hstore_plperlu.control create mode 100644 contrib/hstore_plperl/sql/create_transform.sql create mode 100644 contrib/hstore_plperl/sql/hstore_plperl.sql create mode 100644 contrib/hstore_plpython/.gitignore create mode 100644 contrib/hstore_plpython/Makefile create mode 100644 contrib/hstore_plpython/expected/hstore_plpython.out create mode 100644 contrib/hstore_plpython/hstore_plpython.c create mode 100644 contrib/hstore_plpython/hstore_plpython2u--1.0.sql create mode 100644 contrib/hstore_plpython/hstore_plpython2u.control create mode 100644 contrib/hstore_plpython/hstore_plpython3u--1.0.sql create mode 100644 contrib/hstore_plpython/hstore_plpython3u.control create mode 100644 contrib/hstore_plpython/hstore_plpythonu--1.0.sql create mode 100644 contrib/hstore_plpython/hstore_plpythonu.control create mode 100644 contrib/hstore_plpython/sql/hstore_plpython.sql create mode 100644 contrib/ltree_plpython/.gitignore create mode 100644 contrib/ltree_plpython/Makefile create mode 100644 contrib/ltree_plpython/expected/ltree_plpython.out create mode 100644 contrib/ltree_plpython/ltree_plpython.c create mode 100644 contrib/ltree_plpython/ltree_plpython2u--1.0.sql create mode 100644 contrib/ltree_plpython/ltree_plpython2u.control create mode 100644 contrib/ltree_plpython/ltree_plpython3u--1.0.sql create mode 100644 contrib/ltree_plpython/ltree_plpython3u.control create mode 100644 contrib/ltree_plpython/ltree_plpythonu--1.0.sql create mode 100644 contrib/ltree_plpython/ltree_plpythonu.control create mode 100644 contrib/ltree_plpython/sql/ltree_plpython.sql create mode 100644 doc/src/sgml/ref/create_transform.sgml create mode 100644 doc/src/sgml/ref/drop_transform.sgml create mode 100644 src/include/catalog/pg_transform.h create mode 100644 src/pl/plpython/regress-python3-mangle.mk diff --git a/contrib/Makefile b/contrib/Makefile index 8a2a937..6495b13 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -79,6 +79,18 @@ else ALWAYS_SUBDIRS += sepgsql endif +ifeq ($(with_perl),yes) +SUBDIRS += hstore_plperl +else +ALWAYS_SUBDIRS += hstore_plperl +endif + +ifeq ($(with_python),yes) +SUBDIRS += hstore_plpython ltree_plpython +else +ALWAYS_SUBDIRS += hstore_plpython ltree_plpython +endif + # Missing: # start-scripts \ (does not have a makefile) diff --git a/contrib/hstore_plperl/.gitignore b/contrib/hstore_plperl/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/hstore_plperl/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/hstore_plperl/Makefile b/contrib/hstore_plperl/Makefile new file mode 100644 index 0000000..81f037e --- /dev/null +++ b/contrib/hstore_plperl/Makefile @@ -0,0 +1,23 @@ +# contrib/hstore_plperl/Makefile + +MODULE_big = hstore_plperl +OBJS = hstore_plperl.o + +PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl -I$(perl_archlibexp)/CORE -I$(top_srcdir)/contrib/hstore + +EXTENSION = hstore_plperl hstore_plperlu +DATA = hstore_plperl--1.0.sql hstore_plperlu--1.0.sql + +REGRESS = hstore_plperl create_transform +REGRESS_OPTS = --extra-install=contrib/hstore --load-extension=hstore --load-extension=plperl --load-extension=plperlu + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/hstore_plperl +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/hstore_plperl/expected/create_transform.out b/contrib/hstore_plperl/expected/create_transform.out new file mode 100644 index 0000000..8111d1b --- /dev/null +++ b/contrib/hstore_plperl/expected/create_transform.out @@ -0,0 +1,70 @@ +-- general regression test for transforms +DROP EXTENSION IF EXISTS hstore CASCADE; +NOTICE: extension "hstore" does not exist, skipping +DROP EXTENSION IF EXISTS plperl CASCADE; +NOTICE: extension "plperl" does not exist, skipping +DROP EXTENSION IF EXISTS hstore_plperl CASCADE; +NOTICE: extension "hstore_plperl" does not exist, skipping +CREATE EXTENSION hstore; +CREATE EXTENSION plperl; +CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS '$libdir/hstore_plperl'; +CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS '$libdir/hstore_plperl'; +CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: type "foo" does not exist +CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: language "foo" does not exist +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: return data type of FROM SQL function must be "internal" +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: first argument of transform function must be type "internal" +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: transform for type hstore language plperl already exists +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +DROP TRANSFORM FOR foo LANGUAGE plperl; +ERROR: type "foo" does not exist +DROP TRANSFORM FOR hstore LANGUAGE foo; +ERROR: language "foo" does not exist +DROP TRANSFORM FOR hstore LANGUAGE plperl; +DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl; +NOTICE: transform for type hstore language plperl does not exist, skipping +DROP FUNCTION hstore_to_plperl(val internal); +DROP FUNCTION plperl_to_hstore(val internal); +CREATE EXTENSION hstore_plperl; +\dx+ hstore_plperl + Objects in extension "hstore_plperl" + Object Description +-------------------------------------- + function hstore_to_plperl(internal) + function plperl_to_hstore(internal) + transform for hstore language plperl +(3 rows) + +ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl; +\dx+ hstore_plperl +Objects in extension "hstore_plperl" + Object Description +------------------------------------- + function hstore_to_plperl(internal) + function plperl_to_hstore(internal) +(2 rows) + +ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl; +\dx+ hstore_plperl + Objects in extension "hstore_plperl" + Object Description +-------------------------------------- + function hstore_to_plperl(internal) + function plperl_to_hstore(internal) + transform for hstore language plperl +(3 rows) + +DROP EXTENSION hstore CASCADE; +NOTICE: drop cascades to extension hstore_plperl +DROP EXTENSION plperl CASCADE; diff --git a/contrib/hstore_plperl/expected/hstore_plperl.out b/contrib/hstore_plperl/expected/hstore_plperl.out new file mode 100644 index 0000000..ce31ab8 --- /dev/null +++ b/contrib/hstore_plperl/expected/hstore_plperl.out @@ -0,0 +1,170 @@ +CREATE EXTENSION hstore_plperl; +CREATE EXTENSION hstore_plperlu; +-- test hstore -> perl +CREATE FUNCTION test1(val hstore) RETURNS int +LANGUAGE plperlu +AS $$ +use Data::Dumper; +elog(INFO, Dumper($_[0])); +return scalar(keys %{$_[0]}); +$$; +SELECT test1('aa=>bb, cc=>NULL'::hstore); +INFO: $VAR1 = { + 'cc' => undef, + 'aa' => 'bb' + }; + +CONTEXT: PL/Perl function "test1" + test1 +------- + 2 +(1 row) + +-- test hstore[] -> perl +CREATE FUNCTION test1arr(val hstore[]) RETURNS int +LANGUAGE plperlu +AS $$ +use Data::Dumper; +elog(INFO, Dumper($_[0]->[0], $_[0]->[1])); +return scalar(keys %{$_[0]}); +$$; +SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']); +INFO: $VAR1 = { + 'cc' => undef, + 'aa' => 'bb' + }; +$VAR2 = { + 'dd' => 'ee' + }; + +CONTEXT: PL/Perl function "test1arr" + test1arr +---------- + 2 +(1 row) + +-- test perl -> hstore +CREATE FUNCTION test2() RETURNS hstore +LANGUAGE plperl +AS $$ +$val = {a => 1, b => 'boo', c => undef}; +return $val; +$$; +SELECT test2(); + test2 +--------------------------------- + "a"=>"1", "b"=>"boo", "c"=>NULL +(1 row) + +-- test perl -> hstore[] +CREATE FUNCTION test2arr() RETURNS hstore[] +LANGUAGE plperl +AS $$ +$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; +return $val; +$$; +SELECT test2arr(); + test2arr +-------------------------------------------------------------- + {"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""} +(1 row) + +-- test as part of prepare/execute +CREATE FUNCTION test3() RETURNS void +LANGUAGE plperlu +AS $$ +use Data::Dumper; + +$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1}); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); + +$val = {a => 1, b => 'boo', c => undef}; +$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore"); +$rv = spi_exec_prepared($plan, {}, $val); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); +$$; +SELECT test3(); +INFO: $VAR1 = { + 'cc' => undef, + 'aa' => 'bb' + }; + +CONTEXT: PL/Perl function "test3" +INFO: $VAR1 = '"a"=>"1", "b"=>"boo", "c"=>NULL'; + +CONTEXT: PL/Perl function "test3" + test3 +------- + +(1 row) + +-- test inline +DO LANGUAGE plperlu $$ +use Data::Dumper; + +$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1}); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); + +$val = {a => 1, b => 'boo', c => undef}; +$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore"); +$rv = spi_exec_prepared($plan, {}, $val); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); +$$; +INFO: $VAR1 = { + 'cc' => undef, + 'aa' => 'bb' + }; + +CONTEXT: PL/Perl anonymous code block +INFO: $VAR1 = '"a"=>"1", "b"=>"boo", "c"=>NULL'; + +CONTEXT: PL/Perl anonymous code block +-- test trigger +CREATE TABLE test1 (a int, b hstore); +INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL'); +SELECT * FROM test1; + a | b +---+------------------------ + 1 | "aa"=>"bb", "cc"=>NULL +(1 row) + +CREATE FUNCTION test4() RETURNS trigger +LANGUAGE plperlu +AS $$ +use Data::Dumper; +elog(INFO, Dumper($_TD->{new})); +if ($_TD->{new}{a} == 1) { + $_TD->{new}{b} = {a => 1, b => 'boo', c => undef}; +} + +return "MODIFY"; +$$; +CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4(); +UPDATE test1 SET a = a; +INFO: $VAR1 = { + 'a' => '1', + 'b' => { + 'cc' => undef, + 'aa' => 'bb' + } + }; + +CONTEXT: PL/Perl function "test4" +SELECT * FROM test1; + a | b +---+--------------------------------- + 1 | "a"=>"1", "b"=>"boo", "c"=>NULL +(1 row) + +DROP TABLE test1; +DROP FUNCTION test1(hstore); +DROP FUNCTION test1arr(hstore[]); +DROP FUNCTION test2(); +DROP FUNCTION test2arr(); +DROP FUNCTION test3(); +DROP FUNCTION test4(); +DROP EXTENSION hstore_plperl; +DROP EXTENSION hstore_plperlu; +DROP EXTENSION hstore; +DROP EXTENSION plperl; +DROP EXTENSION plperlu; diff --git a/contrib/hstore_plperl/hstore_plperl--1.0.sql b/contrib/hstore_plperl/hstore_plperl--1.0.sql new file mode 100644 index 0000000..ea0ad76 --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperl--1.0.sql @@ -0,0 +1,17 @@ +-- make sure the prerequisite libraries are loaded +DO '' LANGUAGE plperl; +SELECT NULL::hstore; + + +CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE TRANSFORM FOR hstore LANGUAGE plperl ( + FROM SQL WITH FUNCTION hstore_to_plperl(internal), + TO SQL WITH FUNCTION plperl_to_hstore(internal) +); diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c new file mode 100644 index 0000000..cdc224c --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperl.c @@ -0,0 +1,90 @@ +#include "postgres.h" +#undef _ +#include "fmgr.h" +#include "plperl.h" +#include "plperl_helpers.h" +#include "hstore.h" + +PG_MODULE_MAGIC; + + +PG_FUNCTION_INFO_V1(hstore_to_plperl); +Datum hstore_to_plperl(PG_FUNCTION_ARGS); + +Datum +hstore_to_plperl(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HS(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + HV *hv; + + hv = newHV(); + + for (i = 0; i < count; i++) + { + const char *key; + SV *value; + + key = pnstrdup(HS_KEY(entries, base, i), HS_KEYLEN(entries, i)); + value = HS_VALISNULL(entries, i) ? newSV(0) : cstr2sv(pnstrdup(HS_VAL(entries, base,i), HS_VALLEN(entries, i))); + + (void) hv_store(hv, key, strlen(key), value, 0); + } + + return PointerGetDatum(newRV((SV *) hv)); +} + + +PG_FUNCTION_INFO_V1(plperl_to_hstore); +Datum plperl_to_hstore(PG_FUNCTION_ARGS); + +Datum +plperl_to_hstore(PG_FUNCTION_ARGS) +{ + HV *hv; + HE *he; + int32 buflen; + int32 i; + int32 pcount; + HStore *out; + Pairs *pairs; + + hv = (HV *) SvRV((SV *) PG_GETARG_POINTER(0)); + + pcount = hv_iterinit(hv); + + pairs = palloc(pcount * sizeof(Pairs)); + + i = 0; + while ((he = hv_iternext(hv))) + { + char *key = sv2cstr(HeSVKEY_force(he)); + SV *value = HeVAL(he); + + pairs[i].key = pstrdup(key); + pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key)); + pairs[i].needfree = true; + + if (!SvOK(value)) + { + pairs[i].val = NULL; + pairs[i].vallen = 0; + pairs[i].isnull = true; + } + else + { + pairs[i].val = pstrdup(sv2cstr(value)); + pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val)); + pairs[i].isnull = false; + } + + i++; + } + + pcount = hstoreUniquePairs(pairs, pcount, &buflen); + out = hstorePairs(pairs, pcount, buflen); + PG_RETURN_POINTER(out); +} diff --git a/contrib/hstore_plperl/hstore_plperl.control b/contrib/hstore_plperl/hstore_plperl.control new file mode 100644 index 0000000..16277f6 --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperl.control @@ -0,0 +1,6 @@ +# hstore_plperl extension +comment = 'transform between hstore and plperl' +default_version = '1.0' +module_pathname = '$libdir/hstore_plperl' +relocatable = true +requires = 'hstore,plperl' diff --git a/contrib/hstore_plperl/hstore_plperlu--1.0.sql b/contrib/hstore_plperl/hstore_plperlu--1.0.sql new file mode 100644 index 0000000..46ad35c --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperlu--1.0.sql @@ -0,0 +1,17 @@ +-- make sure the prerequisite libraries are loaded +DO '' LANGUAGE plperlu; +SELECT NULL::hstore; + + +CREATE FUNCTION hstore_to_plperlu(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'hstore_to_plperl'; + +CREATE FUNCTION plperlu_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'plperl_to_hstore'; + +CREATE TRANSFORM FOR hstore LANGUAGE plperlu ( + FROM SQL WITH FUNCTION hstore_to_plperlu(internal), + TO SQL WITH FUNCTION plperlu_to_hstore(internal) +); diff --git a/contrib/hstore_plperl/hstore_plperlu.control b/contrib/hstore_plperl/hstore_plperlu.control new file mode 100644 index 0000000..c8d43b4 --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperlu.control @@ -0,0 +1,6 @@ +# hstore_plperlu extension +comment = 'transform between hstore and plperlu' +default_version = '1.0' +module_pathname = '$libdir/hstore_plperl' +relocatable = true +requires = 'hstore,plperlu' diff --git a/contrib/hstore_plperl/sql/create_transform.sql b/contrib/hstore_plperl/sql/create_transform.sql new file mode 100644 index 0000000..4f615e7 --- /dev/null +++ b/contrib/hstore_plperl/sql/create_transform.sql @@ -0,0 +1,45 @@ +-- general regression test for transforms + +DROP EXTENSION IF EXISTS hstore CASCADE; +DROP EXTENSION IF EXISTS plperl CASCADE; +DROP EXTENSION IF EXISTS hstore_plperl CASCADE; + +CREATE EXTENSION hstore; +CREATE EXTENSION plperl; + +CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS '$libdir/hstore_plperl'; + +CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS '$libdir/hstore_plperl'; + +CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail + +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok + +DROP TRANSFORM FOR foo LANGUAGE plperl; +DROP TRANSFORM FOR hstore LANGUAGE foo; +DROP TRANSFORM FOR hstore LANGUAGE plperl; +DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl; + +DROP FUNCTION hstore_to_plperl(val internal); +DROP FUNCTION plperl_to_hstore(val internal); + +CREATE EXTENSION hstore_plperl; +\dx+ hstore_plperl +ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl; +\dx+ hstore_plperl +ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl; +\dx+ hstore_plperl + +DROP EXTENSION hstore CASCADE; +DROP EXTENSION plperl CASCADE; diff --git a/contrib/hstore_plperl/sql/hstore_plperl.sql b/contrib/hstore_plperl/sql/hstore_plperl.sql new file mode 100644 index 0000000..4796050 --- /dev/null +++ b/contrib/hstore_plperl/sql/hstore_plperl.sql @@ -0,0 +1,120 @@ +CREATE EXTENSION hstore_plperl; +CREATE EXTENSION hstore_plperlu; + + +-- test hstore -> perl +CREATE FUNCTION test1(val hstore) RETURNS int +LANGUAGE plperlu +AS $$ +use Data::Dumper; +elog(INFO, Dumper($_[0])); +return scalar(keys %{$_[0]}); +$$; + +SELECT test1('aa=>bb, cc=>NULL'::hstore); + + +-- test hstore[] -> perl +CREATE FUNCTION test1arr(val hstore[]) RETURNS int +LANGUAGE plperlu +AS $$ +use Data::Dumper; +elog(INFO, Dumper($_[0]->[0], $_[0]->[1])); +return scalar(keys %{$_[0]}); +$$; + +SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']); + + +-- test perl -> hstore +CREATE FUNCTION test2() RETURNS hstore +LANGUAGE plperl +AS $$ +$val = {a => 1, b => 'boo', c => undef}; +return $val; +$$; + +SELECT test2(); + + +-- test perl -> hstore[] +CREATE FUNCTION test2arr() RETURNS hstore[] +LANGUAGE plperl +AS $$ +$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; +return $val; +$$; + +SELECT test2arr(); + + +-- test as part of prepare/execute +CREATE FUNCTION test3() RETURNS void +LANGUAGE plperlu +AS $$ +use Data::Dumper; + +$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1}); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); + +$val = {a => 1, b => 'boo', c => undef}; +$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore"); +$rv = spi_exec_prepared($plan, {}, $val); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); +$$; + +SELECT test3(); + + +-- test inline +DO LANGUAGE plperlu $$ +use Data::Dumper; + +$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1}); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); + +$val = {a => 1, b => 'boo', c => undef}; +$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore"); +$rv = spi_exec_prepared($plan, {}, $val); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); +$$; + + +-- test trigger +CREATE TABLE test1 (a int, b hstore); +INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL'); +SELECT * FROM test1; + +CREATE FUNCTION test4() RETURNS trigger +LANGUAGE plperlu +AS $$ +use Data::Dumper; +elog(INFO, Dumper($_TD->{new})); +if ($_TD->{new}{a} == 1) { + $_TD->{new}{b} = {a => 1, b => 'boo', c => undef}; +} + +return "MODIFY"; +$$; + +CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4(); + +UPDATE test1 SET a = a; +SELECT * FROM test1; + + +DROP TABLE test1; + +DROP FUNCTION test1(hstore); +DROP FUNCTION test1arr(hstore[]); +DROP FUNCTION test2(); +DROP FUNCTION test2arr(); +DROP FUNCTION test3(); +DROP FUNCTION test4(); + + +DROP EXTENSION hstore_plperl; +DROP EXTENSION hstore_plperlu; +DROP EXTENSION hstore; +DROP EXTENSION plperl; +DROP EXTENSION plperlu; diff --git a/contrib/hstore_plpython/.gitignore b/contrib/hstore_plpython/.gitignore new file mode 100644 index 0000000..ce6fab9 --- /dev/null +++ b/contrib/hstore_plpython/.gitignore @@ -0,0 +1,6 @@ +# Generated subdirectories +/expected/python3/ +/log/ +/results/ +/sql/python3/ +/tmp_check/ diff --git a/contrib/hstore_plpython/Makefile b/contrib/hstore_plpython/Makefile new file mode 100644 index 0000000..65ddcf7 --- /dev/null +++ b/contrib/hstore_plpython/Makefile @@ -0,0 +1,30 @@ +# contrib/hstore_plpython/Makefile + +MODULE_big = hstore_plpython$(python_majorversion) +OBJS = hstore_plpython.o + +PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/hstore + +EXTENSION = hstore_plpythonu hstore_plpython2u hstore_plpython3u +DATA = hstore_plpythonu--1.0.sql hstore_plpython2u--1.0.sql hstore_plpython3u--1.0.sql + +REGRESS = hstore_plpython +REGRESS_PLPYTHON3_MANGLE := $(REGRESS) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/hstore_plpython +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +REGRESS_OPTS = --extra-install=contrib/hstore --load-extension=hstore +ifeq ($(python_majorversion),2) +REGRESS_OPTS += --load-extension=plpythonu --load-extension=hstore_plpythonu +endif + +include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out b/contrib/hstore_plpython/expected/hstore_plpython.out new file mode 100644 index 0000000..fa61251 --- /dev/null +++ b/contrib/hstore_plpython/expected/hstore_plpython.out @@ -0,0 +1,139 @@ +CREATE EXTENSION plpython2u; +CREATE EXTENSION hstore_plpython2u; +-- test hstore -> python +CREATE FUNCTION test1(val hstore) RETURNS int +LANGUAGE plpythonu +AS $$ +assert isinstance(val, dict) +plpy.info(sorted(val.items())) +return len(val) +$$; +SELECT test1('aa=>bb, cc=>NULL'::hstore); +INFO: [('aa', 'bb'), ('cc', None)] +CONTEXT: PL/Python function "test1" + test1 +------- + 2 +(1 row) + +-- the same with the versioned language name +CREATE FUNCTION test1n(val hstore) RETURNS int +LANGUAGE plpython2u +AS $$ +assert isinstance(val, dict) +plpy.info(sorted(val.items())) +return len(val) +$$; +SELECT test1n('aa=>bb, cc=>NULL'::hstore); +INFO: [('aa', 'bb'), ('cc', None)] +CONTEXT: PL/Python function "test1n" + test1n +-------- + 2 +(1 row) + +-- test hstore[] -> python + CREATE FUNCTION test1arr(val hstore[]) RETURNS int + LANGUAGE plpythonu + AS $$ + plpy.info(repr(val)) + return len(val) + $$; + SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']); +INFO: [{'aa': 'bb', 'cc': None}, {'dd': 'ee'}] +CONTEXT: PL/Python function "test1arr" + test1arr +---------- + 2 +(1 row) + +-- test python -> hstore +CREATE FUNCTION test2() RETURNS hstore +LANGUAGE plpythonu +AS $$ +val = {'a': 1, 'b': 'boo', 'c': None} +return val +$$; +SELECT test2(); + test2 +--------------------------------- + "a"=>"1", "b"=>"boo", "c"=>NULL +(1 row) + +-- test python -> hstore[] + CREATE FUNCTION test2arr() RETURNS hstore[] + LANGUAGE plpythonu + AS $$ + val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}] + return val + $$; + SELECT test2arr(); + test2arr +-------------------------------------------------------------- + {"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""} +(1 row) + +-- test as part of prepare/execute +CREATE FUNCTION test3() RETURNS void +LANGUAGE plpythonu +AS $$ +rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1") +plpy.info(repr(rv[0]["col1"])) + +val = {'a': 1, 'b': 'boo', 'c': None} +plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"]) +rv = plpy.execute(plan, [val]) +plpy.info(repr(rv[0]["col1"])) +$$; +SELECT test3(); +INFO: {'aa': 'bb', 'cc': None} +CONTEXT: PL/Python function "test3" +INFO: '"a"=>"1", "b"=>"boo", "c"=>NULL' +CONTEXT: PL/Python function "test3" + test3 +------- + +(1 row) + +-- test inline +DO LANGUAGE plpythonu $$ +rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1") +plpy.info(repr(rv[0]["col1"])) + +val = {'a': 1, 'b': 'boo', 'c': None} +plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"]) +rv = plpy.execute(plan, [val]) +plpy.info(repr(rv[0]["col1"])) +$$; +INFO: {'aa': 'bb', 'cc': None} +CONTEXT: PL/Python anonymous code block +INFO: '"a"=>"1", "b"=>"boo", "c"=>NULL' +CONTEXT: PL/Python anonymous code block +-- test trigger +CREATE TABLE test1 (a int, b hstore); +INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL'); +SELECT * FROM test1; + a | b +---+------------------------ + 1 | "aa"=>"bb", "cc"=>NULL +(1 row) + +CREATE FUNCTION test4() RETURNS trigger +LANGUAGE plpythonu +AS $$ +plpy.info("Trigger row: {'a': %r, 'b': %r}" % (TD["new"]["a"], TD["new"]["b"])) +if TD["new"]["a"] == 1: + TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None} + +return "MODIFY" +$$; +CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4(); +UPDATE test1 SET a = a; +INFO: Trigger row: {'a': 1, 'b': {'aa': 'bb', 'cc': None}} +CONTEXT: PL/Python function "test4" +SELECT * FROM test1; + a | b +---+--------------------------------- + 1 | "a"=>"1", "b"=>"boo", "c"=>NULL +(1 row) + diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c new file mode 100644 index 0000000..92cd4f8 --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython.c @@ -0,0 +1,116 @@ +#include "postgres.h" +#include "fmgr.h" +#include "plpython.h" +#include "plpy_typeio.h" +#include "hstore.h" + +PG_MODULE_MAGIC; + + +PG_FUNCTION_INFO_V1(hstore_to_plpython); +Datum hstore_to_plpython(PG_FUNCTION_ARGS); + +Datum +hstore_to_plpython(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HS(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + PyObject *dict; + + dict = PyDict_New(); + + for (i = 0; i < count; i++) + { + PyObject *key; + + key = PyString_FromStringAndSize(HS_KEY(entries, base, i), HS_KEYLEN(entries, i)); + if (HS_VALISNULL(entries, i)) + PyDict_SetItem(dict, key, Py_None); + else + { + PyObject *value; + + value = PyString_FromStringAndSize(HS_VAL(entries, base,i), HS_VALLEN(entries, i)); + PyDict_SetItem(dict, key, value); + Py_XDECREF(value); + } + Py_XDECREF(key); + } + + return PointerGetDatum(dict); +} + + +PG_FUNCTION_INFO_V1(plpython_to_hstore); +Datum plpython_to_hstore(PG_FUNCTION_ARGS); + +Datum +plpython_to_hstore(PG_FUNCTION_ARGS) +{ + PyObject *dict; + volatile PyObject *items_v = NULL; + int32 pcount; + HStore *out; + + dict = (PyObject *) PG_GETARG_POINTER(0); + if (!PyMapping_Check(dict)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("not a Python mapping"))); + + pcount = PyMapping_Size(dict); + items_v = PyMapping_Items(dict); + + PG_TRY(); + { + int32 buflen; + int32 i; + Pairs *pairs; + PyObject *items = (PyObject *) items_v; + + pairs = palloc(pcount * sizeof(*pairs)); + + for (i = 0; i < pcount; i++) + { + PyObject *tuple; + PyObject *key; + PyObject *value; + + tuple = PyList_GetItem(items, i); + key = PyTuple_GetItem(tuple, 0); + value = PyTuple_GetItem(tuple, 1); + + pairs[i].key = PLyObject_AsString(key); + pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key)); + pairs[i].needfree = true; + + if (value == Py_None) + { + pairs[i].val = NULL; + pairs[i].vallen = 0; + pairs[i].isnull = true; + } + else + { + pairs[i].val = PLyObject_AsString(value); + pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val)); + pairs[i].isnull = false; + } + } + Py_DECREF(items_v); + + pcount = hstoreUniquePairs(pairs, pcount, &buflen); + out = hstorePairs(pairs, pcount, buflen); + } + PG_CATCH(); + { + Py_DECREF(items_v); + PG_RE_THROW(); + } + PG_END_TRY(); + + PG_RETURN_POINTER(out); +} diff --git a/contrib/hstore_plpython/hstore_plpython2u--1.0.sql b/contrib/hstore_plpython/hstore_plpython2u--1.0.sql new file mode 100644 index 0000000..c998de5 --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython2u--1.0.sql @@ -0,0 +1,19 @@ +-- make sure the prerequisite libraries are loaded +DO '1' LANGUAGE plpython2u; +SELECT NULL::hstore; + + +CREATE FUNCTION hstore_to_plpython2(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'hstore_to_plpython'; + +CREATE FUNCTION plpython2_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'plpython_to_hstore'; + +CREATE TRANSFORM FOR hstore LANGUAGE plpython2u ( + FROM SQL WITH FUNCTION hstore_to_plpython2(internal), + TO SQL WITH FUNCTION plpython2_to_hstore(internal) +); + +COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython2u IS 'transform between hstore and Python dict'; diff --git a/contrib/hstore_plpython/hstore_plpython2u.control b/contrib/hstore_plpython/hstore_plpython2u.control new file mode 100644 index 0000000..ed90567 --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython2u.control @@ -0,0 +1,6 @@ +# hstore_plpython2u extension +comment = 'transform between hstore and plpython2u' +default_version = '1.0' +module_pathname = '$libdir/hstore_plpython2' +relocatable = true +requires = 'hstore,plpython2u' diff --git a/contrib/hstore_plpython/hstore_plpython3u--1.0.sql b/contrib/hstore_plpython/hstore_plpython3u--1.0.sql new file mode 100644 index 0000000..61d0e47 --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython3u--1.0.sql @@ -0,0 +1,19 @@ +-- make sure the prerequisite libraries are loaded +DO '1' LANGUAGE plpython3u; +SELECT NULL::hstore; + + +CREATE FUNCTION hstore_to_plpython3(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'hstore_to_plpython'; + +CREATE FUNCTION plpython3_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'plpython_to_hstore'; + +CREATE TRANSFORM FOR hstore LANGUAGE plpython3u ( + FROM SQL WITH FUNCTION hstore_to_plpython3(internal), + TO SQL WITH FUNCTION plpython3_to_hstore(internal) +); + +COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython3u IS 'transform between hstore and Python dict'; diff --git a/contrib/hstore_plpython/hstore_plpython3u.control b/contrib/hstore_plpython/hstore_plpython3u.control new file mode 100644 index 0000000..d86f38e --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython3u.control @@ -0,0 +1,6 @@ +# hstore_plpython3u extension +comment = 'transform between hstore and plpython3u' +default_version = '1.0' +module_pathname = '$libdir/hstore_plpython3' +relocatable = true +requires = 'hstore,plpython3u' diff --git a/contrib/hstore_plpython/hstore_plpythonu--1.0.sql b/contrib/hstore_plpython/hstore_plpythonu--1.0.sql new file mode 100644 index 0000000..6acb97a --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpythonu--1.0.sql @@ -0,0 +1,19 @@ +-- make sure the prerequisite libraries are loaded +DO '1' LANGUAGE plpythonu; +SELECT NULL::hstore; + + +CREATE FUNCTION hstore_to_plpython(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION plpython_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE TRANSFORM FOR hstore LANGUAGE plpythonu ( + FROM SQL WITH FUNCTION hstore_to_plpython(internal), + TO SQL WITH FUNCTION plpython_to_hstore(internal) +); + +COMMENT ON TRANSFORM FOR hstore LANGUAGE plpythonu IS 'transform between hstore and Python dict'; diff --git a/contrib/hstore_plpython/hstore_plpythonu.control b/contrib/hstore_plpython/hstore_plpythonu.control new file mode 100644 index 0000000..8e9b35e --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpythonu.control @@ -0,0 +1,6 @@ +# hstore_plpythonu extension +comment = 'transform between hstore and plpythonu' +default_version = '1.0' +module_pathname = '$libdir/hstore_plpython2' +relocatable = true +requires = 'hstore,plpythonu' diff --git a/contrib/hstore_plpython/sql/hstore_plpython.sql b/contrib/hstore_plpython/sql/hstore_plpython.sql new file mode 100644 index 0000000..2638645 --- /dev/null +++ b/contrib/hstore_plpython/sql/hstore_plpython.sql @@ -0,0 +1,108 @@ +CREATE EXTENSION plpython2u; +CREATE EXTENSION hstore_plpython2u; + + +-- test hstore -> python +CREATE FUNCTION test1(val hstore) RETURNS int +LANGUAGE plpythonu +AS $$ +assert isinstance(val, dict) +plpy.info(sorted(val.items())) +return len(val) +$$; + +SELECT test1('aa=>bb, cc=>NULL'::hstore); + + +-- the same with the versioned language name +CREATE FUNCTION test1n(val hstore) RETURNS int +LANGUAGE plpython2u +AS $$ +assert isinstance(val, dict) +plpy.info(sorted(val.items())) +return len(val) +$$; + +SELECT test1n('aa=>bb, cc=>NULL'::hstore); + + +-- test hstore[] -> python + CREATE FUNCTION test1arr(val hstore[]) RETURNS int + LANGUAGE plpythonu + AS $$ + plpy.info(repr(val)) + return len(val) + $$; + + SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']); + + +-- test python -> hstore +CREATE FUNCTION test2() RETURNS hstore +LANGUAGE plpythonu +AS $$ +val = {'a': 1, 'b': 'boo', 'c': None} +return val +$$; + +SELECT test2(); + + +-- test python -> hstore[] + CREATE FUNCTION test2arr() RETURNS hstore[] + LANGUAGE plpythonu + AS $$ + val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}] + return val + $$; + + SELECT test2arr(); + + +-- test as part of prepare/execute +CREATE FUNCTION test3() RETURNS void +LANGUAGE plpythonu +AS $$ +rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1") +plpy.info(repr(rv[0]["col1"])) + +val = {'a': 1, 'b': 'boo', 'c': None} +plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"]) +rv = plpy.execute(plan, [val]) +plpy.info(repr(rv[0]["col1"])) +$$; + +SELECT test3(); + + +-- test inline +DO LANGUAGE plpythonu $$ +rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1") +plpy.info(repr(rv[0]["col1"])) + +val = {'a': 1, 'b': 'boo', 'c': None} +plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"]) +rv = plpy.execute(plan, [val]) +plpy.info(repr(rv[0]["col1"])) +$$; + + +-- test trigger +CREATE TABLE test1 (a int, b hstore); +INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL'); +SELECT * FROM test1; + +CREATE FUNCTION test4() RETURNS trigger +LANGUAGE plpythonu +AS $$ +plpy.info("Trigger row: {'a': %r, 'b': %r}" % (TD["new"]["a"], TD["new"]["b"])) +if TD["new"]["a"] == 1: + TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None} + +return "MODIFY" +$$; + +CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4(); + +UPDATE test1 SET a = a; +SELECT * FROM test1; diff --git a/contrib/ltree_plpython/.gitignore b/contrib/ltree_plpython/.gitignore new file mode 100644 index 0000000..ce6fab9 --- /dev/null +++ b/contrib/ltree_plpython/.gitignore @@ -0,0 +1,6 @@ +# Generated subdirectories +/expected/python3/ +/log/ +/results/ +/sql/python3/ +/tmp_check/ diff --git a/contrib/ltree_plpython/Makefile b/contrib/ltree_plpython/Makefile new file mode 100644 index 0000000..34e03ce --- /dev/null +++ b/contrib/ltree_plpython/Makefile @@ -0,0 +1,30 @@ +# contrib/ltree_plpython/Makefile + +MODULE_big = ltree_plpython$(python_majorversion) +OBJS = ltree_plpython.o + +PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/ltree + +EXTENSION = ltree_plpythonu ltree_plpython2u ltree_plpython3u +DATA = ltree_plpythonu--1.0.sql ltree_plpython2u--1.0.sql ltree_plpython3u--1.0.sql + +REGRESS = ltree_plpython +REGRESS_PLPYTHON3_MANGLE := $(REGRESS) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/ltree_plpython +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +REGRESS_OPTS = --extra-install=contrib/ltree --load-extension=ltree +ifeq ($(python_majorversion),2) +REGRESS_OPTS += --load-extension=plpythonu --load-extension=ltree_plpythonu +endif + +include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk diff --git a/contrib/ltree_plpython/expected/ltree_plpython.out b/contrib/ltree_plpython/expected/ltree_plpython.out new file mode 100644 index 0000000..6626fe9 --- /dev/null +++ b/contrib/ltree_plpython/expected/ltree_plpython.out @@ -0,0 +1,42 @@ +CREATE EXTENSION plpython2u; +CREATE EXTENSION ltree_plpython2u; +CREATE FUNCTION test1(val ltree) RETURNS int +LANGUAGE plpythonu +AS $$ +plpy.info(repr(val)) +return len(val) +$$; +SELECT test1('aa.bb.cc'::ltree); +INFO: ['aa', 'bb', 'cc'] +CONTEXT: PL/Python function "test1" + test1 +------- + 3 +(1 row) + +CREATE FUNCTION test1n(val ltree) RETURNS int +LANGUAGE plpython2u +AS $$ +plpy.info(repr(val)) +return len(val) +$$; +SELECT test1n('aa.bb.cc'::ltree); +INFO: ['aa', 'bb', 'cc'] +CONTEXT: PL/Python function "test1n" + test1n +-------- + 3 +(1 row) + +CREATE FUNCTION test2() RETURNS ltree +LANGUAGE plpythonu +AS $$ +return ['foo', 'bar', 'baz'] +$$; +-- plpython to ltree is not yet implemented, so this will fail, +-- because it will try to parse the Python list as an ltree input +-- string. +SELECT test2(); +ERROR: syntax error at position 0 +CONTEXT: while creating return value +PL/Python function "test2" diff --git a/contrib/ltree_plpython/ltree_plpython.c b/contrib/ltree_plpython/ltree_plpython.c new file mode 100644 index 0000000..111e3e3 --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpython.c @@ -0,0 +1,32 @@ +#include "postgres.h" +#include "fmgr.h" +#include "plpython.h" +#include "ltree.h" + +PG_MODULE_MAGIC; + + +PG_FUNCTION_INFO_V1(ltree_to_plpython); +Datum ltree_to_plpython(PG_FUNCTION_ARGS); + +Datum +ltree_to_plpython(PG_FUNCTION_ARGS) +{ + ltree *in = PG_GETARG_LTREE(0); + int i; + PyObject *list; + ltree_level *curlevel; + + list = PyList_New(in->numlevel); + + curlevel = LTREE_FIRST(in); + for (i = 0; i < in->numlevel; i++) + { + PyList_SetItem(list, i, PyString_FromStringAndSize(curlevel->name, curlevel->len)); + curlevel = LEVEL_NEXT(curlevel); + } + + PG_FREE_IF_COPY(in, 0); + + return PointerGetDatum(list); +} diff --git a/contrib/ltree_plpython/ltree_plpython2u--1.0.sql b/contrib/ltree_plpython/ltree_plpython2u--1.0.sql new file mode 100644 index 0000000..29a12d4 --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpython2u--1.0.sql @@ -0,0 +1,12 @@ +-- make sure the prerequisite libraries are loaded +DO '1' LANGUAGE plpython2u; +SELECT NULL::ltree; + + +CREATE FUNCTION ltree_to_plpython2(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'ltree_to_plpython'; + +CREATE TRANSFORM FOR ltree LANGUAGE plpython2u ( + FROM SQL WITH FUNCTION ltree_to_plpython2(internal) +); diff --git a/contrib/ltree_plpython/ltree_plpython2u.control b/contrib/ltree_plpython/ltree_plpython2u.control new file mode 100644 index 0000000..bedfd0a --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpython2u.control @@ -0,0 +1,6 @@ +# ltree_plpython2u extension +comment = 'transform between ltree and plpython2u' +default_version = '1.0' +module_pathname = '$libdir/ltree_plpython2' +relocatable = true +requires = 'ltree,plpython2u' diff --git a/contrib/ltree_plpython/ltree_plpython3u--1.0.sql b/contrib/ltree_plpython/ltree_plpython3u--1.0.sql new file mode 100644 index 0000000..1300a78 --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpython3u--1.0.sql @@ -0,0 +1,12 @@ +-- make sure the prerequisite libraries are loaded +DO '1' LANGUAGE plpython3u; +SELECT NULL::ltree; + + +CREATE FUNCTION ltree_to_plpython3(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'ltree_to_plpython'; + +CREATE TRANSFORM FOR ltree LANGUAGE plpython3u ( + FROM SQL WITH FUNCTION ltree_to_plpython3(internal) +); diff --git a/contrib/ltree_plpython/ltree_plpython3u.control b/contrib/ltree_plpython/ltree_plpython3u.control new file mode 100644 index 0000000..96c9764 --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpython3u.control @@ -0,0 +1,6 @@ +# ltree_plpython3u extension +comment = 'transform between ltree and plpython3u' +default_version = '1.0' +module_pathname = '$libdir/ltree_plpython3' +relocatable = true +requires = 'ltree,plpython3u' diff --git a/contrib/ltree_plpython/ltree_plpythonu--1.0.sql b/contrib/ltree_plpython/ltree_plpythonu--1.0.sql new file mode 100644 index 0000000..1d1af28 --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpythonu--1.0.sql @@ -0,0 +1,12 @@ +-- make sure the prerequisite libraries are loaded +DO '1' LANGUAGE plpythonu; +SELECT NULL::ltree; + + +CREATE FUNCTION ltree_to_plpython(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE TRANSFORM FOR ltree LANGUAGE plpythonu ( + FROM SQL WITH FUNCTION ltree_to_plpython(internal) +); diff --git a/contrib/ltree_plpython/ltree_plpythonu.control b/contrib/ltree_plpython/ltree_plpythonu.control new file mode 100644 index 0000000..b03c89a --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpythonu.control @@ -0,0 +1,6 @@ +# ltree_plpythonu extension +comment = 'transform between ltree and plpythonu' +default_version = '1.0' +module_pathname = '$libdir/ltree_plpython2' +relocatable = true +requires = 'ltree,plpythonu' diff --git a/contrib/ltree_plpython/sql/ltree_plpython.sql b/contrib/ltree_plpython/sql/ltree_plpython.sql new file mode 100644 index 0000000..2785592 --- /dev/null +++ b/contrib/ltree_plpython/sql/ltree_plpython.sql @@ -0,0 +1,34 @@ +CREATE EXTENSION plpython2u; +CREATE EXTENSION ltree_plpython2u; + + +CREATE FUNCTION test1(val ltree) RETURNS int +LANGUAGE plpythonu +AS $$ +plpy.info(repr(val)) +return len(val) +$$; + +SELECT test1('aa.bb.cc'::ltree); + + +CREATE FUNCTION test1n(val ltree) RETURNS int +LANGUAGE plpython2u +AS $$ +plpy.info(repr(val)) +return len(val) +$$; + +SELECT test1n('aa.bb.cc'::ltree); + + +CREATE FUNCTION test2() RETURNS ltree +LANGUAGE plpythonu +AS $$ +return ['foo', 'bar', 'baz'] +$$; + +-- plpython to ltree is not yet implemented, so this will fail, +-- because it will try to parse the Python list as an ltree input +-- string. +SELECT test2(); diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 9af4697..7a4f7b3 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -264,6 +264,11 @@ System Catalogs + pg_transform + transforms (data type to procedural language conversions) + + + pg_trigger triggers @@ -5732,6 +5737,74 @@ <structname>pg_tablespace</> Columns + + <structname>pg_transform</structname> + + + pg_transform + + + + The catalog pg_transform stores information about + transforms, which are a mechanism to adapt data types to procedural + languages. See for more information. + + + + <structname>pg_transform</> Columns + + + + + Name + Type + References + Description + + + + + + trftype + oid + pg_type.oid + OID of the data type this transform is for + + + + trflang + oid + pg_language.oid + OID of the language this transform is for + + + + trffromsql + regproc + pg_proc.oid + + 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. + + + + + trftosql + regproc + pg_proc.oid + + 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. + + + + +
+
+ + <structname>pg_trigger</structname> diff --git a/doc/src/sgml/hstore.sgml b/doc/src/sgml/hstore.sgml index 3810776..6eacfcd 100644 --- a/doc/src/sgml/hstore.sgml +++ b/doc/src/sgml/hstore.sgml @@ -597,6 +597,24 @@ Compatibility + Transforms + + + Additional extensions are available that implement transforms for + the hstore type for the languages PL/Perl and PL/Python. The + extensions for PL/Perl are called hstore_plperl + and hstore_plperlu, for trusted and untrusted PL/Perl. + If you install these extensions, hstore values are + automatically mapped to Perl hashes. The extensions for PL/Python are + called hstore_plpythonu, hstore_plpython2u, + and hstore_plpython3u + (see for the PL/Python naming + convention). If you install them, hstore values are + automatically mapped to Python dictionaries. + + + + Authors diff --git a/doc/src/sgml/ltree.sgml b/doc/src/sgml/ltree.sgml index cd8a061..051d63b 100644 --- a/doc/src/sgml/ltree.sgml +++ b/doc/src/sgml/ltree.sgml @@ -665,6 +665,21 @@ Example + Transforms + + + Additional extensions are available that implement transforms for + the ltree type for PL/Python. The extensions are + called ltree_plpythonu, ltree_plpython2u, + and ltree_plpython3u + (see for the PL/Python naming + convention). If you install them, ltree values are + automatically mapped to Python lists. (The reverse is currently not + supported, however.) + + + + Authors diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 5846974..9ee8517 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -76,6 +76,7 @@ + @@ -116,6 +117,7 @@ + diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml index a14fcb4..90b8f93 100644 --- a/doc/src/sgml/ref/alter_extension.sgml +++ b/doc/src/sgml/ref/alter_extension.sgml @@ -52,6 +52,7 @@ TEXT SEARCH DICTIONARY object_name | TEXT SEARCH PARSER object_name | TEXT SEARCH TEMPLATE object_name | + TRANSFORM FOR type_name LANGUAGE lang_name | TYPE object_name | VIEW object_name @@ -264,6 +265,26 @@ Parameters + + + type_name + + + + The name of the data type of the transform. + + + + + + lang_name + + + + The name of the language of the transform. + + + diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index e550500..7d4dcd0 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -54,6 +54,7 @@ TEXT SEARCH DICTIONARY object_name | TEXT SEARCH PARSER object_name | TEXT SEARCH TEMPLATE object_name | + TRANSFORM FOR type_name LANGUAGE lang_name | TRIGGER trigger_name ON table_name | TYPE object_name | VIEW object_name @@ -217,6 +218,26 @@ Parameters + + type_name + + + + The name of the data type of the transform. + + + + + + lang_name + + + + The name of the language of the transform. + + + + text @@ -296,6 +317,7 @@ Examples COMMENT ON TEXT SEARCH DICTIONARY swedish IS 'Snowball stemmer for swedish language'; COMMENT ON TEXT SEARCH PARSER my_parser IS 'Splits text into words'; COMMENT ON TEXT SEARCH TEMPLATE snowball IS 'Snowball stemmer'; +COMMENT ON TRANSFORM FOR hstore LANGUAGE plpythonu IS 'Transform between hstore and Python dict'; COMMENT ON TRIGGER my_trigger ON my_table IS 'Used for RI'; COMMENT ON TYPE complex IS 'Complex number data type'; COMMENT ON VIEW my_view IS 'View of departmental costs'; diff --git a/doc/src/sgml/ref/create_transform.sgml b/doc/src/sgml/ref/create_transform.sgml new file mode 100644 index 0000000..5fdbfda --- /dev/null +++ b/doc/src/sgml/ref/create_transform.sgml @@ -0,0 +1,190 @@ + + + + + CREATE TRANSFORM + 7 + SQL - Language Statements + + + + CREATE TRANSFORM + define a new transform + + + + CREATE TRANSFORM + + + + +CREATE [ OR REPLACE ] TRANSFORM FOR type_name LANGUAGE lang_name ( + FROM SQL WITH FUNCTION from_sql_function_name (argument_type [, ...]), + TO SQL WITH FUNCTION to_sql_function_name (argument_type [, ...]) +); + + + + + Description + + + CREATE TRANSFORM defines a new transform. + CREATE OR REPLACE TRANSFORM will either create a new + transform, or replace an existing definition. + + + + A transform specifies how to adapt a data type to a procedural language. + For example, when writing a function in PL/Python using the hstore type, + PL/Python has no prior knowledge how to present hstore values in the Python + environment. Language implementations usually default to using the text + representation, but that is inconvenient when, for example, an associative + array or a list would be more appropriate. A transform specifies two + functions: one from SQL function that converts the type from + the SQL environment to the language (In other words, this function will be + invoked on the arguments of a function written in the language.), and one + to SQL function that converts the type from the language to + the SQL environment (In other words, this function will be invoked on the + return value of a function written in the language.). It is not necessary + to provide both of these functions. If one is not specified, the + language-specific default behavior will be used if necessary. (To prevent a + transformation in a certain direction from happening at all, you could also + write a transform function that always errors out.) + + + + To be able to create a transform, you must own and + have USAGE privilege on the type, have + USAGE privilege on the language, and own and + have EXECUTE privilege on the from-SQL and to-SQL + functions, if specified. + + + + + Parameters + + + + type_name + + + + The name of the data type of the transform. + + + + + + lang_name + + + + The name of the language of the transform. + + + + + + from_sql_function_name(argument_type [, ...]) + + + + The name of the function for converting the type from the SQL + environment to the language. It must take one argument of + type internal and return type internal. 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 internal without at + least one argument of type internal. The actual return + value will be something specific to the language implementation. + + + + + + to_sql_function_name(argument_type [, ...]) + + + + The name of the function for converting the type from the language to + the SQL environment. It must take one argument of type + internal and return the type that is the type for the + transform. The actual argument value will be something specific to the + language implementation. + + + + + + + + Notes + + + Use to remove transforms. + + + + + Examples + + + To create a transform for type hstore and language + plpythonu, first set up the type and the language: + +CREATE TYPE hstore ...; + +CREATE LANGUAGE plpythonu ...; + + Then create the necessary functions: + +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 ...; + + And finally create the transform to connect them all together: + +CREATE TRANSFORM FOR hstore LANGUAGE plpythonu ( + FROM SQL WITH FUNCTION hstore_to_plpython(internal), + TO SQL WITH FUNCTION plpython_to_hstore(internal) +); + + In practice, these commands would be wrapped up in extensions. + + + + The contrib section contains a number of extensions + that provide transforms, which can serve as real-world examples. + + + + + Compatibility + + + This form of CREATE TRANSFORM is a + PostgreSQL extension. There is a CREATE + TRANSFORM command in the SQL standard, but it + is for adapting data types to client languages. That usage is not supported + by PostgreSQL. + + + + + See Also + + + , + , + , + + + + + diff --git a/doc/src/sgml/ref/drop_transform.sgml b/doc/src/sgml/ref/drop_transform.sgml new file mode 100644 index 0000000..c9b558e --- /dev/null +++ b/doc/src/sgml/ref/drop_transform.sgml @@ -0,0 +1,123 @@ + + + + + DROP TRANSFORM + 7 + SQL - Language Statements + + + + DROP TRANSFORM + remove a transform + + + + DROP TRANSFORM + + + + +DROP TRANSFORM [ IF EXISTS ] FOR type_name LANGUAGE lang_name + + + + + Description + + + DROP TRANSFORM removes a previously defined transform. + + + + 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. + + + + + Parameters + + + + + IF EXISTS + + + Do not throw an error if the transform does not exist. A notice is issued + in this case. + + + + + + type_name + + + + The name of the data type of the transform. + + + + + + lang_name + + + + The name of the language of the transform. + + + + + + CASCADE + + + Automatically drop objects that depend on the transform. + + + + + + RESTRICT + + + Refuse to drop the transform if any objects depend on it. This is the + default. + + + + + + + + Examples + + + To drop the transform for type hstore and language + plpythonu: + +DROP TRANSFORM FOR hstore LANGUAGE plpythonu; + + + + + Compatibility + + + This form of DROP TRANSFORM is a + PostgreSQL extension. See for details. + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 14e217a..0aac876 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -108,6 +108,7 @@ SQL Commands &createTSDictionary; &createTSParser; &createTSTemplate; + &createTransform; &createTrigger; &createType; &createUser; @@ -148,6 +149,7 @@ SQL Commands &dropTSDictionary; &dropTSParser; &dropTSTemplate; + &dropTransform; &dropTrigger; &dropType; &dropUser; diff --git a/src/Makefile.shlib b/src/Makefile.shlib index 2a0c7a9..3ab9b82 100644 --- a/src/Makefile.shlib +++ b/src/Makefile.shlib @@ -133,7 +133,7 @@ ifeq ($(PORTNAME), darwin) else # loadable module DLSUFFIX = .so - LINK.shared = $(COMPILER) -bundle -multiply_defined suppress + LINK.shared = $(COMPILER) -bundle -multiply_defined suppress -Wl,-undefined,dynamic_lookup endif BUILD.exports = $(AWK) '/^[^\#]/ {printf "_%s\n",$$1}' $< >$@ exports_file = $(SHLIB_EXPORTS:%.txt=%.list) diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index c4d3f3c..f02ea60 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -41,6 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h \ pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \ + pg_transform.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index fe17c96..257edc3 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -46,6 +46,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_tablespace.h" +#include "catalog/pg_transform.h" #include "catalog/pg_trigger.h" #include "catalog/pg_ts_config.h" #include "catalog/pg_ts_dict.h" @@ -1249,6 +1250,10 @@ static bool stack_address_present_add_flags(const ObjectAddress *object, RemoveEventTriggerById(object->objectId); break; + case OCLASS_TRANSFORM: + DropTransformById(object->objectId); + break; + default: elog(ERROR, "unrecognized object class: %u", object->classId); @@ -2308,6 +2313,9 @@ static bool stack_address_present_add_flags(const ObjectAddress *object, case EventTriggerRelationId: return OCLASS_EVENT_TRIGGER; + + case TransformRelationId: + return OCLASS_TRANSFORM; } /* shouldn't get here */ diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 4d22f3a..2946f3e 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -44,6 +44,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_tablespace.h" +#include "catalog/pg_transform.h" #include "catalog/pg_trigger.h" #include "catalog/pg_ts_config.h" #include "catalog/pg_ts_dict.h" @@ -332,6 +333,12 @@ true }, { + TransformRelationId, + TransformOidIndexId, + TRFOID, + InvalidAttrNumber + }, + { TriggerRelationId, TriggerOidIndexId, -1, @@ -589,6 +596,19 @@ static void getRelationTypeDescription(StringInfo buffer, Oid relid, address.objectSubId = 0; } break; + case OBJECT_TRANSFORM: + { + TypeName *typename = (TypeName *) linitial(objname); + char *langname = (char *) linitial(objargs); + Oid typeid = typenameTypeId(NULL, typename); + Oid langid = get_language_oid(langname, false); + + address.classId = TransformRelationId; + address.objectId = + get_transform_oid(typeid, langid, missing_ok); + address.objectSubId = 0; + } + break; case OBJECT_TSPARSER: address.classId = TSParserRelationId; address.objectId = get_ts_parser_oid(objname, missing_ok); @@ -1234,6 +1254,15 @@ static void getRelationTypeDescription(StringInfo buffer, Oid relid, format_type_be(targettypeid)))); } break; + case OBJECT_TRANSFORM: + { + TypeName *typename = (TypeName *) linitial(objname); + Oid typeid = typenameTypeId(NULL, typename); + + if (!pg_type_ownercheck(typeid, roleid)) + aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid); + } + break; case OBJECT_TABLESPACE: if (!pg_tablespace_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, @@ -1667,19 +1696,10 @@ static void getRelationTypeDescription(StringInfo buffer, Oid relid, } case OCLASS_LANGUAGE: - { - HeapTuple langTup; + appendStringInfo(&buffer, _("language %s"), + get_language_name(object->objectId, false)); + break; - langTup = SearchSysCache1(LANGOID, - ObjectIdGetDatum(object->objectId)); - if (!HeapTupleIsValid(langTup)) - elog(ERROR, "cache lookup failed for language %u", - object->objectId); - appendStringInfo(&buffer, _("language %s"), - NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname)); - ReleaseSysCache(langTup); - break; - } case OCLASS_LARGEOBJECT: appendStringInfo(&buffer, _("large object %u"), object->objectId); @@ -1867,6 +1887,27 @@ static void getRelationTypeDescription(StringInfo buffer, Oid relid, break; } + case OCLASS_TRANSFORM: + { + HeapTuple trfTup; + Form_pg_transform trfForm; + + trfTup = SearchSysCache1(TRFOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(trfTup)) + elog(ERROR, "could not find tuple for transform %u", + object->objectId); + + trfForm = (Form_pg_transform) GETSTRUCT(trfTup); + + appendStringInfo(&buffer, _("transform for %s language %s"), + format_type_be(trfForm->trftype), + get_language_name(trfForm->trflang, false)); + + ReleaseSysCache(trfTup); + break; + } + case OCLASS_TRIGGER: { Relation trigDesc; diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 05a550e..f71ebb5 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -23,7 +23,9 @@ #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_proc_fn.h" +#include "catalog/pg_transform.h" #include "catalog/pg_type.h" +#include "commands/defrem.h" #include "executor/functions.h" #include "funcapi.h" #include "mb/pg_wchar.h" @@ -116,6 +118,7 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal, ObjectAddress myself, referenced; int i; + Oid trfid; /* * sanity checks @@ -624,6 +627,15 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal, referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + /* dependency on transform used by return type, if any */ + if ((trfid = get_transform_oid(returnType, languageObjectId, true))) + { + referenced.classId = TransformRelationId; + referenced.objectId = trfid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + /* dependency on parameter types */ for (i = 0; i < allParamCount; i++) { @@ -631,6 +643,15 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal, referenced.objectId = allParams[i]; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on transform used by parameter type, if any */ + if ((trfid = get_transform_oid(allParams[i], languageObjectId, true))) + { + referenced.classId = TransformRelationId; + referenced.objectId = trfid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } } /* dependency on parameter default expressions */ diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c index b32ad3a..c63e07b 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -200,6 +200,12 @@ static void does_not_exist_skipping(ObjectType objtype, args = format_type_be(typenameTypeId(NULL, (TypeName *) linitial(objargs))); break; + case OBJECT_TRANSFORM: + msg = gettext_noop("transform for type %s language %s does not exist, skipping"); + name = format_type_be(typenameTypeId(NULL, + (TypeName *) linitial(objname))); + args = (char *) linitial(objargs); + break; case OBJECT_TRIGGER: msg = gettext_noop("trigger \"%s\" for table \"%s\" does not exist, skipping"); name = strVal(llast(objname)); diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 328e2a8..7a53d56 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -92,6 +92,7 @@ {"SERVER", true}, {"TABLE", true}, {"TABLESPACE", false}, + {"TRANSFORM", true}, {"TRIGGER", true}, {"TEXT SEARCH CONFIGURATION", true}, {"TEXT SEARCH DICTIONARY", true}, @@ -937,6 +938,7 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname, case OBJECT_SCHEMA: case OBJECT_SEQUENCE: case OBJECT_TABLE: + case OBJECT_TRANSFORM: case OBJECT_TRIGGER: case OBJECT_TSCONFIGURATION: case OBJECT_TSDICTIONARY: @@ -983,6 +985,7 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname, case OCLASS_REWRITE: case OCLASS_TRIGGER: case OCLASS_SCHEMA: + case OCLASS_TRANSFORM: case OCLASS_TSPARSER: case OCLASS_TSDICT: case OCLASS_TSTEMPLATE: diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index ca754b4..b6744a5 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -45,6 +45,7 @@ #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_proc_fn.h" +#include "catalog/pg_transform.h" #include "catalog/pg_type.h" #include "catalog/pg_type_fn.h" #include "commands/alter.h" @@ -1642,6 +1643,293 @@ 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 typeid, Oid langid, bool missing_ok) +{ + Oid oid; + + oid = GetSysCacheOid2(TRFTYPELANG, + ObjectIdGetDatum(typeid), + ObjectIdGetDatum(langid)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("transform for type %s language \"%s\" does not exist", + format_type_be(typeid), + get_language_name(langid, false)))); + return oid; +} + + +void +DropTransformById(Oid transformOid) +{ + Relation relation; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tuple; + + relation = heap_open(TransformRelationId, RowExclusiveLock); + + ScanKeyInit(&scankey, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(transformOid)); + scan = systable_beginscan(relation, TransformOidIndexId, true, + NULL, 1, &scankey); + + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for transform %u", transformOid); + simple_heap_delete(relation, &tuple->t_self); + + systable_endscan(scan); + heap_close(relation, RowExclusiveLock); +} + + /* * Subroutine for ALTER FUNCTION/AGGREGATE SET SCHEMA/RENAME * diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 65f3b98..02c5eb2 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3499,6 +3499,20 @@ return newnode; } +static CreateTransformStmt * +_copyCreateTransformStmt(const CreateTransformStmt *from) +{ + CreateTransformStmt *newnode = makeNode(CreateTransformStmt); + + COPY_SCALAR_FIELD(replace); + COPY_NODE_FIELD(type_name); + COPY_STRING_FIELD(lang); + COPY_NODE_FIELD(fromsql); + COPY_NODE_FIELD(tosql); + + return newnode; +} + static CreateTrigStmt * _copyCreateTrigStmt(const CreateTrigStmt *from) { @@ -4400,6 +4414,9 @@ case T_CreateForeignTableStmt: retval = _copyCreateForeignTableStmt(from); break; + case T_CreateTransformStmt: + retval = _copyCreateTransformStmt(from); + break; case T_CreateTrigStmt: retval = _copyCreateTrigStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 4c9b05e..d8dd7ec 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1731,6 +1731,18 @@ } static bool +_equalCreateTransformStmt(const CreateTransformStmt *a, const CreateTransformStmt *b) +{ + COMPARE_SCALAR_FIELD(replace); + COMPARE_NODE_FIELD(type_name); + COMPARE_STRING_FIELD(lang); + COMPARE_NODE_FIELD(fromsql); + COMPARE_NODE_FIELD(tosql); + + return true; +} + +static bool _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b) { COMPARE_STRING_FIELD(trigname); @@ -2870,6 +2882,9 @@ case T_CreateForeignTableStmt: retval = _equalCreateForeignTableStmt(a, b); break; + case T_CreateTransformStmt: + retval = _equalCreateTransformStmt(a, b); + break; case T_CreateTrigStmt: retval = _equalCreateTrigStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a9812af..5629a4f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -227,12 +227,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt - CreateAssertStmt CreateTrigStmt CreateEventTrigStmt + CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt - DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt + DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropTransformStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt @@ -345,6 +345,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); opt_enum_val_list enum_val_list table_func_column_list create_generic_options alter_generic_options relation_expr_list dostmt_opt_list + transform_element_list %type opt_fdw_options fdw_options %type fdw_option @@ -582,12 +583,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE - SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START + SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SYMMETRIC SYSID SYSTEM_P TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP - TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P + TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P TRUNCATE TRUSTED TYPE_P TYPES_P UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED @@ -756,6 +757,7 @@ stmt : | CreateSeqStmt | CreateStmt | CreateTableSpaceStmt + | CreateTransformStmt | CreateTrigStmt | CreateEventTrigStmt | CreateRoleStmt @@ -780,6 +782,7 @@ stmt : | DropRuleStmt | DropStmt | DropTableSpaceStmt + | DropTransformStmt | DropTrigStmt | DropRoleStmt | DropUserStmt @@ -3866,6 +3869,16 @@ AlterExtensionContentsStmt: n->objname = list_make1(makeString($6)); $$ = (Node *)n; } + | ALTER EXTENSION name add_drop TRANSFORM FOR Typename LANGUAGE name + { + AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt); + n->extname = $3; + n->action = $4; + n->objtype = OBJECT_TRANSFORM; + n->objname = list_make1($7); + n->objargs = list_make1($9); + $$ = (Node *)n; + } | ALTER EXTENSION name add_drop TYPE_P any_name { AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt); @@ -5289,6 +5302,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); @@ -6781,6 +6803,56 @@ opt_if_exists: IF_P EXISTS { $$ = TRUE; } /***************************************************************************** * + * CREATE TRANSFORM / DROP TRANSFORM + * + *****************************************************************************/ + +CreateTransformStmt: CREATE opt_or_replace TRANSFORM FOR Typename LANGUAGE name '(' transform_element_list ')' + { + CreateTransformStmt *n = makeNode(CreateTransformStmt); + n->replace = $2; + n->type_name = $5; + n->lang = $7; + n->fromsql = linitial($9); + n->tosql = lsecond($9); + $$ = (Node *)n; + } + ; + +transform_element_list: FROM SQL_P WITH FUNCTION function_with_argtypes ',' TO SQL_P WITH FUNCTION function_with_argtypes + { + $$ = list_make2($5, $11); + } + | TO SQL_P WITH FUNCTION function_with_argtypes ',' FROM SQL_P WITH FUNCTION function_with_argtypes + { + $$ = list_make2($11, $5); + } + | FROM SQL_P WITH FUNCTION function_with_argtypes + { + $$ = list_make2($5, NULL); + } + | TO SQL_P WITH FUNCTION function_with_argtypes + { + $$ = list_make2(NULL, $5); + } + ; + + +DropTransformStmt: DROP TRANSFORM opt_if_exists FOR Typename LANGUAGE name opt_drop_behavior + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_TRANSFORM; + n->objects = list_make1(list_make1($5)); + n->arguments = list_make1(list_make1($7)); + n->behavior = $8; + n->missing_ok = $3; + $$ = (Node *)n; + } + ; + + +/***************************************************************************** + * * QUERY: * * REINDEX type [FORCE] @@ -12674,6 +12746,7 @@ unreserved_keyword: | SHOW | SIMPLE | SNAPSHOT + | SQL_P | STABLE | STANDALONE_P | START @@ -12693,6 +12766,7 @@ unreserved_keyword: | TEMPORARY | TEXT_P | TRANSACTION + | TRANSFORM | TRIGGER | TRUNCATE | TRUSTED diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index b1023c4..855d64a 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -214,6 +214,7 @@ static void ProcessUtilitySlow(Node *parsetree, case T_CreateTableAsStmt: case T_RefreshMatViewStmt: case T_CreateTableSpaceStmt: + case T_CreateTransformStmt: case T_CreateTrigStmt: case T_CompositeTypeStmt: case T_CreateEnumStmt: @@ -1291,6 +1292,10 @@ static void ProcessUtilitySlow(Node *parsetree, DefineOpFamily((CreateOpFamilyStmt *) parsetree); break; + case T_CreateTransformStmt: + CreateTransform((CreateTransformStmt *) parsetree); + break; + case T_AlterOpFamilyStmt: AlterOpFamily((AlterOpFamilyStmt *) parsetree); break; @@ -1947,6 +1952,9 @@ static void ProcessUtilitySlow(Node *parsetree, case OBJECT_OPFAMILY: tag = "DROP OPERATOR FAMILY"; break; + case OBJECT_TRANSFORM: + tag = "DROP TRANSFORM"; + break; default: tag = "???"; } @@ -2195,6 +2203,10 @@ static void ProcessUtilitySlow(Node *parsetree, } break; + case T_CreateTransformStmt: + tag = "CREATE TRANSFORM"; + break; + case T_CreateTrigStmt: tag = "CREATE TRIGGER"; break; @@ -2810,6 +2822,10 @@ static void ProcessUtilitySlow(Node *parsetree, lev = LOGSTMT_DDL; break; + case T_CreateTransformStmt: + lev = LOGSTMT_DDL; + break; + case T_AlterOpFamilyStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 9a1d12e..6ea6808 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -1869,9 +1869,7 @@ static char *generate_function_name(Oid funcid, int nargs, StringInfoData buf; StringInfoData dq; HeapTuple proctup; - HeapTuple langtup; Form_pg_proc proc; - Form_pg_language lang; Datum tmp; bool isnull; const char *prosrc; @@ -1894,12 +1892,6 @@ static char *generate_function_name(Oid funcid, int nargs, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is an aggregate function", name))); - /* Need its pg_language tuple for the language name */ - langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang)); - if (!HeapTupleIsValid(langtup)) - elog(ERROR, "cache lookup failed for language %u", proc->prolang); - lang = (Form_pg_language) GETSTRUCT(langtup); - /* * We always qualify the function name, to ensure the right function gets * replaced. @@ -1911,7 +1903,7 @@ static char *generate_function_name(Oid funcid, int nargs, appendStringInfoString(&buf, ")\n RETURNS "); print_function_rettype(&buf, proctup); appendStringInfo(&buf, "\n LANGUAGE %s\n", - quote_identifier(NameStr(lang->lanname))); + quote_identifier(get_language_name(proc->prolang, false))); /* Emit some miscellaneous options on one line */ oldlen = buf.len; @@ -2031,7 +2023,6 @@ static char *generate_function_name(Oid funcid, int nargs, appendStringInfoString(&buf, "\n"); - ReleaseSysCache(langtup); ReleaseSysCache(proctup); PG_RETURN_TEXT_P(string_to_text(buf.data)); diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 5865962..48e14ee 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -23,12 +23,14 @@ #include "catalog/pg_amproc.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_language.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_range.h" #include "catalog/pg_statistic.h" +#include "catalog/pg_transform.h" #include "catalog/pg_type.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -1032,6 +1034,30 @@ return NULL; } +/* ---------- LANGUAGE CACHE ---------- */ + +char * +get_language_name(Oid langoid, bool missing_ok) +{ + HeapTuple tp; + + tp = SearchSysCache1(LANGOID, ObjectIdGetDatum(langoid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_language lantup = (Form_pg_language) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(lantup->lanname)); + ReleaseSysCache(tp); + return result; + } + + if (!missing_ok) + elog(ERROR, "cache lookup failed for language %u", + langoid); + return NULL; +} + /* ---------- OPCLASS CACHE ---------- */ /* @@ -1779,6 +1805,45 @@ } +/* ---------- TRANSFORM CACHE ---------- */ + +Oid +get_transform_fromsql(Oid typid, Oid langid) +{ + HeapTuple tup; + + tup = SearchSysCache2(TRFTYPELANG, typid, langid); + if (HeapTupleIsValid(tup)) + { + Oid funcid; + + funcid = ((Form_pg_transform) GETSTRUCT(tup))->trffromsql; + ReleaseSysCache(tup); + return funcid; + } + else + return InvalidOid; +} + +Oid +get_transform_tosql(Oid typid, Oid langid) +{ + HeapTuple tup; + + tup = SearchSysCache2(TRFTYPELANG, typid, langid); + if (HeapTupleIsValid(tup)) + { + Oid funcid; + + funcid = ((Form_pg_transform) GETSTRUCT(tup))->trftosql; + ReleaseSysCache(tup); + return funcid; + } + else + return InvalidOid; +} + + /* ---------- TYPE CACHE ---------- */ /* diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index e9bdfea..45c2ef9 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -56,6 +56,7 @@ #include "catalog/pg_shseclabel.h" #include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" +#include "catalog/pg_transform.h" #include "catalog/pg_ts_config.h" #include "catalog/pg_ts_config_map.h" #include "catalog/pg_ts_dict.h" @@ -653,6 +654,28 @@ struct cachedesc }, 4 }, + {TransformRelationId, /* TRFOID */ + TransformOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0, + }, + 16 + }, + {TransformRelationId, /* TRFTYPELANG */ + TransformTypeLangIndexId, + 2, + { + Anum_pg_transform_trftype, + Anum_pg_transform_trflang, + 0, + 0, + }, + 16 + }, {TSConfigMapRelationId, /* TSCONFIGMAP */ TSConfigMapIndexId, 3, diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 247ad92..da4dd57 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -89,6 +89,7 @@ static void findParentsByOid(TableInfo *self, int numRules; int numProcLangs; int numCasts; + int numTransforms; int numOpclasses; int numOpfamilies; int numConversions; @@ -199,6 +200,10 @@ static void findParentsByOid(TableInfo *self, getCasts(fout, &numCasts); if (g_verbose) + write_msg(NULL, "reading transforms\n"); + getTransforms(fout, &numTransforms); + + if (g_verbose) write_msg(NULL, "reading table inheritance information\n"); inhinfo = getInherits(fout, &numInherits); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 57320cc..a53c1f3 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -182,6 +182,7 @@ static int findSecLabels(Archive *fout, Oid classoid, Oid objoid, static void dumpProcLang(Archive *fout, ProcLangInfo *plang); static void dumpFunc(Archive *fout, FuncInfo *finfo); static void dumpCast(Archive *fout, CastInfo *cast); +static void dumpTransform(Archive *fout, TransformInfo *transform); static void dumpOpr(Archive *fout, OprInfo *oprinfo); static void dumpOpclass(Archive *fout, OpclassInfo *opcinfo); static void dumpOpfamily(Archive *fout, OpfamilyInfo *opfinfo); @@ -6108,6 +6109,110 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, return castinfo; } +static char * +get_language_name(Archive *fout, Oid langid) +{ + PQExpBuffer query; + PGresult *res; + char *lanname; + + query = createPQExpBuffer(); + appendPQExpBuffer(query, "SELECT lanname FROM pg_language WHERE oid = %u", langid); + res = ExecuteSqlQueryForSingleRow(fout, query->data); + lanname = pg_strdup(fmtId(PQgetvalue(res, 0, 0))); + destroyPQExpBuffer(query); + PQclear(res); + + return lanname; +} + +/* + * getTransforms + * get basic information about every transform in the system + * + * numTransforms is set to the number of transforms read in + */ +TransformInfo * +getTransforms(Archive *fout, int *numTransforms) +{ + PGresult *res; + int ntups; + int i; + PQExpBuffer query = createPQExpBuffer(); + TransformInfo *transforminfo; + int i_tableoid; + int i_oid; + int i_trftype; + int i_trflang; + int i_trffromsql; + int i_trftosql; + + /* Transforms didn't exist pre-9.4 */ + if (fout->remoteVersion < 90400) + { + *numTransforms = 0; + return NULL; + } + + /* Make sure we are in proper schema */ + selectSourceSchema(fout, "pg_catalog"); + + appendPQExpBuffer(query, "SELECT tableoid, oid, " + "trftype, trflang, trffromsql::oid, trftosql::oid " + "FROM pg_transform " + "ORDER BY 3,4"); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + *numTransforms = ntups; + + transforminfo = (TransformInfo *) pg_malloc(ntups * sizeof(TransformInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_trftype = PQfnumber(res, "trftype"); + i_trflang = PQfnumber(res, "trflang"); + i_trffromsql = PQfnumber(res, "trffromsql"); + i_trftosql = PQfnumber(res, "trftosql"); + + for (i = 0; i < ntups; i++) + { + PQExpBufferData namebuf; + TypeInfo *typeInfo; + char *lanname; + + transforminfo[i].dobj.objType = DO_TRANSFORM; + transforminfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + transforminfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&transforminfo[i].dobj); + transforminfo[i].trftype = atooid(PQgetvalue(res, i, i_trftype)); + transforminfo[i].trflang = atooid(PQgetvalue(res, i, i_trflang)); + transforminfo[i].trffromsql = atooid(PQgetvalue(res, i, i_trffromsql)); + transforminfo[i].trftosql = atooid(PQgetvalue(res, i, i_trftosql)); + + /* + * Try to name transform as concatenation of type and language name. + * This is only used for purposes of sorting. If we fail to find + * either, the name will be an empty string. + */ + initPQExpBuffer(&namebuf); + typeInfo = findTypeByOid(transforminfo[i].trftype); + lanname = get_language_name(fout, transforminfo[i].trflang); + if (typeInfo && lanname) + appendPQExpBuffer(&namebuf, "%s %s", + typeInfo->dobj.name, lanname); + transforminfo[i].dobj.name = namebuf.data; + } + + PQclear(res); + + destroyPQExpBuffer(query); + + return transforminfo; +} + /* * getTableAttrs - * for each interesting table, read info about its attributes @@ -7718,6 +7823,9 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, case DO_CAST: dumpCast(fout, (CastInfo *) dobj); break; + case DO_TRANSFORM: + dumpTransform(fout, (TransformInfo *) dobj); + break; case DO_TABLE_DATA: if (((TableDataInfo *) dobj)->tdtable->relkind == RELKIND_SEQUENCE) dumpSequenceData(fout, (TableDataInfo *) dobj); @@ -10114,6 +10222,127 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, } /* + * Dump a transform + */ +static void +dumpTransform(Archive *fout, TransformInfo *transform) +{ + PQExpBuffer defqry; + PQExpBuffer delqry; + PQExpBuffer labelq; + FuncInfo *fromsqlFuncInfo = NULL; + FuncInfo *tosqlFuncInfo = NULL; + char *lanname; + + /* Skip if not to be dumped */ + if (!transform->dobj.dump || dataOnly) + return; + + /* Cannot dump if we don't have the transform functions' info */ + if (OidIsValid(transform->trffromsql)) + { + fromsqlFuncInfo = findFuncByOid(transform->trffromsql); + if (fromsqlFuncInfo == NULL) + return; + } + if (OidIsValid(transform->trftosql)) + { + tosqlFuncInfo = findFuncByOid(transform->trftosql); + if (tosqlFuncInfo == NULL) + return; + } + + /* Make sure we are in proper schema (needed for getFormattedTypeName) */ + selectSourceSchema(fout, "pg_catalog"); + + defqry = createPQExpBuffer(); + delqry = createPQExpBuffer(); + labelq = createPQExpBuffer(); + + lanname = get_language_name(fout, transform->trflang); + + appendPQExpBuffer(delqry, "DROP TRANSFORM FOR %s LANGUAGE %s;\n", + getFormattedTypeName(fout, transform->trftype, zeroAsNone), + lanname); + + appendPQExpBuffer(defqry, "CREATE TRANSFORM FOR %s LANGUAGE %s (", + getFormattedTypeName(fout, transform->trftype, zeroAsNone), + lanname); + + if (!transform->trffromsql && !transform->trftosql) + write_msg(NULL, "WARNING: bogus transform definition, at least one of trffromsql and trftosql should be nonzero\n"); + + if (transform->trffromsql) + { + if (fromsqlFuncInfo) + { + char *fsig = format_function_signature(fout, fromsqlFuncInfo, true); + + /* + * Always qualify the function name, in case it is not in + * pg_catalog schema (format_function_signature won't qualify + * it). + */ + appendPQExpBuffer(defqry, "FROM SQL WITH FUNCTION %s.%s", + fmtId(fromsqlFuncInfo->dobj.namespace->dobj.name), fsig); + free(fsig); + } + else + write_msg(NULL, "WARNING: bogus value in pg_transform.trffromsql field\n"); + } + + if (transform->trftosql) + { + if (transform->trffromsql) + appendPQExpBuffer(defqry, ", "); + + if (tosqlFuncInfo) + { + char *fsig = format_function_signature(fout, tosqlFuncInfo, true); + + /* + * Always qualify the function name, in case it is not in + * pg_catalog schema (format_function_signature won't qualify + * it). + */ + appendPQExpBuffer(defqry, "TO SQL WITH FUNCTION %s.%s", + fmtId(tosqlFuncInfo->dobj.namespace->dobj.name), fsig); + free(fsig); + } + else + write_msg(NULL, "WARNING: bogus value in pg_transform.trftosql field\n"); + } + + appendPQExpBuffer(defqry, ");\n"); + + appendPQExpBuffer(labelq, "TRANSFORM FOR %s LANGUAGE %s", + getFormattedTypeName(fout, transform->trftype, zeroAsNone), + lanname); + + if (binary_upgrade) + binary_upgrade_extension_member(defqry, &transform->dobj, labelq->data); + + ArchiveEntry(fout, transform->dobj.catId, transform->dobj.dumpId, + labelq->data, + "pg_catalog", NULL, "", + false, "TRANSFORM", SECTION_PRE_DATA, + defqry->data, delqry->data, NULL, + transform->dobj.dependencies, transform->dobj.nDeps, + NULL, NULL); + + /* Dump Transform Comments */ + dumpComment(fout, labelq->data, + NULL, "", + transform->dobj.catId, 0, transform->dobj.dumpId); + + free(lanname); + destroyPQExpBuffer(defqry); + destroyPQExpBuffer(delqry); + destroyPQExpBuffer(labelq); +} + + +/* * dumpOpr * write out a single operator definition */ @@ -14958,6 +15187,7 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, case DO_TSCONFIG: case DO_FDW: case DO_FOREIGN_SERVER: + case DO_TRANSFORM: case DO_BLOB: /* Pre-data objects: must come before the pre-data boundary */ addObjectDependency(preDataBound, dobj->dumpId); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 2c5971c..fc1e46a 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -106,6 +106,7 @@ typedef enum DO_FDW, DO_FOREIGN_SERVER, DO_DEFAULT_ACL, + DO_TRANSFORM, DO_BLOB, DO_BLOB_DATA, DO_PRE_DATA_BOUNDARY, @@ -407,6 +408,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 { @@ -559,6 +569,7 @@ extern RuleInfo *getRules(Archive *fout, int *numRules); extern void getTriggers(Archive *fout, TableInfo tblinfo[], int numTables); extern ProcLangInfo *getProcLangs(Archive *fout, int *numProcLangs); extern CastInfo *getCasts(Archive *fout, int *numCasts); +extern TransformInfo *getTransforms(Archive *fout, int *numTransforms); extern void getTableAttrs(Archive *fout, TableInfo *tbinfo, int numTables); extern bool shouldPrintColumn(TableInfo *tbinfo, int colno); extern TSParserInfo *getTSParsers(Archive *fout, int *numTSParsers); diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 141e713..9d0a6d1 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -26,8 +26,8 @@ * by OID. (This is a relatively crude hack to provide semi-reasonable * behavior for old databases without full dependency info.) Note: collations, * extensions, text search, foreign-data, materialized view, event trigger, - * and default ACL objects can't really happen here, so the rather bogus - * priorities for them don't matter. + * transforms, and default ACL objects can't really happen here, so the rather + * bogus priorities for them don't matter. * * NOTE: object-type priorities must match the section assignments made in * pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY, @@ -65,6 +65,7 @@ 4, /* DO_FDW */ 4, /* DO_FOREIGN_SERVER */ 19, /* DO_DEFAULT_ACL */ + 4, /* DO_TRANSFORM */ 9, /* DO_BLOB */ 12, /* DO_BLOB_DATA */ 10, /* DO_PRE_DATA_BOUNDARY */ @@ -113,6 +114,7 @@ 16, /* DO_FDW */ 17, /* DO_FOREIGN_SERVER */ 31, /* DO_DEFAULT_ACL */ + 3, /* DO_TRANSFORM */ 21, /* DO_BLOB */ 24, /* DO_BLOB_DATA */ 22, /* DO_PRE_DATA_BOUNDARY */ @@ -1287,6 +1289,13 @@ static void describeDumpableObject(DumpableObject *obj, ((CastInfo *) obj)->casttarget, obj->dumpId, obj->catId.oid); return; + case DO_TRANSFORM: + snprintf(buf, bufsize, + "TRANSFORM %u lang %u (ID %d OID %u)", + ((TransformInfo *) obj)->trftype, + ((TransformInfo *) obj)->trflang, + obj->dumpId, obj->catId.oid); + return; case DO_TABLE_DATA: snprintf(buf, bufsize, "TABLE DATA %s (ID %d OID %u)", diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 3a18935..9f5f275 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201309051 +#define CATALOG_VERSION_NO 201309113 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 3aefbb5e..404fda4 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -147,6 +147,7 @@ typedef enum ObjectClass OCLASS_DEFACL, /* pg_default_acl */ OCLASS_EXTENSION, /* pg_extension */ OCLASS_EVENT_TRIGGER, /* pg_event_trigger */ + OCLASS_TRANSFORM, /* pg_transform */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 4860e98..a98b768 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -228,6 +228,11 @@ DECLARE_UNIQUE_INDEX(pg_tablespace_oid_index, 2697, on pg_tablespace using btree DECLARE_UNIQUE_INDEX(pg_tablespace_spcname_index, 2698, on pg_tablespace using btree(spcname name_ops)); #define TablespaceNameIndexId 2698 +DECLARE_UNIQUE_INDEX(pg_transform_oid_index, 3780, on pg_transform using btree(oid oid_ops)); +#define TransformOidIndexId 3780 +DECLARE_UNIQUE_INDEX(pg_transform_type_lang_index, 3781, on pg_transform using btree(trftype oid_ops, trflang oid_ops)); +#define TransformTypeLangIndexId 3781 + /* This following index is not used for a cache and is not unique */ DECLARE_INDEX(pg_trigger_tgconstraint_index, 2699, on pg_trigger using btree(tgconstraint oid_ops)); #define TriggerConstraintIndexId 2699 diff --git a/src/include/catalog/pg_transform.h b/src/include/catalog/pg_transform.h new file mode 100644 index 0000000..80b191e --- /dev/null +++ b/src/include/catalog/pg_transform.h @@ -0,0 +1,47 @@ +/*------------------------------------------------------------------------- + * + * pg_transform.h + * + * Copyright (c) 2012, PostgreSQL Global Development Group + * + * src/include/catalog/pg_transform.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_TRANSFORM_H +#define PG_TRANSFORM_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_transform definition. cpp turns this into + * typedef struct FormData_pg_transform + * ---------------- + */ +#define TransformRelationId 3779 + +CATALOG(pg_transform,3779) +{ + Oid trftype; + Oid trflang; + regproc trffromsql; + regproc trftosql; +} FormData_pg_transform; + +typedef FormData_pg_transform *Form_pg_transform; + +/* ---------------- + * compiler constants for pg_transform + * ---------------- + */ +#define Natts_pg_transform 4 +#define Anum_pg_transform_trftype 1 +#define Anum_pg_transform_trflang 2 +#define Anum_pg_transform_trffromsql 3 +#define Anum_pg_transform_trftosql 4 + +#endif /* PG_TRANSFORM_H */ diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 836c99e..91b2b72 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -50,10 +50,13 @@ extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType); extern Oid AlterFunction(AlterFunctionStmt *stmt); extern Oid CreateCast(CreateCastStmt *stmt); extern void DropCastById(Oid castOid); +extern Oid CreateTransform(CreateTransformStmt *stmt); +extern void DropTransformById(Oid transformOid); extern void IsThereFunctionInNamespace(const char *proname, int pronargs, oidvector *proargtypes, Oid nspOid); extern void ExecuteDoStmt(DoStmt *stmt); extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); +extern Oid get_transform_oid(Oid typeid, Oid langid, bool missing_ok); extern void interpret_function_parameter_list(List *parameters, Oid languageOid, bool is_aggregate, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 78368c6..67f7fed 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -362,6 +362,7 @@ typedef enum NodeTag T_CreateEventTrigStmt, T_AlterEventTrigStmt, T_RefreshMatViewStmt, + T_CreateTransformStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 51fef68..b421f5d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1184,6 +1184,7 @@ typedef enum ObjectType OBJECT_SEQUENCE, OBJECT_TABLE, OBJECT_TABLESPACE, + OBJECT_TRANSFORM, OBJECT_TRIGGER, OBJECT_TSCONFIGURATION, OBJECT_TSDICTIONARY, @@ -2618,6 +2619,20 @@ typedef struct CreateCastStmt } CreateCastStmt; /* ---------------------- + * CREATE TRANSFORM Statement + * ---------------------- + */ +typedef struct CreateTransformStmt +{ + NodeTag type; + bool replace; + TypeName *type_name; + char *lang; + FuncWithArgs *fromsql; + FuncWithArgs *tosql; +} CreateTransformStmt; + +/* ---------------------- * PREPARE Statement * ---------------------- */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 8bd34d6..e8bb988 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -347,6 +347,7 @@ PG_KEYWORD("simple", SIMPLE, UNRESERVED_KEYWORD) PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD) PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD) PG_KEYWORD("some", SOME, RESERVED_KEYWORD) +PG_KEYWORD("sql", SQL_P, UNRESERVED_KEYWORD) PG_KEYWORD("stable", STABLE, UNRESERVED_KEYWORD) PG_KEYWORD("standalone", STANDALONE_P, UNRESERVED_KEYWORD) PG_KEYWORD("start", START, UNRESERVED_KEYWORD) @@ -374,6 +375,7 @@ PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD) PG_KEYWORD("to", TO, RESERVED_KEYWORD) PG_KEYWORD("trailing", TRAILING, RESERVED_KEYWORD) PG_KEYWORD("transaction", TRANSACTION, UNRESERVED_KEYWORD) +PG_KEYWORD("transform", TRANSFORM, UNRESERVED_KEYWORD) PG_KEYWORD("treat", TREAT, COL_NAME_KEYWORD) PG_KEYWORD("trigger", TRIGGER, UNRESERVED_KEYWORD) PG_KEYWORD("trim", TRIM, COL_NAME_KEYWORD) diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 49f459a..85c5a6a 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -72,6 +72,7 @@ extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum, Oid *typid, int32 *typmod, Oid *collid); extern char *get_collation_name(Oid colloid); extern char *get_constraint_name(Oid conoid); +extern char *get_language_name(Oid langoid, bool missing_ok); extern Oid get_opclass_family(Oid opclass); extern Oid get_opclass_input_type(Oid opclass); extern RegProcedure get_opcode(Oid opno); @@ -102,6 +103,8 @@ extern Oid get_rel_namespace(Oid relid); extern Oid get_rel_type_id(Oid relid); extern char get_rel_relkind(Oid relid); extern Oid get_rel_tablespace(Oid relid); +extern Oid get_transform_fromsql(Oid typid, Oid langid); +extern Oid get_transform_tosql(Oid typid, Oid langid); extern bool get_typisdefined(Oid typid); extern int16 get_typlen(Oid typid); extern bool get_typbyval(Oid typid); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index e41b3d2..bad0fd9 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -80,6 +80,8 @@ enum SysCacheIdentifier RULERELNAME, STATRELATTINH, TABLESPACEOID, + TRFOID, + TRFTYPELANG, TSCONFIGMAP, TSCONFIGNAMENSP, TSCONFIGOID, diff --git a/src/interfaces/ecpg/preproc/ecpg.tokens b/src/interfaces/ecpg/preproc/ecpg.tokens index b55138a..68ba925 100644 --- a/src/interfaces/ecpg/preproc/ecpg.tokens +++ b/src/interfaces/ecpg/preproc/ecpg.tokens @@ -12,7 +12,7 @@ SQL_LONG SQL_NULLABLE SQL_OCTET_LENGTH SQL_OPEN SQL_OUTPUT SQL_REFERENCE SQL_RETURNED_LENGTH SQL_RETURNED_OCTET_LENGTH SQL_SCALE - SQL_SECTION SQL_SHORT SQL_SIGNED SQL_SQL SQL_SQLERROR + SQL_SECTION SQL_SHORT SQL_SIGNED SQL_SQLERROR SQL_SQLPRINT SQL_SQLWARNING SQL_START SQL_STOP SQL_STRUCT SQL_UNSIGNED SQL_VAR SQL_WHENEVER diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer index 58155ab..c91b8b0 100644 --- a/src/interfaces/ecpg/preproc/ecpg.trailer +++ b/src/interfaces/ecpg/preproc/ecpg.trailer @@ -1000,7 +1000,7 @@ ecpg_using: USING using_list { $$ = EMPTY; } | using_descriptor { $$ = $1; } ; -using_descriptor: USING SQL_SQL SQL_DESCRIPTOR quoted_ident_stringvar +using_descriptor: USING SQL_P SQL_DESCRIPTOR quoted_ident_stringvar { add_variable_to_head(&argsinsert, descriptor_variable($4,0), &no_indicator); $$ = EMPTY; @@ -1012,7 +1012,7 @@ using_descriptor: USING SQL_SQL SQL_DESCRIPTOR quoted_ident_stringvar } ; -into_descriptor: INTO SQL_SQL SQL_DESCRIPTOR quoted_ident_stringvar +into_descriptor: INTO SQL_P SQL_DESCRIPTOR quoted_ident_stringvar { add_variable_to_head(&argsresult, descriptor_variable($4,1), &no_indicator); $$ = EMPTY; @@ -1489,7 +1489,6 @@ ECPGKeywords_vanames: SQL_BREAK { $$ = mm_strdup("break"); } | SQL_RETURNED_OCTET_LENGTH { $$ = mm_strdup("returned_octet_length"); } | SQL_SCALE { $$ = mm_strdup("scale"); } | SQL_SECTION { $$ = mm_strdup("section"); } - | SQL_SQL { $$ = mm_strdup("sql"); } | SQL_SQLERROR { $$ = mm_strdup("sqlerror"); } | SQL_SQLPRINT { $$ = mm_strdup("sqlprint"); } | SQL_SQLWARNING { $$ = mm_strdup("sqlwarning"); } diff --git a/src/interfaces/ecpg/preproc/ecpg_keywords.c b/src/interfaces/ecpg/preproc/ecpg_keywords.c index fb54d7b..6c819fd 100644 --- a/src/interfaces/ecpg/preproc/ecpg_keywords.c +++ b/src/interfaces/ecpg/preproc/ecpg_keywords.c @@ -67,8 +67,6 @@ {"section", SQL_SECTION, 0}, {"short", SQL_SHORT, 0}, {"signed", SQL_SIGNED, 0}, - {"sql", SQL_SQL, 0}, /* strange thing, used for into sql descriptor - * MYDESC; */ {"sqlerror", SQL_SQLERROR, 0}, {"sqlprint", SQL_SQLPRINT, 0}, {"sqlwarning", SQL_SQLWARNING, 0}, diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile index e0e31ec..49a4ed8 100644 --- a/src/pl/plperl/GNUmakefile +++ b/src/pl/plperl/GNUmakefile @@ -79,15 +79,17 @@ Util.c: Util.xs plperl_helpers.h install: all install-lib install-data installdirs: installdirs-lib - $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' + $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)' uninstall: uninstall-lib uninstall-data install-data: installdirs $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/' + $(INSTALL_DATA) $(srcdir)/plperl.h $(srcdir)/ppport.h '$(DESTDIR)$(includedir_server)' uninstall-data: rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA))) + rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, plperl.h ppport.h) .PHONY: install-data uninstall-data diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index de8cb0e..851a41f 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -110,6 +110,7 @@ SV *reference; /* CODE reference for Perl sub */ plperl_interp_desc *interp; /* interpreter it's created in */ bool fn_readonly; /* is function readonly (not volatile)? */ + Oid lang_oid; bool lanpltrusted; /* is it plperl, rather than plperlu? */ bool fn_retistuple; /* true, if function returns tuple */ bool fn_retisset; /* true, if function returns set */ @@ -210,6 +211,7 @@ bool *nulls; int *nelems; FmgrInfo proc; + FmgrInfo transform_proc; } plperl_array_info; /********************************************************************** @@ -1260,6 +1262,7 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, bool *isnull) { FmgrInfo tmp; + Oid funcid; /* we might recurse */ check_stack_depth(); @@ -1283,6 +1286,8 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, /* must call typinput in case it wants to reject NULL */ return InputFunctionCall(finfo, NULL, typioparam, typmod); } + else if ((funcid = get_transform_tosql(typid, current_call_data->prodesc->lang_oid))) + return OidFunctionCall1(funcid, PointerGetDatum(sv)); else if (SvROK(sv)) { /* handle references */ @@ -1395,6 +1400,7 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, typdelim; Oid typioparam; Oid typoutputfunc; + Oid transform_funcid; int i, nitems, *dims; @@ -1402,14 +1408,17 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, SV *av; HV *hv; - info = palloc(sizeof(plperl_array_info)); + info = palloc0(sizeof(plperl_array_info)); /* get element type information, including output conversion function */ get_type_io_data(elementtype, IOFunc_output, &typlen, &typbyval, &typalign, &typdelim, &typioparam, &typoutputfunc); - perm_fmgr_info(typoutputfunc, &info->proc); + if ((transform_funcid = get_transform_fromsql(elementtype, current_call_data->prodesc->lang_oid))) + perm_fmgr_info(transform_funcid, &info->transform_proc); + else + perm_fmgr_info(typoutputfunc, &info->proc); info->elem_is_rowtype = type_is_rowtype(elementtype); @@ -1490,8 +1499,10 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, { Datum itemvalue = info->elements[i]; - /* Handle composite type elements */ - if (info->elem_is_rowtype) + if (info->transform_proc.fn_oid) + av_push(result, (SV *) DatumGetPointer(FunctionCall1(&info->transform_proc, itemvalue))); + else if (info->elem_is_rowtype) + /* Handle composite type elements */ av_push(result, plperl_hash_from_datum(itemvalue)); else { @@ -1778,6 +1789,7 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, desc.proname = "inline_code_block"; desc.fn_readonly = false; + desc.lang_oid = codeblock->langOid; desc.lanpltrusted = codeblock->langIsTrusted; desc.fn_retistuple = false; @@ -2035,6 +2047,8 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, SV *retval; int i; int count; + Oid *argtypes = NULL; + int nargs = 0; ENTER; SAVETMPS; @@ -2042,6 +2056,9 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, PUSHMARK(SP); EXTEND(sp, desc->nargs); + if (fcinfo->flinfo->fn_oid) + get_func_signature(fcinfo->flinfo->fn_oid, &argtypes, &nargs); + for (i = 0; i < desc->nargs; i++) { if (fcinfo->argnull[i]) @@ -2055,9 +2072,12 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, else { SV *sv; + Oid funcid; if (OidIsValid(desc->arg_arraytype[i])) sv = plperl_ref_from_pg_array(fcinfo->arg[i], desc->arg_arraytype[i]); + else if ((funcid = get_transform_fromsql(argtypes[i], current_call_data->prodesc->lang_oid))) + sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, fcinfo->arg[i])); else { char *tmp; @@ -2536,6 +2556,7 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, procStruct->prolang); } langStruct = (Form_pg_language) GETSTRUCT(langTup); + prodesc->lang_oid = HeapTupleGetOid(langTup); prodesc->lanpltrusted = langStruct->lanpltrusted; ReleaseSysCache(langTup); @@ -2768,9 +2789,12 @@ static SV *plperl_call_perl_func(plperl_proc_desc *desc, else { SV *sv; + Oid funcid; if (OidIsValid(get_base_element_type(tupdesc->attrs[i]->atttypid))) sv = plperl_ref_from_pg_array(attr, tupdesc->attrs[i]->atttypid); + else if ((funcid = get_transform_fromsql(tupdesc->attrs[i]->atttypid, current_call_data->prodesc->lang_oid))) + sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, attr)); else { char *outputstr; diff --git a/src/pl/plperl/plperl_helpers.h b/src/pl/plperl/plperl_helpers.h index 3e8aa7c..53ff66a 100644 --- a/src/pl/plperl/plperl_helpers.h +++ b/src/pl/plperl/plperl_helpers.h @@ -1,6 +1,8 @@ #ifndef PL_PERL_HELPERS_H #define PL_PERL_HELPERS_H +#include "mb/pg_wchar.h" + /* * convert from utf8 to database encoding * diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index 3fe8e4a..d595c5c 100644 --- a/src/pl/plpython/Makefile +++ b/src/pl/plpython/Makefile @@ -115,54 +115,22 @@ all: all-lib install: all install-lib install-data installdirs: installdirs-lib - $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' + $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)' uninstall: uninstall-lib uninstall-data install-data: installdirs $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/' + $(INSTALL_DATA) $(srcdir)/plpython.h $(srcdir)/plpy_util.h '$(DESTDIR)$(includedir_server)' uninstall-data: rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA))) + rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, plpython.h plpy_util.h) .PHONY: install-data uninstall-data -ifeq ($(python_majorversion),3) -# Adjust regression tests for Python 3 compatibility -# -# Mention those regression test files that need to be mangled in the -# variable REGRESS_PLPYTHON3_MANGLE. They will be copied to a -# subdirectory python3/ and have their Python syntax and other bits -# adjusted to work with Python 3. - -# Note that the order of the tests needs to be preserved in this -# expression. -REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_MANGLE)),python3/$(test),$(test))) - -.PHONY: pgregress-python3-mangle -pgregress-python3-mangle: - $(MKDIR_P) sql/python3 expected/python3 results/python3 - for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \ - sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \ - -e "s///g" \ - -e "s///g" \ - -e "s/\([0-9][0-9]*\)L/\1/g" \ - -e 's/\([ [{]\)u"/\1"/g' \ - -e "s/\([ [{]\)u'/\1'/g" \ - -e "s/def next/def __next__/g" \ - -e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \ - -e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \ - -e "s/EXTENSION plpythonu/EXTENSION plpython3u/g" \ - -e "s/EXTENSION plpython2u/EXTENSION plpython3u/g" \ - $$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \ - done - -check installcheck: pgregress-python3-mangle - -pg_regress_clean_files += sql/python3/ expected/python3/ results/python3/ - -endif # Python 3 +include $(srcdir)/regress-python3-mangle.mk check: all submake diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c index 0dad843..10cb623 100644 --- a/src/pl/plpython/plpy_main.c +++ b/src/pl/plpython/plpy_main.c @@ -283,6 +283,7 @@ MemSet(&proc, 0, sizeof(PLyProcedure)); proc.pyname = PLy_strdup("__plpython_inline_block"); + proc.langid = codeblock->langOid; proc.result.out.d.typoid = VOIDOID; /* diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c index d278d6e..7f55d20 100644 --- a/src/pl/plpython/plpy_procedure.c +++ b/src/pl/plpython/plpy_procedure.c @@ -13,6 +13,7 @@ #include "catalog/pg_type.h" #include "utils/builtins.h" #include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/syscache.h" #include "plpython.h" @@ -26,6 +27,7 @@ static HTAB *PLy_procedure_cache = NULL; static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger); +static void invalidate_procedure_caches(Datum arg, int cacheid, uint32 hashvalue); static bool PLy_procedure_argument_valid(PLyTypeInfo *arg); static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup); static char *PLy_procedure_munge_source(const char *name, const char *src); @@ -42,6 +44,29 @@ hash_ctl.hash = tag_hash; PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl, HASH_ELEM | HASH_FUNCTION); + CacheRegisterSyscacheCallback(TRFTYPELANG, + invalidate_procedure_caches, + (Datum) 0); +} + +static void +invalidate_procedure_caches(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS status; + PLyProcedureEntry *hentry; + + Assert(PLy_procedure_cache != NULL); + + /* flush all entries */ + hash_seq_init(&status, PLy_procedure_cache); + + while ((hentry = (PLyProcedureEntry *) hash_seq_search(&status))) + { + if (hash_search(PLy_procedure_cache, + (void *) &hentry->key, + HASH_REMOVE, NULL) == NULL) + elog(ERROR, "hash table corrupted"); + } } /* @@ -166,6 +191,7 @@ for (i = 0; i < FUNC_MAX_ARGS; i++) PLy_typeinfo_init(&proc->args[i]); proc->nargs = 0; + proc->langid = procStruct->prolang; proc->code = proc->statics = NULL; proc->globals = NULL; proc->is_setof = procStruct->proretset; @@ -220,7 +246,7 @@ else { /* do the real work */ - PLy_output_datum_func(&proc->result, rvTypeTup); + PLy_output_datum_func(&proc->result, rvTypeTup, proc->langid); } ReleaseSysCache(rvTypeTup); @@ -294,7 +320,8 @@ default: PLy_input_datum_func(&(proc->args[pos]), types[i], - argTypeTup); + argTypeTup, + proc->langid); break; } diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h index f1c8510..9776e7d 100644 --- a/src/pl/plpython/plpy_procedure.h +++ b/src/pl/plpython/plpy_procedure.h @@ -27,6 +27,7 @@ typedef struct PLyProcedure char **argnames; /* Argument names */ PLyTypeInfo args[FUNC_MAX_ARGS]; int nargs; + Oid langid; /* OID of plpython pg_language entry */ PyObject *code; /* compiled procedure code */ PyObject *statics; /* data saved across calls, local scope */ PyObject *globals; /* data saved across calls, global scope */ diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c index 982bf84..dbae6b6 100644 --- a/src/pl/plpython/plpy_spi.c +++ b/src/pl/plpython/plpy_spi.c @@ -76,6 +76,7 @@ PG_TRY(); { int i; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); /* * the other loop might throw an exception, if PLyTypeInfo member @@ -131,7 +132,7 @@ plan->types[i] = typeId; typeStruct = (Form_pg_type) GETSTRUCT(typeTup); if (typeStruct->typtype != TYPTYPE_COMPOSITE) - PLy_output_datum_func(&plan->args[i], typeTup); + PLy_output_datum_func(&plan->args[i], typeTup, exec_ctx->curr_proc->langid); else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c index caccbf9..33187db 100644 --- a/src/pl/plpython/plpy_typeio.c +++ b/src/pl/plpython/plpy_typeio.c @@ -29,8 +29,8 @@ /* I/O function caching */ -static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup); -static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup); +static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid); +static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid); /* conversion from Datums to Python objects */ static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d); @@ -43,6 +43,7 @@ static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d); static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d); static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d); +static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d); static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d); /* conversion from Python objects to Datums */ @@ -50,6 +51,7 @@ static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv); static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv); static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv); +static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv); static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv); /* conversion from Python objects to composite Datums (used by triggers and SRFs) */ @@ -102,27 +104,28 @@ * PostgreSQL, and vice versa. */ void -PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup) +PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid) { if (arg->is_rowtype > 0) elog(ERROR, "PLyTypeInfo struct is initialized for Tuple"); arg->is_rowtype = 0; - PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup); + PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup, langid); } void -PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup) +PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid) { if (arg->is_rowtype > 0) elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple"); arg->is_rowtype = 0; - PLy_output_datum_func2(&(arg->out.d), typeTup); + PLy_output_datum_func2(&(arg->out.d), typeTup, langid); } void PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc) { int i; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); if (arg->is_rowtype == 0) elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); @@ -181,7 +184,8 @@ PLy_input_datum_func2(&(arg->in.r.atts[i]), desc->attrs[i]->atttypid, - typeTup); + typeTup, + exec_ctx->curr_proc->langid); ReleaseSysCache(typeTup); } @@ -191,6 +195,7 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc) { int i; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); if (arg->is_rowtype == 0) elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); @@ -243,7 +248,7 @@ elog(ERROR, "cache lookup failed for type %u", desc->attrs[i]->atttypid); - PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup); + PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup, exec_ctx->curr_proc->langid); ReleaseSysCache(typeTup); } @@ -362,10 +367,12 @@ } static void -PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup) +PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid) { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); Oid element_type; + Oid base_type; + Oid funcid; perm_fmgr_info(typeStruct->typinput, &arg->typfunc); arg->typoid = HeapTupleGetOid(typeTup); @@ -374,12 +381,24 @@ arg->typbyval = typeStruct->typbyval; element_type = get_element_type(arg->typoid); + base_type = getBaseType(element_type ? element_type : arg->typoid); /* * Select a conversion function to convert Python objects to PostgreSQL - * datums. Most data types can go through the generic function. + * datums. */ - switch (getBaseType(element_type ? element_type : arg->typoid)) + + if ((funcid = get_transform_tosql(base_type, langid))) + { + arg->func = PLyObject_ToTransform; + perm_fmgr_info(funcid, &arg->typtransform); + } + else if (typeStruct->typtype == TYPTYPE_COMPOSITE) + { + arg->func = PLyObject_ToComposite; + } + else + switch (base_type) { case BOOLOID: arg->func = PLyObject_ToBool; @@ -392,12 +411,6 @@ break; } - /* Composite types need their own input routine, though */ - if (typeStruct->typtype == TYPTYPE_COMPOSITE) - { - arg->func = PLyObject_ToComposite; - } - if (element_type) { char dummy_delim; @@ -412,6 +425,7 @@ arg->elm = PLy_malloc0(sizeof(*arg->elm)); arg->elm->func = arg->func; + arg->elm->typtransform = arg->typtransform; arg->func = PLySequence_ToArray; arg->elm->typoid = element_type; @@ -424,10 +438,12 @@ } static void -PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup) +PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid) { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); - Oid element_type = get_element_type(typeOid); + Oid element_type; + Oid base_type; + Oid funcid; /* Get the type's conversion information */ perm_fmgr_info(typeStruct->typoutput, &arg->typfunc); @@ -439,7 +455,17 @@ arg->typalign = typeStruct->typalign; /* Determine which kind of Python object we will convert to */ - switch (getBaseType(element_type ? element_type : typeOid)) + + element_type = get_element_type(typeOid); + base_type = getBaseType(element_type ? element_type : typeOid); + + if ((funcid = get_transform_fromsql(base_type, langid))) + { + arg->func = PLyObject_FromTransform; + perm_fmgr_info(funcid, &arg->typtransform); + } + else + switch (base_type) { case BOOLOID: arg->func = PLyBool_FromBool; @@ -480,6 +506,7 @@ arg->elm = PLy_malloc0(sizeof(*arg->elm)); arg->elm->func = arg->func; + arg->elm->typtransform = arg->typtransform; arg->func = PLyList_FromArray; arg->elm->typoid = element_type; arg->elm->typmod = -1; @@ -591,14 +618,22 @@ static PyObject * PLyString_FromDatum(PLyDatumToOb *arg, Datum d) { - char *x = OutputFunctionCall(&arg->typfunc, d); - PyObject *r = PyString_FromString(x); + char *x; + PyObject *r; + x = OutputFunctionCall(&arg->typfunc, d); + r = PyString_FromString(x); pfree(x); return r; } static PyObject * +PLyObject_FromTransform(PLyDatumToOb *arg, Datum d) +{ + return (PyObject *) DatumGetPointer(FunctionCall1(&arg->typtransform, d)); +} + +static PyObject * PLyList_FromArray(PLyDatumToOb *arg, Datum d) { ArrayType *array = DatumGetArrayTypeP(d); @@ -747,16 +782,15 @@ /* - * Generic conversion function: Convert PyObject to cstring and - * cstring into PostgreSQL type. + * Convert Python object to C string in server encoding. */ -static Datum -PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv) +char * +PLyObject_AsString(PyObject *plrv) { - PyObject *volatile plrv_bo = NULL; - Datum rv; - - Assert(plrv != Py_None); + PyObject *plrv_bo; + char *plrv_sc; + size_t plen; + size_t slen; if (PyUnicode_Check(plrv)) plrv_bo = PLyUnicode_Bytes(plrv); @@ -774,36 +808,47 @@ if (!plrv_bo) PLy_elog(ERROR, "could not create string representation of Python object"); - PG_TRY(); - { - char *plrv_sc = PyBytes_AsString(plrv_bo); - size_t plen = PyBytes_Size(plrv_bo); - size_t slen = strlen(plrv_sc); - - if (slen < plen) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes"))); - else if (slen > plen) - elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length"); - pg_verifymbstr(plrv_sc, slen, false); - rv = InputFunctionCall(&arg->typfunc, - plrv_sc, - arg->typioparam, - typmod); - } - PG_CATCH(); - { - Py_XDECREF(plrv_bo); - PG_RE_THROW(); - } - PG_END_TRY(); + plrv_sc = pstrdup(PyBytes_AsString(plrv_bo)); + plen = PyBytes_Size(plrv_bo); + slen = strlen(plrv_sc); Py_XDECREF(plrv_bo); - return rv; + if (slen < plen) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes"))); + else if (slen > plen) + elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length"); + pg_verifymbstr(plrv_sc, slen, false); + + return plrv_sc; } + +/* + * Generic conversion function: Convert PyObject to cstring and + * cstring into PostgreSQL type. + */ +static Datum +PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv) +{ + Assert(plrv != Py_None); + + return InputFunctionCall(&arg->typfunc, + PLyObject_AsString(plrv), + arg->typioparam, + typmod); +} + + +static Datum +PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv) +{ + return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv)); +} + + static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv) { @@ -853,12 +898,13 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string) { HeapTuple typeTup; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid)); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid); - PLy_output_datum_func2(&info->out.d, typeTup); + PLy_output_datum_func2(&info->out.d, typeTup, exec_ctx->curr_proc->langid); ReleaseSysCache(typeTup); ReleaseTupleDesc(desc); diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h index 82e472a..b869aaa 100644 --- a/src/pl/plpython/plpy_typeio.h +++ b/src/pl/plpython/plpy_typeio.h @@ -17,6 +17,7 @@ typedef struct PLyDatumToOb { PLyDatumToObFunc func; FmgrInfo typfunc; /* The type's output function */ + FmgrInfo typtransform; /* from-SQL transform */ Oid typoid; /* The OID of the type */ int32 typmod; /* The typmod of the type */ Oid typioparam; @@ -48,6 +49,7 @@ typedef struct PLyObToDatum { PLyObToDatumFunc func; FmgrInfo typfunc; /* The type's input function */ + FmgrInfo typtransform; /* to-SQL transform */ Oid typoid; /* The OID of the type */ int32 typmod; /* The typmod of the type */ Oid typioparam; @@ -91,8 +93,8 @@ typedef struct PLyTypeInfo extern void PLy_typeinfo_init(PLyTypeInfo *arg); extern void PLy_typeinfo_dealloc(PLyTypeInfo *arg); -extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup); -extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup); +extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid); +extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid); extern void PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc); extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc); @@ -105,4 +107,7 @@ extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObj /* conversion from heap tuples to Python dictionaries */ extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc); +/* conversion from Python objects to C strings */ +extern char *PLyObject_AsString(PyObject *plrv); + #endif /* PLPY_TYPEIO_H */ diff --git a/src/pl/plpython/plpy_util.c b/src/pl/plpython/plpy_util.c index 95cbba5..bfa09d8 100644 --- a/src/pl/plpython/plpy_util.c +++ b/src/pl/plpython/plpy_util.c @@ -144,22 +144,33 @@ * unicode object. Reference ownership is passed to the caller. */ PyObject * -PLyUnicode_FromString(const char *s) +PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size) { char *utf8string; PyObject *o; utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s, - strlen(s), + size, GetDatabaseEncoding(), PG_UTF8); - o = PyUnicode_FromString(utf8string); - - if (utf8string != s) + if (utf8string == s) + { + o = PyUnicode_FromStringAndSize(s, size); + } + else + { + o = PyUnicode_FromString(utf8string); pfree(utf8string); + } return o; } +PyObject * +PLyUnicode_FromString(const char *s) +{ + return PLyUnicode_FromStringAndSize(s, strlen(s)); +} + #endif /* PY_MAJOR_VERSION >= 3 */ diff --git a/src/pl/plpython/plpy_util.h b/src/pl/plpython/plpy_util.h index f93e837..4c29f9a 100644 --- a/src/pl/plpython/plpy_util.h +++ b/src/pl/plpython/plpy_util.h @@ -16,6 +16,7 @@ extern char *PLyUnicode_AsString(PyObject *unicode); #if PY_MAJOR_VERSION >= 3 extern PyObject *PLyUnicode_FromString(const char *s); +extern PyObject *PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size); #endif #endif /* PLPY_UTIL_H */ diff --git a/src/pl/plpython/plpython.h b/src/pl/plpython/plpython.h index 795231c..632b09a 100644 --- a/src/pl/plpython/plpython.h +++ b/src/pl/plpython/plpython.h @@ -91,6 +91,7 @@ typedef int Py_ssize_t; #define PyString_Check(x) 0 #define PyString_AsString(x) PLyUnicode_AsString(x) #define PyString_FromString(x) PLyUnicode_FromString(x) +#define PyString_FromStringAndSize(x, size) PLyUnicode_FromStringAndSize(x, size) #endif /* diff --git a/src/pl/plpython/regress-python3-mangle.mk b/src/pl/plpython/regress-python3-mangle.mk new file mode 100644 index 0000000..d2c7490 --- /dev/null +++ b/src/pl/plpython/regress-python3-mangle.mk @@ -0,0 +1,35 @@ +ifeq ($(python_majorversion),3) +# Adjust regression tests for Python 3 compatibility +# +# Mention those regression test files that need to be mangled in the +# variable REGRESS_PLPYTHON3_MANGLE. They will be copied to a +# subdirectory python3/ and have their Python syntax and other bits +# adjusted to work with Python 3. + +# Note that the order of the tests needs to be preserved in this +# expression. +REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_MANGLE)),python3/$(test),$(test))) + +.PHONY: pgregress-python3-mangle +pgregress-python3-mangle: + $(MKDIR_P) sql/python3 expected/python3 results/python3 + for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \ + sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \ + -e "s///g" \ + -e "s///g" \ + -e "s/\([0-9][0-9]*\)L/\1/g" \ + -e 's/\([ [{]\)u"/\1"/g' \ + -e "s/\([ [{]\)u'/\1'/g" \ + -e "s/def next/def __next__/g" \ + -e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \ + -e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \ + -e "s/EXTENSION \([^ ]*_\)*plpythonu/EXTENSION \1plpython3u/g" \ + -e "s/EXTENSION \([^ ]*_\)*plpython2u/EXTENSION \1plpython3u/g" \ + $$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \ + done + +check installcheck: pgregress-python3-mangle + +pg_regress_clean_files += sql/python3/ expected/python3/ results/python3/ + +endif # Python 3 diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 432d39a..85fb3f7 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -126,6 +126,7 @@ SELECT relname, relhasindex pg_shseclabel | t pg_statistic | t pg_tablespace | t + pg_transform | t pg_trigger | t pg_ts_config | t pg_ts_config_map | t @@ -166,7 +167,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(155 rows) +(156 rows) -- -- another sanity check: every system catalog that has OIDs should have -- 1.8.4.rc3