Add transforms feature

This provides a mechanism for specifying conversions between SQL data
types and procedural languages.  As examples, there are transforms
for hstore and ltree for PL/Perl and PL/Python.

reviews by Pavel Stěhule and Andres Freund
This commit is contained in:
Peter Eisentraut 2015-04-26 10:33:14 -04:00
parent f320cbb615
commit cac7658205
101 changed files with 6034 additions and 2811 deletions

View File

@ -71,6 +71,18 @@ else
ALWAYS_SUBDIRS += sepgsql
endif
ifeq ($(with_perl),yes)
SUBDIRS += hstore_plperl
else
ALWAYS_SUBDIRS += hstore_plperl
endif
ifeq ($(with_python),yes)
SUBDIRS += hstore_plpython ltree_plpython
else
ALWAYS_SUBDIRS += hstore_plpython ltree_plpython
endif
# Missing:
# start-scripts \ (does not have a makefile)

4
contrib/hstore_plperl/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Generated subdirectories
/log/
/results/
/tmp_check/

View File

@ -0,0 +1,24 @@
# contrib/hstore_plperl/Makefile
MODULE_big = hstore_plperl
OBJS = hstore_plperl.o
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl -I$(perl_archlibexp)/CORE -I$(top_srcdir)/contrib/hstore
EXTENSION = hstore_plperl hstore_plperlu
DATA = hstore_plperl--1.0.sql hstore_plperlu--1.0.sql
REGRESS = hstore_plperl create_transform
REGRESS_OPTS = --load-extension=hstore --load-extension=plperl --load-extension=plperlu
EXTRA_INSTALL = contrib/hstore
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/hstore_plperl
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif

View File

@ -0,0 +1,74 @@
-- general regression test for transforms
DROP EXTENSION IF EXISTS hstore CASCADE;
NOTICE: extension "hstore" does not exist, skipping
DROP EXTENSION IF EXISTS plperl CASCADE;
NOTICE: extension "plperl" does not exist, skipping
DROP EXTENSION IF EXISTS hstore_plperl CASCADE;
NOTICE: extension "hstore_plperl" does not exist, skipping
CREATE EXTENSION hstore;
CREATE EXTENSION plperl;
CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS '$libdir/hstore_plperl';
CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS '$libdir/hstore_plperl';
CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: type "foo" does not exist
CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: language "foo" does not exist
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: return data type of FROM SQL function must be "internal"
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: first argument of transform function must be type "internal"
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
ERROR: transform for type hstore language plperl already exists
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl;
NOTICE: type "fake_type" does not exist, skipping
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang;
NOTICE: transform for type hstore language fake_lang does not exist, skipping
DROP TRANSFORM FOR foo LANGUAGE plperl;
ERROR: type "foo" does not exist
DROP TRANSFORM FOR hstore LANGUAGE foo;
ERROR: language "foo" does not exist
DROP TRANSFORM FOR hstore LANGUAGE plperl;
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl;
NOTICE: transform for type hstore language plperl does not exist, skipping
DROP FUNCTION hstore_to_plperl(val internal);
DROP FUNCTION plperl_to_hstore(val internal);
CREATE EXTENSION hstore_plperl;
\dx+ hstore_plperl
Objects in extension "hstore_plperl"
Object Description
--------------------------------------
function hstore_to_plperl(internal)
function plperl_to_hstore(internal)
transform for hstore language plperl
(3 rows)
ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl;
\dx+ hstore_plperl
Objects in extension "hstore_plperl"
Object Description
-------------------------------------
function hstore_to_plperl(internal)
function plperl_to_hstore(internal)
(2 rows)
ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl;
\dx+ hstore_plperl
Objects in extension "hstore_plperl"
Object Description
--------------------------------------
function hstore_to_plperl(internal)
function plperl_to_hstore(internal)
transform for hstore language plperl
(3 rows)
DROP EXTENSION hstore CASCADE;
NOTICE: drop cascades to extension hstore_plperl
DROP EXTENSION plperl CASCADE;

View File

@ -0,0 +1,213 @@
CREATE EXTENSION hstore_plperl;
CREATE EXTENSION hstore_plperlu;
SELECT transforms.udt_schema, transforms.udt_name,
routine_schema, routine_name,
group_name, transform_type
FROM information_schema.transforms JOIN information_schema.routines
USING (specific_catalog, specific_schema, specific_name)
ORDER BY 1, 2, 5, 6;
udt_schema | udt_name | routine_schema | routine_name | group_name | transform_type
------------+----------+----------------+-------------------+------------+----------------
public | hstore | public | hstore_to_plperl | plperl | FROM SQL
public | hstore | public | plperl_to_hstore | plperl | TO SQL
public | hstore | public | hstore_to_plperlu | plperlu | FROM SQL
public | hstore | public | plperlu_to_hstore | plperlu | TO SQL
(4 rows)
-- test hstore -> perl
CREATE FUNCTION test1(val hstore) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1('aa=>bb, cc=>NULL'::hstore);
INFO: $VAR1 = {
'aa' => 'bb',
'cc' => undef
};
CONTEXT: PL/Perl function "test1"
test1
-------
2
(1 row)
CREATE FUNCTION test1none(val hstore) RETURNS int
LANGUAGE plperlu
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1none('aa=>bb, cc=>NULL'::hstore);
INFO: $VAR1 = '"aa"=>"bb", "cc"=>NULL';
CONTEXT: PL/Perl function "test1none"
test1none
-----------
0
(1 row)
CREATE FUNCTION test1list(val hstore) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1list('aa=>bb, cc=>NULL'::hstore);
INFO: $VAR1 = {
'aa' => 'bb',
'cc' => undef
};
CONTEXT: PL/Perl function "test1list"
test1list
-----------
2
(1 row)
-- test hstore[] -> perl
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]->[0], $_[0]->[1]));
return scalar(keys %{$_[0]});
$$;
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
INFO: $VAR1 = {
'aa' => 'bb',
'cc' => undef
};
$VAR2 = {
'dd' => 'ee'
};
CONTEXT: PL/Perl function "test1arr"
test1arr
----------
2
(1 row)
-- test perl -> hstore
CREATE FUNCTION test2() RETURNS hstore
LANGUAGE plperl
TRANSFORM FOR TYPE hstore
AS $$
$val = {a => 1, b => 'boo', c => undef};
return $val;
$$;
SELECT test2();
test2
---------------------------------
"a"=>"1", "b"=>"boo", "c"=>NULL
(1 row)
-- test perl -> hstore[]
CREATE FUNCTION test2arr() RETURNS hstore[]
LANGUAGE plperl
TRANSFORM FOR TYPE hstore
AS $$
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}];
return $val;
$$;
SELECT test2arr();
test2arr
--------------------------------------------------------------
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
(1 row)
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1});
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
$val = {a => 1, b => 'boo', c => undef};
$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore");
$rv = spi_exec_prepared($plan, {}, $val);
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
$$;
SELECT test3();
INFO: $VAR1 = {
'aa' => 'bb',
'cc' => undef
};
CONTEXT: PL/Perl function "test3"
INFO: $VAR1 = '"a"=>"1", "b"=>"boo", "c"=>NULL';
CONTEXT: PL/Perl function "test3"
test3
-------
(1 row)
-- test trigger
CREATE TABLE test1 (a int, b hstore);
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
SELECT * FROM test1;
a | b
---+------------------------
1 | "aa"=>"bb", "cc"=>NULL
(1 row)
CREATE FUNCTION test4() RETURNS trigger
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_TD->{new}));
if ($_TD->{new}{a} == 1) {
$_TD->{new}{b} = {a => 1, b => 'boo', c => undef};
}
return "MODIFY";
$$;
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
UPDATE test1 SET a = a;
INFO: $VAR1 = {
'a' => '1',
'b' => {
'aa' => 'bb',
'cc' => undef
}
};
CONTEXT: PL/Perl function "test4"
SELECT * FROM test1;
a | b
---+---------------------------------
1 | "a"=>"1", "b"=>"boo", "c"=>NULL
(1 row)
DROP TABLE test1;
DROP FUNCTION test1(hstore);
DROP FUNCTION test1none(hstore);
DROP FUNCTION test1list(hstore);
DROP FUNCTION test1arr(hstore[]);
DROP FUNCTION test2();
DROP FUNCTION test2arr();
DROP FUNCTION test3();
DROP FUNCTION test4();
DROP EXTENSION hstore_plperl;
DROP EXTENSION hstore_plperlu;
DROP EXTENSION hstore;
DROP EXTENSION plperl;
DROP EXTENSION plperlu;

View File

@ -0,0 +1,17 @@
-- make sure the prerequisite libraries are loaded
DO '' LANGUAGE plperl;
SELECT NULL::hstore;
CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME';
CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME';
CREATE TRANSFORM FOR hstore LANGUAGE plperl (
FROM SQL WITH FUNCTION hstore_to_plperl(internal),
TO SQL WITH FUNCTION plperl_to_hstore(internal)
);

View File

@ -0,0 +1,90 @@
#include "postgres.h"
#undef _
#include "fmgr.h"
#include "plperl.h"
#include "plperl_helpers.h"
#include "hstore.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(hstore_to_plperl);
Datum hstore_to_plperl(PG_FUNCTION_ARGS);
Datum
hstore_to_plperl(PG_FUNCTION_ARGS)
{
HStore *in = PG_GETARG_HS(0);
int i;
int count = HS_COUNT(in);
char *base = STRPTR(in);
HEntry *entries = ARRPTR(in);
HV *hv;
hv = newHV();
for (i = 0; i < count; i++)
{
const char *key;
SV *value;
key = pnstrdup(HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
value = HS_VALISNULL(entries, i) ? newSV(0) : cstr2sv(pnstrdup(HS_VAL(entries, base,i), HS_VALLEN(entries, i)));
(void) hv_store(hv, key, strlen(key), value, 0);
}
return PointerGetDatum(newRV((SV *) hv));
}
PG_FUNCTION_INFO_V1(plperl_to_hstore);
Datum plperl_to_hstore(PG_FUNCTION_ARGS);
Datum
plperl_to_hstore(PG_FUNCTION_ARGS)
{
HV *hv;
HE *he;
int32 buflen;
int32 i;
int32 pcount;
HStore *out;
Pairs *pairs;
hv = (HV *) SvRV((SV *) PG_GETARG_POINTER(0));
pcount = hv_iterinit(hv);
pairs = palloc(pcount * sizeof(Pairs));
i = 0;
while ((he = hv_iternext(hv)))
{
char *key = sv2cstr(HeSVKEY_force(he));
SV *value = HeVAL(he);
pairs[i].key = pstrdup(key);
pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key));
pairs[i].needfree = true;
if (!SvOK(value))
{
pairs[i].val = NULL;
pairs[i].vallen = 0;
pairs[i].isnull = true;
}
else
{
pairs[i].val = pstrdup(sv2cstr(value));
pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val));
pairs[i].isnull = false;
}
i++;
}
pcount = hstoreUniquePairs(pairs, pcount, &buflen);
out = hstorePairs(pairs, pcount, buflen);
PG_RETURN_POINTER(out);
}

View File

@ -0,0 +1,6 @@
# hstore_plperl extension
comment = 'transform between hstore and plperl'
default_version = '1.0'
module_pathname = '$libdir/hstore_plperl'
relocatable = true
requires = 'hstore,plperl'

View File

@ -0,0 +1,17 @@
-- make sure the prerequisite libraries are loaded
DO '' LANGUAGE plperlu;
SELECT NULL::hstore;
CREATE FUNCTION hstore_to_plperlu(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'hstore_to_plperl';
CREATE FUNCTION plperlu_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'plperl_to_hstore';
CREATE TRANSFORM FOR hstore LANGUAGE plperlu (
FROM SQL WITH FUNCTION hstore_to_plperlu(internal),
TO SQL WITH FUNCTION plperlu_to_hstore(internal)
);

View File

@ -0,0 +1,6 @@
# hstore_plperlu extension
comment = 'transform between hstore and plperlu'
default_version = '1.0'
module_pathname = '$libdir/hstore_plperl'
relocatable = true
requires = 'hstore,plperlu'

View File

@ -0,0 +1,47 @@
-- general regression test for transforms
DROP EXTENSION IF EXISTS hstore CASCADE;
DROP EXTENSION IF EXISTS plperl CASCADE;
DROP EXTENSION IF EXISTS hstore_plperl CASCADE;
CREATE EXTENSION hstore;
CREATE EXTENSION plperl;
CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS '$libdir/hstore_plperl';
CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS '$libdir/hstore_plperl';
CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl;
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang;
DROP TRANSFORM FOR foo LANGUAGE plperl;
DROP TRANSFORM FOR hstore LANGUAGE foo;
DROP TRANSFORM FOR hstore LANGUAGE plperl;
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl;
DROP FUNCTION hstore_to_plperl(val internal);
DROP FUNCTION plperl_to_hstore(val internal);
CREATE EXTENSION hstore_plperl;
\dx+ hstore_plperl
ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl;
\dx+ hstore_plperl
ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl;
\dx+ hstore_plperl
DROP EXTENSION hstore CASCADE;
DROP EXTENSION plperl CASCADE;

View File

@ -0,0 +1,148 @@
CREATE EXTENSION hstore_plperl;
CREATE EXTENSION hstore_plperlu;
SELECT transforms.udt_schema, transforms.udt_name,
routine_schema, routine_name,
group_name, transform_type
FROM information_schema.transforms JOIN information_schema.routines
USING (specific_catalog, specific_schema, specific_name)
ORDER BY 1, 2, 5, 6;
-- test hstore -> perl
CREATE FUNCTION test1(val hstore) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1('aa=>bb, cc=>NULL'::hstore);
CREATE FUNCTION test1none(val hstore) RETURNS int
LANGUAGE plperlu
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1none('aa=>bb, cc=>NULL'::hstore);
CREATE FUNCTION test1list(val hstore) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]));
return scalar(keys %{$_[0]});
$$;
SELECT test1list('aa=>bb, cc=>NULL'::hstore);
-- test hstore[] -> perl
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_[0]->[0], $_[0]->[1]));
return scalar(keys %{$_[0]});
$$;
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
-- test perl -> hstore
CREATE FUNCTION test2() RETURNS hstore
LANGUAGE plperl
TRANSFORM FOR TYPE hstore
AS $$
$val = {a => 1, b => 'boo', c => undef};
return $val;
$$;
SELECT test2();
-- test perl -> hstore[]
CREATE FUNCTION test2arr() RETURNS hstore[]
LANGUAGE plperl
TRANSFORM FOR TYPE hstore
AS $$
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}];
return $val;
$$;
SELECT test2arr();
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1});
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
$val = {a => 1, b => 'boo', c => undef};
$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore");
$rv = spi_exec_prepared($plan, {}, $val);
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
$$;
SELECT test3();
-- test trigger
CREATE TABLE test1 (a int, b hstore);
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
SELECT * FROM test1;
CREATE FUNCTION test4() RETURNS trigger
LANGUAGE plperlu
TRANSFORM FOR TYPE hstore
AS $$
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
elog(INFO, Dumper($_TD->{new}));
if ($_TD->{new}{a} == 1) {
$_TD->{new}{b} = {a => 1, b => 'boo', c => undef};
}
return "MODIFY";
$$;
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
UPDATE test1 SET a = a;
SELECT * FROM test1;
DROP TABLE test1;
DROP FUNCTION test1(hstore);
DROP FUNCTION test1none(hstore);
DROP FUNCTION test1list(hstore);
DROP FUNCTION test1arr(hstore[]);
DROP FUNCTION test2();
DROP FUNCTION test2arr();
DROP FUNCTION test3();
DROP FUNCTION test4();
DROP EXTENSION hstore_plperl;
DROP EXTENSION hstore_plperlu;
DROP EXTENSION hstore;
DROP EXTENSION plperl;
DROP EXTENSION plperlu;

6
contrib/hstore_plpython/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Generated subdirectories
/expected/python3/
/log/
/results/
/sql/python3/
/tmp_check/

View File

@ -0,0 +1,31 @@
# contrib/hstore_plpython/Makefile
MODULE_big = hstore_plpython$(python_majorversion)
OBJS = hstore_plpython.o
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/hstore
EXTENSION = hstore_plpythonu hstore_plpython2u hstore_plpython3u
DATA = hstore_plpythonu--1.0.sql hstore_plpython2u--1.0.sql hstore_plpython3u--1.0.sql
REGRESS = hstore_plpython
REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/hstore_plpython
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
REGRESS_OPTS = --load-extension=hstore
ifeq ($(python_majorversion),2)
REGRESS_OPTS += --load-extension=plpythonu --load-extension=hstore_plpythonu
endif
EXTRA_INSTALL = contrib/hstore
include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk

View File

@ -0,0 +1,132 @@
CREATE EXTENSION plpython2u;
CREATE EXTENSION hstore_plpython2u;
-- test hstore -> python
CREATE FUNCTION test1(val hstore) RETURNS int
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
assert isinstance(val, dict)
plpy.info(sorted(val.items()))
return len(val)
$$;
SELECT test1('aa=>bb, cc=>NULL'::hstore);
INFO: [('aa', 'bb'), ('cc', None)]
CONTEXT: PL/Python function "test1"
test1
-------
2
(1 row)
-- the same with the versioned language name
CREATE FUNCTION test1n(val hstore) RETURNS int
LANGUAGE plpython2u
TRANSFORM FOR TYPE hstore
AS $$
assert isinstance(val, dict)
plpy.info(sorted(val.items()))
return len(val)
$$;
SELECT test1n('aa=>bb, cc=>NULL'::hstore);
INFO: [('aa', 'bb'), ('cc', None)]
CONTEXT: PL/Python function "test1n"
test1n
--------
2
(1 row)
-- test hstore[] -> python
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
plpy.info(repr(val))
return len(val)
$$;
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
INFO: [{'aa': 'bb', 'cc': None}, {'dd': 'ee'}]
CONTEXT: PL/Python function "test1arr"
test1arr
----------
2
(1 row)
-- test python -> hstore
CREATE FUNCTION test2() RETURNS hstore
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
val = {'a': 1, 'b': 'boo', 'c': None}
return val
$$;
SELECT test2();
test2
---------------------------------
"a"=>"1", "b"=>"boo", "c"=>NULL
(1 row)
-- test python -> hstore[]
CREATE FUNCTION test2arr() RETURNS hstore[]
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val
$$;
SELECT test2arr();
test2arr
--------------------------------------------------------------
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
(1 row)
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1")
plpy.info(repr(rv[0]["col1"]))
val = {'a': 1, 'b': 'boo', 'c': None}
plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"])
rv = plpy.execute(plan, [val])
plpy.info(repr(rv[0]["col1"]))
$$;
SELECT test3();
INFO: {'aa': 'bb', 'cc': None}
CONTEXT: PL/Python function "test3"
INFO: '"a"=>"1", "b"=>"boo", "c"=>NULL'
CONTEXT: PL/Python function "test3"
test3
-------
(1 row)
-- test trigger
CREATE TABLE test1 (a int, b hstore);
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
SELECT * FROM test1;
a | b
---+------------------------
1 | "aa"=>"bb", "cc"=>NULL
(1 row)
CREATE FUNCTION test4() RETURNS trigger
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
plpy.info("Trigger row: {'a': %r, 'b': %r}" % (TD["new"]["a"], TD["new"]["b"]))
if TD["new"]["a"] == 1:
TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None}
return "MODIFY"
$$;
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
UPDATE test1 SET a = a;
INFO: Trigger row: {'a': 1, 'b': {'aa': 'bb', 'cc': None}}
CONTEXT: PL/Python function "test4"
SELECT * FROM test1;
a | b
---+---------------------------------
1 | "a"=>"1", "b"=>"boo", "c"=>NULL
(1 row)

View File

@ -0,0 +1,116 @@
#include "postgres.h"
#include "fmgr.h"
#include "plpython.h"
#include "plpy_typeio.h"
#include "hstore.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(hstore_to_plpython);
Datum hstore_to_plpython(PG_FUNCTION_ARGS);
Datum
hstore_to_plpython(PG_FUNCTION_ARGS)
{
HStore *in = PG_GETARG_HS(0);
int i;
int count = HS_COUNT(in);
char *base = STRPTR(in);
HEntry *entries = ARRPTR(in);
PyObject *dict;
dict = PyDict_New();
for (i = 0; i < count; i++)
{
PyObject *key;
key = PyString_FromStringAndSize(HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
if (HS_VALISNULL(entries, i))
PyDict_SetItem(dict, key, Py_None);
else
{
PyObject *value;
value = PyString_FromStringAndSize(HS_VAL(entries, base,i), HS_VALLEN(entries, i));
PyDict_SetItem(dict, key, value);
Py_XDECREF(value);
}
Py_XDECREF(key);
}
return PointerGetDatum(dict);
}
PG_FUNCTION_INFO_V1(plpython_to_hstore);
Datum plpython_to_hstore(PG_FUNCTION_ARGS);
Datum
plpython_to_hstore(PG_FUNCTION_ARGS)
{
PyObject *dict;
volatile PyObject *items_v = NULL;
int32 pcount;
HStore *out;
dict = (PyObject *) PG_GETARG_POINTER(0);
if (!PyMapping_Check(dict))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("not a Python mapping")));
pcount = PyMapping_Size(dict);
items_v = PyMapping_Items(dict);
PG_TRY();
{
int32 buflen;
int32 i;
Pairs *pairs;
PyObject *items = (PyObject *) items_v;
pairs = palloc(pcount * sizeof(*pairs));
for (i = 0; i < pcount; i++)
{
PyObject *tuple;
PyObject *key;
PyObject *value;
tuple = PyList_GetItem(items, i);
key = PyTuple_GetItem(tuple, 0);
value = PyTuple_GetItem(tuple, 1);
pairs[i].key = PLyObject_AsString(key);
pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key));
pairs[i].needfree = true;
if (value == Py_None)
{
pairs[i].val = NULL;
pairs[i].vallen = 0;
pairs[i].isnull = true;
}
else
{
pairs[i].val = PLyObject_AsString(value);
pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val));
pairs[i].isnull = false;
}
}
Py_DECREF(items_v);
pcount = hstoreUniquePairs(pairs, pcount, &buflen);
out = hstorePairs(pairs, pcount, buflen);
}
PG_CATCH();
{
Py_DECREF(items_v);
PG_RE_THROW();
}
PG_END_TRY();
PG_RETURN_POINTER(out);
}

View File

@ -0,0 +1,19 @@
-- make sure the prerequisite libraries are loaded
DO '1' LANGUAGE plpython2u;
SELECT NULL::hstore;
CREATE FUNCTION hstore_to_plpython2(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'hstore_to_plpython';
CREATE FUNCTION plpython2_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'plpython_to_hstore';
CREATE TRANSFORM FOR hstore LANGUAGE plpython2u (
FROM SQL WITH FUNCTION hstore_to_plpython2(internal),
TO SQL WITH FUNCTION plpython2_to_hstore(internal)
);
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython2u IS 'transform between hstore and Python dict';

View File

@ -0,0 +1,6 @@
# hstore_plpython2u extension
comment = 'transform between hstore and plpython2u'
default_version = '1.0'
module_pathname = '$libdir/hstore_plpython2'
relocatable = true
requires = 'hstore,plpython2u'

View File

@ -0,0 +1,19 @@
-- make sure the prerequisite libraries are loaded
DO '1' LANGUAGE plpython3u;
SELECT NULL::hstore;
CREATE FUNCTION hstore_to_plpython3(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'hstore_to_plpython';
CREATE FUNCTION plpython3_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'plpython_to_hstore';
CREATE TRANSFORM FOR hstore LANGUAGE plpython3u (
FROM SQL WITH FUNCTION hstore_to_plpython3(internal),
TO SQL WITH FUNCTION plpython3_to_hstore(internal)
);
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython3u IS 'transform between hstore and Python dict';

View File

@ -0,0 +1,6 @@
# hstore_plpython3u extension
comment = 'transform between hstore and plpython3u'
default_version = '1.0'
module_pathname = '$libdir/hstore_plpython3'
relocatable = true
requires = 'hstore,plpython3u'

View File

@ -0,0 +1,19 @@
-- make sure the prerequisite libraries are loaded
DO '1' LANGUAGE plpythonu;
SELECT NULL::hstore;
CREATE FUNCTION hstore_to_plpython(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME';
CREATE FUNCTION plpython_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME';
CREATE TRANSFORM FOR hstore LANGUAGE plpythonu (
FROM SQL WITH FUNCTION hstore_to_plpython(internal),
TO SQL WITH FUNCTION plpython_to_hstore(internal)
);
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpythonu IS 'transform between hstore and Python dict';

View File

@ -0,0 +1,6 @@
# hstore_plpythonu extension
comment = 'transform between hstore and plpythonu'
default_version = '1.0'
module_pathname = '$libdir/hstore_plpython2'
relocatable = true
requires = 'hstore,plpythonu'

View File

@ -0,0 +1,103 @@
CREATE EXTENSION plpython2u;
CREATE EXTENSION hstore_plpython2u;
-- test hstore -> python
CREATE FUNCTION test1(val hstore) RETURNS int
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
assert isinstance(val, dict)
plpy.info(sorted(val.items()))
return len(val)
$$;
SELECT test1('aa=>bb, cc=>NULL'::hstore);
-- the same with the versioned language name
CREATE FUNCTION test1n(val hstore) RETURNS int
LANGUAGE plpython2u
TRANSFORM FOR TYPE hstore
AS $$
assert isinstance(val, dict)
plpy.info(sorted(val.items()))
return len(val)
$$;
SELECT test1n('aa=>bb, cc=>NULL'::hstore);
-- test hstore[] -> python
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
plpy.info(repr(val))
return len(val)
$$;
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
-- test python -> hstore
CREATE FUNCTION test2() RETURNS hstore
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
val = {'a': 1, 'b': 'boo', 'c': None}
return val
$$;
SELECT test2();
-- test python -> hstore[]
CREATE FUNCTION test2arr() RETURNS hstore[]
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val
$$;
SELECT test2arr();
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1")
plpy.info(repr(rv[0]["col1"]))
val = {'a': 1, 'b': 'boo', 'c': None}
plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"])
rv = plpy.execute(plan, [val])
plpy.info(repr(rv[0]["col1"]))
$$;
SELECT test3();
-- test trigger
CREATE TABLE test1 (a int, b hstore);
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
SELECT * FROM test1;
CREATE FUNCTION test4() RETURNS trigger
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
plpy.info("Trigger row: {'a': %r, 'b': %r}" % (TD["new"]["a"], TD["new"]["b"]))
if TD["new"]["a"] == 1:
TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None}
return "MODIFY"
$$;
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
UPDATE test1 SET a = a;
SELECT * FROM test1;

6
contrib/ltree_plpython/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Generated subdirectories
/expected/python3/
/log/
/results/
/sql/python3/
/tmp_check/

View File

@ -0,0 +1,31 @@
# contrib/ltree_plpython/Makefile
MODULE_big = ltree_plpython$(python_majorversion)
OBJS = ltree_plpython.o
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/ltree
EXTENSION = ltree_plpythonu ltree_plpython2u ltree_plpython3u
DATA = ltree_plpythonu--1.0.sql ltree_plpython2u--1.0.sql ltree_plpython3u--1.0.sql
REGRESS = ltree_plpython
REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/ltree_plpython
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
REGRESS_OPTS = --load-extension=ltree
ifeq ($(python_majorversion),2)
REGRESS_OPTS += --load-extension=plpythonu --load-extension=ltree_plpythonu
endif
EXTRA_INSTALL = contrib/ltree
include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk

View File

@ -0,0 +1,45 @@
CREATE EXTENSION plpython2u;
CREATE EXTENSION ltree_plpython2u;
CREATE FUNCTION test1(val ltree) RETURNS int
LANGUAGE plpythonu
TRANSFORM FOR TYPE ltree
AS $$
plpy.info(repr(val))
return len(val)
$$;
SELECT test1('aa.bb.cc'::ltree);
INFO: ['aa', 'bb', 'cc']
CONTEXT: PL/Python function "test1"
test1
-------
3
(1 row)
CREATE FUNCTION test1n(val ltree) RETURNS int
LANGUAGE plpython2u
TRANSFORM FOR TYPE ltree
AS $$
plpy.info(repr(val))
return len(val)
$$;
SELECT test1n('aa.bb.cc'::ltree);
INFO: ['aa', 'bb', 'cc']
CONTEXT: PL/Python function "test1n"
test1n
--------
3
(1 row)
CREATE FUNCTION test2() RETURNS ltree
LANGUAGE plpythonu
TRANSFORM FOR TYPE ltree
AS $$
return ['foo', 'bar', 'baz']
$$;
-- plpython to ltree is not yet implemented, so this will fail,
-- because it will try to parse the Python list as an ltree input
-- string.
SELECT test2();
ERROR: syntax error at position 0
CONTEXT: while creating return value
PL/Python function "test2"

View File

@ -0,0 +1,32 @@
#include "postgres.h"
#include "fmgr.h"
#include "plpython.h"
#include "ltree.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(ltree_to_plpython);
Datum ltree_to_plpython(PG_FUNCTION_ARGS);
Datum
ltree_to_plpython(PG_FUNCTION_ARGS)
{
ltree *in = PG_GETARG_LTREE(0);
int i;
PyObject *list;
ltree_level *curlevel;
list = PyList_New(in->numlevel);
curlevel = LTREE_FIRST(in);
for (i = 0; i < in->numlevel; i++)
{
PyList_SetItem(list, i, PyString_FromStringAndSize(curlevel->name, curlevel->len));
curlevel = LEVEL_NEXT(curlevel);
}
PG_FREE_IF_COPY(in, 0);
return PointerGetDatum(list);
}

View File

@ -0,0 +1,12 @@
-- make sure the prerequisite libraries are loaded
DO '1' LANGUAGE plpython2u;
SELECT NULL::ltree;
CREATE FUNCTION ltree_to_plpython2(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'ltree_to_plpython';
CREATE TRANSFORM FOR ltree LANGUAGE plpython2u (
FROM SQL WITH FUNCTION ltree_to_plpython2(internal)
);

View File

@ -0,0 +1,6 @@
# ltree_plpython2u extension
comment = 'transform between ltree and plpython2u'
default_version = '1.0'
module_pathname = '$libdir/ltree_plpython2'
relocatable = true
requires = 'ltree,plpython2u'

View File

@ -0,0 +1,12 @@
-- make sure the prerequisite libraries are loaded
DO '1' LANGUAGE plpython3u;
SELECT NULL::ltree;
CREATE FUNCTION ltree_to_plpython3(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME', 'ltree_to_plpython';
CREATE TRANSFORM FOR ltree LANGUAGE plpython3u (
FROM SQL WITH FUNCTION ltree_to_plpython3(internal)
);

View File

@ -0,0 +1,6 @@
# ltree_plpython3u extension
comment = 'transform between ltree and plpython3u'
default_version = '1.0'
module_pathname = '$libdir/ltree_plpython3'
relocatable = true
requires = 'ltree,plpython3u'

View File

@ -0,0 +1,12 @@
-- make sure the prerequisite libraries are loaded
DO '1' LANGUAGE plpythonu;
SELECT NULL::ltree;
CREATE FUNCTION ltree_to_plpython(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS 'MODULE_PATHNAME';
CREATE TRANSFORM FOR ltree LANGUAGE plpythonu (
FROM SQL WITH FUNCTION ltree_to_plpython(internal)
);

View File

@ -0,0 +1,6 @@
# ltree_plpythonu extension
comment = 'transform between ltree and plpythonu'
default_version = '1.0'
module_pathname = '$libdir/ltree_plpython2'
relocatable = true
requires = 'ltree,plpythonu'

View File

@ -0,0 +1,37 @@
CREATE EXTENSION plpython2u;
CREATE EXTENSION ltree_plpython2u;
CREATE FUNCTION test1(val ltree) RETURNS int
LANGUAGE plpythonu
TRANSFORM FOR TYPE ltree
AS $$
plpy.info(repr(val))
return len(val)
$$;
SELECT test1('aa.bb.cc'::ltree);
CREATE FUNCTION test1n(val ltree) RETURNS int
LANGUAGE plpython2u
TRANSFORM FOR TYPE ltree
AS $$
plpy.info(repr(val))
return len(val)
$$;
SELECT test1n('aa.bb.cc'::ltree);
CREATE FUNCTION test2() RETURNS ltree
LANGUAGE plpythonu
TRANSFORM FOR TYPE ltree
AS $$
return ['foo', 'bar', 'baz']
$$;
-- plpython to ltree is not yet implemented, so this will fail,
-- because it will try to parse the Python list as an ltree input
-- string.
SELECT test2();

View File

@ -273,6 +273,11 @@
<entry>tablespaces within this database cluster</entry>
</row>
<row>
<entry><link linkend="catalog-pg-transform"><structname>pg_transform</structname></link></entry>
<entry>transforms (data type to procedural language conversions)</entry>
</row>
<row>
<entry><link linkend="catalog-pg-trigger"><structname>pg_trigger</structname></link></entry>
<entry>triggers</entry>
@ -5071,6 +5076,15 @@
</entry>
</row>
<row>
<entry><structfield>protrftypes</structfield></entry>
<entry><type>oid[]</type></entry>
<entry></entry>
<entry>
Data type OIDs for which to apply transforms.
</entry>
</row>
<row>
<entry><structfield>prosrc</structfield></entry>
<entry><type>text</type></entry>
@ -6071,6 +6085,74 @@
</sect1>
<sect1 id="catalog-pg-transform">
<title><structname>pg_transform</structname></title>
<indexterm zone="catalog-pg-transform">
<primary>pg_transform</primary>
</indexterm>
<para>
The catalog <structname>pg_transform</structname> stores information about
transforms, which are a mechanism to adapt data types to procedural
languages. See <xref linkend="sql-createtransform"> for more information.
</para>
<table>
<title><structname>pg_transform</> Columns</title>
<tgroup cols="4">
<thead>
<row>
<entry>Name</entry>
<entry>Type</entry>
<entry>References</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><structfield>trftype</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
<entry>OID of the data type this transform is for</entry>
</row>
<row>
<entry><structfield>trflang</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-language"><structname>pg_language</structname></link>.oid</literal></entry>
<entry>OID of the language this transform is for</entry>
</row>
<row>
<entry><structfield>trffromsql</structfield></entry>
<entry><type>regproc</type></entry>
<entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
<entry>
The OID of the function to use when converting the data type for input
to the procedural language (e.g., function parameters). Zero is stored
if this operation is not supported.
</entry>
</row>
<row>
<entry><structfield>trftosql</structfield></entry>
<entry><type>regproc</type></entry>
<entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
<entry>
The OID of the function to use when converting output from the
procedural language (e.g., return values) to the data type. Zero is
stored if this operation is not supported.
</entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1 id="catalog-pg-trigger">
<title><structname>pg_trigger</structname></title>

View File

@ -596,6 +596,25 @@ ALTER TABLE tablename ALTER hstorecol TYPE hstore USING hstorecol || '';
</sect2>
<sect2>
<title>Transforms</title>
<para>
Additional extensions are available that implement transforms for
the <type>hstore</type> type for the languages PL/Perl and PL/Python. The
extensions for PL/Perl are called <literal>hstore_plperl</literal>
and <literal>hstore_plperlu</literal>, for trusted and untrusted PL/Perl.
If you install these transforms and specify them when creating a
function, <type>hstore</type> values are mapped to Perl hashes. The
extensions for PL/Python are
called <literal>hstore_plpythonu</literal>, <literal>hstore_plpython2u</literal>,
and <literal>hstore_plpython3u</literal>
(see <xref linkend="plpython-python23"> for the PL/Python naming
convention). If you use them, <type>hstore</type> values are mapped to
Python dictionaries.
</para>
</sect2>
<sect2>
<title>Authors</title>

View File

@ -5519,6 +5519,91 @@ ORDER BY c.ordinal_position;
</table>
</sect1>
<sect1 id="infoschema-transforms">
<title><literal>transforms</literal></title>
<para>
The view <literal>transforms</literal> contains information about the
transforms defined in the current database. More precisely, it contains a
row for each function contained in a transform (the <quote>from SQL</quote>
or <quote>to SQL</quote> function).
</para>
<table>
<title><literal>transforms</literal> Columns</title>
<tgroup cols="3">
<thead>
<row>
<entry>Name</entry>
<entry>Data Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>udt_catalog</literal></entry>
<entry><type>sql_identifier</type></entry>
<entry>Name of the database that contains the type the transform is for (always the current database)</entry>
</row>
<row>
<entry><literal>udt_schema</literal></entry>
<entry><type>sql_identifier</type></entry>
<entry>Name of the schema that contains the type the transform is for</entry>
</row>
<row>
<entry><literal>udt_name</literal></entry>
<entry><type>sql_identifier</type></entry>
<entry>Name of the type the transform is for</entry>
</row>
<row>
<entry><literal>specific_catalog</literal></entry>
<entry><literal>sql_identifier</literal></entry>
<entry>Name of the database containing the function (always the current database)</entry>
</row>
<row>
<entry><literal>specific_schema</literal></entry>
<entry><literal>sql_identifier</literal></entry>
<entry>Name of the schema containing the function</entry>
</row>
<row>
<entry><literal>specific_name</literal></entry>
<entry><literal>sql_identifier</literal></entry>
<entry>
The <quote>specific name</quote> of the function. See <xref
linkend="infoschema-routines"> for more information.
</entry>
</row>
<row>
<entry><literal>group_name</literal></entry>
<entry><literal>sql_identifier</literal></entry>
<entry>
The SQL standard allows defining transforms in <quote>groups</quote>,
and selecting a group at run time. PostgreSQL does not support this.
Instead, transforms are specific to a language. As a compromise, this
field contains the language the transform is for.
</entry>
</row>
<row>
<entry><literal>transform_type</literal></entry>
<entry><type>character_data</type></entry>
<entry>
<literal>FROM SQL</literal> or <literal>TO SQL</literal>
</entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1 id="infoschema-triggered-update-columns">
<title><literal>triggered_update_columns</literal></title>

View File

@ -664,6 +664,21 @@ ltreetest=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
</para>
</sect2>
<sect2>
<title>Transforms</title>
<para>
Additional extensions are available that implement transforms for
the <type>ltree</type> type for PL/Python. The extensions are
called <literal>ltree_plpythonu</literal>, <literal>ltree_plpython2u</literal>,
and <literal>ltree_plpython3u</literal>
(see <xref linkend="plpython-python23"> for the PL/Python naming
convention). If you install these transforms and specify them when
creating a function, <type>ltree</type> values are mapped to Python lists.
(The reverse is currently not supported, however.)
</para>
</sect2>
<sect2>
<title>Authors</title>

View File

@ -79,6 +79,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY createTable SYSTEM "create_table.sgml">
<!ENTITY createTableAs SYSTEM "create_table_as.sgml">
<!ENTITY createTableSpace SYSTEM "create_tablespace.sgml">
<!ENTITY createTransform SYSTEM "create_transform.sgml">
<!ENTITY createTrigger SYSTEM "create_trigger.sgml">
<!ENTITY createTSConfig SYSTEM "create_tsconfig.sgml">
<!ENTITY createTSDictionary SYSTEM "create_tsdictionary.sgml">
@ -120,6 +121,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY dropServer SYSTEM "drop_server.sgml">
<!ENTITY dropTable SYSTEM "drop_table.sgml">
<!ENTITY dropTableSpace SYSTEM "drop_tablespace.sgml">
<!ENTITY dropTransform SYSTEM "drop_transform.sgml">
<!ENTITY dropTrigger SYSTEM "drop_trigger.sgml">
<!ENTITY dropTSConfig SYSTEM "drop_tsconfig.sgml">
<!ENTITY dropTSDictionary SYSTEM "drop_tsdictionary.sgml">

View File

@ -52,6 +52,7 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
TEXT SEARCH DICTIONARY <replaceable class="PARAMETER">object_name</replaceable> |
TEXT SEARCH PARSER <replaceable class="PARAMETER">object_name</replaceable> |
TEXT SEARCH TEMPLATE <replaceable class="PARAMETER">object_name</replaceable> |
TRANSFORM FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable> |
TYPE <replaceable class="PARAMETER">object_name</replaceable> |
VIEW <replaceable class="PARAMETER">object_name</replaceable>
@ -259,6 +260,26 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>type_name</replaceable></term>
<listitem>
<para>
The name of the data type of the transform.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>lang_name</replaceable></term>
<listitem>
<para>
The name of the language of the transform.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</refsect1>

View File

@ -55,6 +55,7 @@ COMMENT ON
TEXT SEARCH DICTIONARY <replaceable class="PARAMETER">object_name</replaceable> |
TEXT SEARCH PARSER <replaceable class="PARAMETER">object_name</replaceable> |
TEXT SEARCH TEMPLATE <replaceable class="PARAMETER">object_name</replaceable> |
TRANSFORM FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable> |
TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> |
TYPE <replaceable class="PARAMETER">object_name</replaceable> |
VIEW <replaceable class="PARAMETER">object_name</replaceable>
@ -225,6 +226,26 @@ COMMENT ON
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>type_name</replaceable></term>
<listitem>
<para>
The name of the data type of the transform.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>lang_name</replaceable></term>
<listitem>
<para>
The name of the language of the transform.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">text</replaceable></term>
<listitem>
@ -305,6 +326,7 @@ COMMENT ON TEXT SEARCH CONFIGURATION my_config IS 'Special word filtering';
COMMENT ON TEXT SEARCH DICTIONARY swedish IS 'Snowball stemmer for Swedish language';
COMMENT ON TEXT SEARCH PARSER my_parser IS 'Splits text into words';
COMMENT ON TEXT SEARCH TEMPLATE snowball IS 'Snowball stemmer';
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpythonu IS 'Transform between hstore and Python dict';
COMMENT ON TRIGGER my_trigger ON my_table IS 'Used for RI';
COMMENT ON TYPE complex IS 'Complex number data type';
COMMENT ON VIEW my_view IS 'View of departmental costs';

View File

@ -25,6 +25,7 @@ CREATE [ OR REPLACE ] FUNCTION
[ RETURNS <replaceable class="parameter">rettype</replaceable>
| RETURNS TABLE ( <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">column_type</replaceable> [, ...] ) ]
{ LANGUAGE <replaceable class="parameter">lang_name</replaceable>
| TRANSFORM { FOR TYPE <replaceable class="parameter">type_name</replaceable> } [, ... ]
| WINDOW
| IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF
| CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
@ -260,6 +261,23 @@ CREATE [ OR REPLACE ] FUNCTION
</listitem>
</varlistentry>
<varlistentry>
<term><literal>TRANSFORM { FOR TYPE <replaceable class="parameter">type_name</replaceable> } [, ... ] }</literal></term>
<listitem>
<para>
Lists which transforms a call to the function should apply. Transforms
convert between SQL types and language-specific data types;
see <xref linkend="sql-createtransform">. Procedural language
implementations usually have hardcoded knowledge of the built-in types,
so those don't need to be listed here. If a procedural language
implementation does not know how to handle a type and no transform is
supplied, it will fall back to a default behavior for converting data
types, but this depends on the implementation.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>WINDOW</literal></term>

View File

@ -0,0 +1,207 @@
<!-- doc/src/sgml/ref/create_transform.sgml -->
<refentry id="SQL-CREATETRANSFORM">
<indexterm zone="sql-createtransform">
<primary>CREATE TRANSFORM</primary>
</indexterm>
<refmeta>
<refentrytitle>CREATE TRANSFORM</refentrytitle>
<manvolnum>7</manvolnum>
<refmiscinfo>SQL - Language Statements</refmiscinfo>
</refmeta>
<refnamediv>
<refname>CREATE TRANSFORM</refname>
<refpurpose>define a new transform</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
CREATE [ OR REPLACE ] TRANSFORM FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable> (
FROM SQL WITH FUNCTION <replaceable>from_sql_function_name</replaceable> (<replaceable>argument_type</replaceable> [, ...]),
TO SQL WITH FUNCTION <replaceable>to_sql_function_name</replaceable> (<replaceable>argument_type</replaceable> [, ...])
);
</synopsis>
</refsynopsisdiv>
<refsect1 id="sql-createtransform-description">
<title>Description</title>
<para>
<command>CREATE TRANSFORM</command> defines a new transform.
<command>CREATE OR REPLACE TRANSFORM</command> will either create a new
transform, or replace an existing definition.
</para>
<para>
A transform specifies how to adapt a data type to a procedural language.
For example, when writing a function in PL/Python using
the <type>hstore</type> type, PL/Python has no prior knowledge how to
present <type>hstore</type> values in the Python environment. Language
implementations usually default to using the text representation, but that
is inconvenient when, for example, an associative array or a list would be
more appropriate.
</para>
<para>
A transform specifies two functions:
<itemizedlist>
<listitem>
<para>
A <quote>from SQL</quote> function that converts the type from the SQL
environment to the language. This function will be invoked on the
arguments of a function written in the language.
</para>
</listitem>
<listitem>
<para>
A <quote>to SQL</quote> function that converts the type from the
language to the SQL environment. This function will be invoked on the
return value of a function written in the language.
</para>
</listitem>
</itemizedlist>
It is not necessary to provide both of these functions. If one is not
specified, the language-specific default behavior will be used if
necessary. (To prevent a transformation in a certain direction from
happening at all, you could also write a transform function that always
errors out.)
</para>
<para>
To be able to create a transform, you must own and
have <literal>USAGE</literal> privilege on the type, have
<literal>USAGE</literal> privilege on the language, and own and
have <literal>EXECUTE</literal> privilege on the from-SQL and to-SQL
functions, if specified.
</para>
</refsect1>
<refsect1>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><replaceable>type_name</replaceable></term>
<listitem>
<para>
The name of the data type of the transform.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>lang_name</replaceable></term>
<listitem>
<para>
The name of the language of the transform.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>from_sql_function_name</replaceable>(<replaceable>argument_type</replaceable> [, ...])</term>
<listitem>
<para>
The name of the function for converting the type from the SQL
environment to the language. It must take one argument of
type <type>internal</type> and return type <type>internal</type>. The
actual argument will be of the type for the transform, and the function
should be coded as if it were. (But it is not allowed to declare an
SQL-level function function returning <type>internal</type> without at
least one argument of type <type>internal</type>.) The actual return
value will be something specific to the language implementation.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>to_sql_function_name</replaceable>(<replaceable>argument_type</replaceable> [, ...])</term>
<listitem>
<para>
The name of the function for converting the type from the language to
the SQL environment. It must take one argument of type
<type>internal</type> and return the type that is the type for the
transform. The actual argument value will be something specific to the
language implementation.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="sql-createtransform-notes">
<title>Notes</title>
<para>
Use <xref linkend="sql-droptransform"> to remove transforms.
</para>
</refsect1>
<refsect1 id="sql-createtransform-examples">
<title>Examples</title>
<para>
To create a transform for type <type>hstore</type> and language
<literal>plpythonu</literal>, first set up the type and the language:
<programlisting>
CREATE TYPE hstore ...;
CREATE LANGUAGE plpythonu ...;
</programlisting>
Then create the necessary functions:
<programlisting>
CREATE FUNCTION hstore_to_plpython(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS ...;
CREATE FUNCTION plpython_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS ...;
</programlisting>
And finally create the transform to connect them all together:
<programlisting>
CREATE TRANSFORM FOR hstore LANGUAGE plpythonu (
FROM SQL WITH FUNCTION hstore_to_plpython(internal),
TO SQL WITH FUNCTION plpython_to_hstore(internal)
);
</programlisting>
In practice, these commands would be wrapped up in extensions.
</para>
<para>
The <filename>contrib</filename> section contains a number of extensions
that provide transforms, which can serve as real-world examples.
</para>
</refsect1>
<refsect1 id="sql-createtransform-compat">
<title>Compatibility</title>
<para>
This form of <command>CREATE TRANSFORM</command> is a
<productname>PostgreSQL</productname> extension. There is a <command>CREATE
TRANSFORM</command> command in the <acronym>SQL</acronym> standard, but it
is for adapting data types to client languages. That usage is not supported
by <productname>PostgreSQL</productname>.
</para>
</refsect1>
<refsect1 id="sql-createtransform-seealso">
<title>See Also</title>
<para>
<xref linkend="sql-createfunction">,
<xref linkend="sql-createlanguage">,
<xref linkend="sql-createtype">,
<xref linkend="sql-droptransform">
</para>
</refsect1>
</refentry>

View File

@ -0,0 +1,123 @@
<!-- doc/src/sgml/ref/drop_transform.sgml -->
<refentry id="SQL-DROPTRANSFORM">
<indexterm zone="sql-droptransform">
<primary>DROP TRANSFORM</primary>
</indexterm>
<refmeta>
<refentrytitle>DROP TRANSFORM</refentrytitle>
<manvolnum>7</manvolnum>
<refmiscinfo>SQL - Language Statements</refmiscinfo>
</refmeta>
<refnamediv>
<refname>DROP TRANSFORM</refname>
<refpurpose>remove a transform</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
DROP TRANSFORM [ IF EXISTS ] FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable>
</synopsis>
</refsynopsisdiv>
<refsect1 id="sql-droptransform-description">
<title>Description</title>
<para>
<command>DROP TRANSFORM</command> removes a previously defined transform.
</para>
<para>
To be able to drop a transform, you must own the type and the language.
These are the same privileges that are required to create a transform.
</para>
</refsect1>
<refsect1>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><literal>IF EXISTS</literal></term>
<listitem>
<para>
Do not throw an error if the transform does not exist. A notice is issued
in this case.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>type_name</replaceable></term>
<listitem>
<para>
The name of the data type of the transform.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>lang_name</replaceable></term>
<listitem>
<para>
The name of the language of the transform.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
Automatically drop objects that depend on the transform.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>RESTRICT</literal></term>
<listitem>
<para>
Refuse to drop the transform if any objects depend on it. This is the
default.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="sql-droptransform-examples">
<title>Examples</title>
<para>
To drop the transform for type <type>hstore</type> and language
<literal>plpythonu</literal>:
<programlisting>
DROP TRANSFORM FOR hstore LANGUAGE plpythonu;
</programlisting></para>
</refsect1>
<refsect1 id="sql-droptransform-compat">
<title>Compatibility</title>
<para>
This form of <command>DROP TRANSFORM</command> is a
<productname>PostgreSQL</productname> extension. See <xref
linkend="sql-createtransform"> for details.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-createtransform"></member>
</simplelist>
</refsect1>
</refentry>

View File

@ -111,6 +111,7 @@
&createTSDictionary;
&createTSParser;
&createTSTemplate;
&createTransform;
&createTrigger;
&createType;
&createUser;
@ -152,6 +153,7 @@
&dropTSDictionary;
&dropTSParser;
&dropTSTemplate;
&dropTransform;
&dropTrigger;
&dropType;
&dropUser;

View File

@ -127,7 +127,7 @@ ifeq ($(PORTNAME), darwin)
else
# loadable module
DLSUFFIX = .so
LINK.shared = $(COMPILER) -bundle -multiply_defined suppress
LINK.shared = $(COMPILER) -bundle -multiply_defined suppress -Wl,-undefined,dynamic_lookup
endif
BUILD.exports = $(AWK) '/^[^\#]/ {printf "_%s\n",$$1}' $< >$@
exports_file = $(SHLIB_EXPORTS:%.txt=%.list)

View File

@ -41,6 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
pg_transform.h \
toasting.h indexing.h \
)

View File

@ -47,6 +47,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
@ -1265,6 +1266,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
case OCLASS_TRANSFORM:
DropTransformById(object->objectId);
break;
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
@ -2373,6 +2378,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
case TransformRelationId:
return OCLASS_TRANSFORM;
}
/* shouldn't get here */

View File

@ -1928,7 +1928,39 @@ GRANT SELECT ON tables TO PUBLIC;
* TRANSFORMS view
*/
-- feature not supported
CREATE VIEW transforms AS
SELECT CAST(current_database() AS sql_identifier) AS udt_catalog,
CAST(nt.nspname AS sql_identifier) AS udt_schema,
CAST(t.typname AS sql_identifier) AS udt_name,
CAST(current_database() AS sql_identifier) AS specific_catalog,
CAST(np.nspname AS sql_identifier) AS specific_schema,
CAST(p.proname || '_' || CAST(p.oid AS text) AS sql_identifier) AS specific_name,
CAST(l.lanname AS sql_identifier) AS group_name,
CAST('FROM SQL' AS character_data) AS transform_type
FROM pg_type t JOIN pg_transform x ON t.oid = x.trftype
JOIN pg_language l ON x.trflang = l.oid
JOIN pg_proc p ON x.trffromsql = p.oid
JOIN pg_namespace nt ON t.typnamespace = nt.oid
JOIN pg_namespace np ON p.pronamespace = np.oid
UNION
SELECT CAST(current_database() AS sql_identifier) AS udt_catalog,
CAST(nt.nspname AS sql_identifier) AS udt_schema,
CAST(t.typname AS sql_identifier) AS udt_name,
CAST(current_database() AS sql_identifier) AS specific_catalog,
CAST(np.nspname AS sql_identifier) AS specific_schema,
CAST(p.proname || '_' || CAST(p.oid AS text) AS sql_identifier) AS specific_name,
CAST(l.lanname AS sql_identifier) AS group_name,
CAST('TO SQL' AS character_data) AS transform_type
FROM pg_type t JOIN pg_transform x ON t.oid = x.trftype
JOIN pg_language l ON x.trflang = l.oid
JOIN pg_proc p ON x.trftosql = p.oid
JOIN pg_namespace nt ON t.typnamespace = nt.oid
JOIN pg_namespace np ON p.pronamespace = np.oid
ORDER BY udt_catalog, udt_schema, udt_name, group_name, transform_type -- some sensible grouping for interactive use
;
/*

View File

@ -45,6 +45,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
@ -334,6 +335,12 @@ static const ObjectPropertyType ObjectProperty[] =
ACL_KIND_TABLESPACE,
true
},
{
TransformRelationId,
TransformOidIndexId,
TRFOID,
InvalidAttrNumber
},
{
TriggerRelationId,
TriggerOidIndexId,
@ -760,6 +767,19 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
address.objectSubId = 0;
}
break;
case OBJECT_TRANSFORM:
{
TypeName *typename = (TypeName *) linitial(objname);
char *langname = (char *) linitial(objargs);
Oid type_id = LookupTypeNameOid(NULL, typename, missing_ok);
Oid lang_id = get_language_oid(langname, missing_ok);
address.classId = TransformRelationId;
address.objectId =
get_transform_oid(type_id, lang_id, missing_ok);
address.objectSubId = 0;
}
break;
case OBJECT_TSPARSER:
address.classId = TSParserRelationId;
address.objectId = get_ts_parser_oid(objname, missing_ok);
@ -2006,6 +2026,15 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
format_type_be(targettypeid))));
}
break;
case OBJECT_TRANSFORM:
{
TypeName *typename = (TypeName *) linitial(objname);
Oid typeid = typenameTypeId(NULL, typename);
if (!pg_type_ownercheck(typeid, roleid))
aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
}
break;
case OBJECT_TABLESPACE:
if (!pg_tablespace_ownercheck(address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
@ -2467,19 +2496,10 @@ getObjectDescription(const ObjectAddress *object)
}
case OCLASS_LANGUAGE:
{
HeapTuple langTup;
appendStringInfo(&buffer, _("language %s"),
get_language_name(object->objectId, false));
break;
langTup = SearchSysCache1(LANGOID,
ObjectIdGetDatum(object->objectId));
if (!HeapTupleIsValid(langTup))
elog(ERROR, "cache lookup failed for language %u",
object->objectId);
appendStringInfo(&buffer, _("language %s"),
NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname));
ReleaseSysCache(langTup);
break;
}
case OCLASS_LARGEOBJECT:
appendStringInfo(&buffer, _("large object %u"),
object->objectId);
@ -2667,6 +2687,27 @@ getObjectDescription(const ObjectAddress *object)
break;
}
case OCLASS_TRANSFORM:
{
HeapTuple trfTup;
Form_pg_transform trfForm;
trfTup = SearchSysCache1(TRFOID,
ObjectIdGetDatum(object->objectId));
if (!HeapTupleIsValid(trfTup))
elog(ERROR, "could not find tuple for transform %u",
object->objectId);
trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
appendStringInfo(&buffer, _("transform for %s language %s"),
format_type_be(trfForm->trftype),
get_language_name(trfForm->trflang, false));
ReleaseSysCache(trfTup);
break;
}
case OCLASS_TRIGGER:
{
Relation trigDesc;

View File

@ -545,6 +545,7 @@ AggregateCreate(const char *aggName,
parameterModes, /* parameterModes */
parameterNames, /* parameterNames */
parameterDefaults, /* parameterDefaults */
PointerGetDatum(NULL), /* trftypes */
PointerGetDatum(NULL), /* proconfig */
1, /* procost */
0); /* prorows */

View File

@ -23,7 +23,9 @@
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
@ -59,7 +61,7 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
/* ----------------------------------------------------------------
* ProcedureCreate
*
* Note: allParameterTypes, parameterModes, parameterNames, and proconfig
* Note: allParameterTypes, parameterModes, parameterNames, trftypes, and proconfig
* are either arrays of the proper types or NULL. We declare them Datum,
* not "ArrayType *", to avoid importing array.h into pg_proc_fn.h.
* ----------------------------------------------------------------
@ -86,6 +88,7 @@ ProcedureCreate(const char *procedureName,
Datum parameterModes,
Datum parameterNames,
List *parameterDefaults,
Datum trftypes,
Datum proconfig,
float4 procost,
float4 prorows)
@ -116,6 +119,7 @@ ProcedureCreate(const char *procedureName,
ObjectAddress myself,
referenced;
int i;
Oid trfid;
/*
* sanity checks
@ -360,6 +364,10 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_proargdefaults - 1] = CStringGetTextDatum(nodeToString(parameterDefaults));
else
nulls[Anum_pg_proc_proargdefaults - 1] = true;
if (trftypes != PointerGetDatum(NULL))
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
@ -624,6 +632,15 @@ ProcedureCreate(const char *procedureName,
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on transform used by return type, if any */
if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
{
referenced.classId = TransformRelationId;
referenced.objectId = trfid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/* dependency on parameter types */
for (i = 0; i < allParamCount; i++)
{
@ -631,6 +648,15 @@ ProcedureCreate(const char *procedureName,
referenced.objectId = allParams[i];
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on transform used by parameter type, if any */
if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
{
referenced.classId = TransformRelationId;
referenced.objectId = trfid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
}
/* dependency on parameter default expressions */
@ -1128,3 +1154,21 @@ fail:
*newcursorpos = newcp;
return false;
}
List *
oid_array_to_list(Datum datum)
{
ArrayType *array = DatumGetArrayTypeP(datum);
Datum *values;
int nelems;
int i;
List *result = NIL;
deconstruct_array(array,
OIDOID,
sizeof(Oid), true, 'i',
&values, NULL, &nelems);
for (i = 0; i < nelems; i++)
result = lappend_oid(result, values[i]);
return result;
}

View File

@ -366,6 +366,14 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
}
}
break;
case OBJECT_TRANSFORM:
if (!type_in_list_does_not_exist_skipping(objname, &msg, &name))
{
msg = gettext_noop("transform for type %s language %s does not exist, skipping");
name = TypeNameToString((TypeName *) linitial(objname));
args = (char *) linitial(objargs);
}
break;
case OBJECT_TRIGGER:
if (!owningrel_does_not_exist_skipping(objname, &msg, &name))
{

View File

@ -98,6 +98,7 @@ static event_trigger_support_data event_trigger_support[] = {
{"SERVER", true},
{"TABLE", true},
{"TABLESPACE", false},
{"TRANSFORM", true},
{"TRIGGER", true},
{"TEXT SEARCH CONFIGURATION", true},
{"TEXT SEARCH DICTIONARY", true},
@ -1090,6 +1091,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_SEQUENCE:
case OBJECT_TABCONSTRAINT:
case OBJECT_TABLE:
case OBJECT_TRANSFORM:
case OBJECT_TRIGGER:
case OBJECT_TSCONFIGURATION:
case OBJECT_TSDICTIONARY:
@ -1137,6 +1139,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_REWRITE:
case OCLASS_TRIGGER:
case OCLASS_SCHEMA:
case OCLASS_TRANSFORM:
case OCLASS_TSPARSER:
case OCLASS_TSDICT:
case OCLASS_TSTEMPLATE:

View File

@ -45,6 +45,7 @@
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "commands/alter.h"
@ -583,6 +584,7 @@ static void
compute_attributes_sql_style(List *options,
List **as,
char **language,
Node **transform,
bool *windowfunc_p,
char *volatility_p,
bool *strict_p,
@ -595,6 +597,7 @@ compute_attributes_sql_style(List *options,
ListCell *option;
DefElem *as_item = NULL;
DefElem *language_item = NULL;
DefElem *transform_item = NULL;
DefElem *windowfunc_item = NULL;
DefElem *volatility_item = NULL;
DefElem *strict_item = NULL;
@ -624,6 +627,14 @@ compute_attributes_sql_style(List *options,
errmsg("conflicting or redundant options")));
language_item = defel;
}
else if (strcmp(defel->defname, "transform") == 0)
{
if (transform_item)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
transform_item = defel;
}
else if (strcmp(defel->defname, "window") == 0)
{
if (windowfunc_item)
@ -671,6 +682,8 @@ compute_attributes_sql_style(List *options,
}
/* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
*windowfunc_p = intVal(windowfunc_item->arg);
if (volatility_item)
@ -807,7 +820,6 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
}
/*
* CreateFunction
* Execute a CREATE FUNCTION utility statement.
@ -822,6 +834,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
char *language;
Oid languageOid;
Oid languageValidator;
Node *transformDefElem = NULL;
char *funcname;
Oid namespaceId;
AclResult aclresult;
@ -831,6 +844,8 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
ArrayType *parameterNames;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
ArrayType *trftypes;
Oid requiredResultType;
bool isWindowFunc,
isStrict,
@ -866,7 +881,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
/* override attributes from explicit list */
compute_attributes_sql_style(stmt->options,
&as_clause, &language,
&as_clause, &language, &transformDefElem,
&isWindowFunc, &volatility,
&isStrict, &security, &isLeakProof,
&proconfig, &procost, &prorows);
@ -915,6 +930,23 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser can define a leakproof function")));
if (transformDefElem)
{
ListCell *lc;
Assert(IsA(transformDefElem, List));
foreach (lc, (List *) transformDefElem)
{
Oid typeid = typenameTypeId(NULL, lfirst(lc));
Oid elt = get_base_element_type(typeid);
typeid = elt ? elt : typeid;
get_transform_oid(typeid, languageOid, false);
trftypes_list = lappend_oid(trftypes_list, typeid);
}
}
/*
* Convert remaining parameters of CREATE to form wanted by
* ProcedureCreate.
@ -958,6 +990,25 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
returnsSet = false;
}
if (list_length(trftypes_list) > 0)
{
ListCell *lc;
Datum *arr;
int i;
arr = palloc(list_length(trftypes_list) * sizeof(Datum));
i = 0;
foreach (lc, trftypes_list)
arr[i++] = ObjectIdGetDatum(lfirst_oid(lc));
trftypes = construct_array(arr, list_length(trftypes_list),
OIDOID, sizeof(Oid), true, 'i');
}
else
{
/* store SQL NULL instead of emtpy array */
trftypes = NULL;
}
compute_attributes_with_style(stmt->withClause, &isStrict, &volatility);
interpret_AS_clause(languageOid, language, funcname, as_clause,
@ -1014,6 +1065,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
PointerGetDatum(parameterModes),
PointerGetDatum(parameterNames),
parameterDefaults,
PointerGetDatum(trftypes),
PointerGetDatum(proconfig),
procost,
prorows);
@ -1653,6 +1705,293 @@ DropCastById(Oid castOid)
heap_close(relation, RowExclusiveLock);
}
static void
check_transform_function(Form_pg_proc procstruct)
{
if (procstruct->provolatile == PROVOLATILE_VOLATILE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("transform function must not be volatile")));
if (procstruct->proisagg)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("transform function must not be an aggregate function")));
if (procstruct->proiswindow)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("transform function must not be a window function")));
if (procstruct->proretset)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("transform function must not return a set")));
if (procstruct->pronargs != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("transform function must take one argument")));
if (procstruct->proargtypes.values[0] != INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("first argument of transform function must be type \"internal\"")));
}
/*
* CREATE TRANSFORM
*/
Oid
CreateTransform(CreateTransformStmt *stmt)
{
Oid typeid;
char typtype;
Oid langid;
Oid fromsqlfuncid;
Oid tosqlfuncid;
AclResult aclresult;
Form_pg_proc procstruct;
Datum values[Natts_pg_transform];
bool nulls[Natts_pg_transform];
bool replaces[Natts_pg_transform];
Oid transformid;
HeapTuple tuple;
HeapTuple newtuple;
Relation relation;
ObjectAddress myself,
referenced;
bool is_replace;
/*
* Get the type
*/
typeid = typenameTypeId(NULL, stmt->type_name);
typtype = get_typtype(typeid);
if (typtype == TYPTYPE_PSEUDO)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("data type %s is a pseudo-type",
TypeNameToString(stmt->type_name))));
if (typtype == TYPTYPE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("data type %s is a domain",
TypeNameToString(stmt->type_name))));
if (!pg_type_ownercheck(typeid, GetUserId()))
aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, typeid);
/*
* Get the language
*/
langid = get_language_oid(stmt->lang, false);
aclresult = pg_language_aclcheck(langid, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_LANGUAGE, stmt->lang);
/*
* Get the functions
*/
if (stmt->fromsql)
{
fromsqlfuncid = LookupFuncNameTypeNames(stmt->fromsql->funcname, stmt->fromsql->funcargs, false);
if (!pg_proc_ownercheck(fromsqlfuncid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
aclresult = pg_proc_aclcheck(fromsqlfuncid, GetUserId(), ACL_EXECUTE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fromsqlfuncid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", fromsqlfuncid);
procstruct = (Form_pg_proc) GETSTRUCT(tuple);
if (procstruct->prorettype != INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("return data type of FROM SQL function must be \"internal\"")));
check_transform_function(procstruct);
ReleaseSysCache(tuple);
}
else
fromsqlfuncid = InvalidOid;
if (stmt->tosql)
{
tosqlfuncid = LookupFuncNameTypeNames(stmt->tosql->funcname, stmt->tosql->funcargs, false);
if (!pg_proc_ownercheck(tosqlfuncid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
aclresult = pg_proc_aclcheck(tosqlfuncid, GetUserId(), ACL_EXECUTE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(tosqlfuncid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", tosqlfuncid);
procstruct = (Form_pg_proc) GETSTRUCT(tuple);
if (procstruct->prorettype != typeid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("return data type of TO SQL function must be the transform data type")));
check_transform_function(procstruct);
ReleaseSysCache(tuple);
}
else
tosqlfuncid = InvalidOid;
/*
* Ready to go
*/
values[Anum_pg_transform_trftype - 1] = ObjectIdGetDatum(typeid);
values[Anum_pg_transform_trflang - 1] = ObjectIdGetDatum(langid);
values[Anum_pg_transform_trffromsql - 1] = ObjectIdGetDatum(fromsqlfuncid);
values[Anum_pg_transform_trftosql - 1] = ObjectIdGetDatum(tosqlfuncid);
MemSet(nulls, false, sizeof(nulls));
relation = heap_open(TransformRelationId, RowExclusiveLock);
tuple = SearchSysCache2(TRFTYPELANG,
ObjectIdGetDatum(typeid),
ObjectIdGetDatum(langid));
if (HeapTupleIsValid(tuple))
{
if (!stmt->replace)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("transform for type %s language %s already exists",
format_type_be(typeid),
stmt->lang)));
MemSet(replaces, false, sizeof(replaces));
replaces[Anum_pg_transform_trffromsql - 1] = true;
replaces[Anum_pg_transform_trftosql - 1] = true;
newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);
simple_heap_update(relation, &newtuple->t_self, newtuple);
transformid = HeapTupleGetOid(tuple);
ReleaseSysCache(tuple);
is_replace = true;
}
else
{
newtuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
transformid = simple_heap_insert(relation, newtuple);
is_replace = false;
}
CatalogUpdateIndexes(relation, newtuple);
if (is_replace)
deleteDependencyRecordsFor(TransformRelationId, transformid, true);
/* make dependency entries */
myself.classId = TransformRelationId;
myself.objectId = transformid;
myself.objectSubId = 0;
/* dependency on language */
referenced.classId = LanguageRelationId;
referenced.objectId = langid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on type */
referenced.classId = TypeRelationId;
referenced.objectId = typeid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependencies on functions */
if (OidIsValid(fromsqlfuncid))
{
referenced.classId = ProcedureRelationId;
referenced.objectId = fromsqlfuncid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
if (OidIsValid(tosqlfuncid))
{
referenced.classId = ProcedureRelationId;
referenced.objectId = tosqlfuncid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/* dependency on extension */
recordDependencyOnCurrentExtension(&myself, is_replace);
/* Post creation hook for new transform */
InvokeObjectPostCreateHook(TransformRelationId, transformid, 0);
heap_freetuple(newtuple);
heap_close(relation, RowExclusiveLock);
return transformid;
}
/*
* get_transform_oid - given type OID and language OID, look up a transform OID
*
* If missing_ok is false, throw an error if the transform is not found. If
* true, just return InvalidOid.
*/
Oid
get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok)
{
Oid oid;
oid = GetSysCacheOid2(TRFTYPELANG,
ObjectIdGetDatum(type_id),
ObjectIdGetDatum(lang_id));
if (!OidIsValid(oid) && !missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("transform for type %s language \"%s\" does not exist",
format_type_be(type_id),
get_language_name(lang_id, false))));
return oid;
}
void
DropTransformById(Oid transformOid)
{
Relation relation;
ScanKeyData scankey;
SysScanDesc scan;
HeapTuple tuple;
relation = heap_open(TransformRelationId, RowExclusiveLock);
ScanKeyInit(&scankey,
ObjectIdAttributeNumber,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(transformOid));
scan = systable_beginscan(relation, TransformOidIndexId, true,
NULL, 1, &scankey);
tuple = systable_getnext(scan);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for transform %u", transformOid);
simple_heap_delete(relation, &tuple->t_self);
systable_endscan(scan);
heap_close(relation, RowExclusiveLock);
}
/*
* Subroutine for ALTER FUNCTION/AGGREGATE SET SCHEMA/RENAME
*

View File

@ -141,6 +141,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
PointerGetDatum(NULL),
NIL,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
1,
0);
handlerOid = tmpAddr.objectId;
@ -179,6 +180,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
PointerGetDatum(NULL),
NIL,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
1,
0);
inlineOid = tmpAddr.objectId;
@ -220,6 +222,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
PointerGetDatum(NULL),
NIL,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
1,
0);
valOid = tmpAddr.objectId;

View File

@ -1616,6 +1616,7 @@ makeRangeConstructors(const char *name, Oid namespace,
PointerGetDatum(NULL), /* parameterModes */
PointerGetDatum(NULL), /* parameterNames */
NIL, /* parameterDefaults */
PointerGetDatum(NULL), /* trftypes */
PointerGetDatum(NULL), /* proconfig */
1.0, /* procost */
0.0); /* prorows */

View File

@ -3625,6 +3625,20 @@ _copyImportForeignSchemaStmt(const ImportForeignSchemaStmt *from)
return newnode;
}
static CreateTransformStmt *
_copyCreateTransformStmt(const CreateTransformStmt *from)
{
CreateTransformStmt *newnode = makeNode(CreateTransformStmt);
COPY_SCALAR_FIELD(replace);
COPY_NODE_FIELD(type_name);
COPY_STRING_FIELD(lang);
COPY_NODE_FIELD(fromsql);
COPY_NODE_FIELD(tosql);
return newnode;
}
static CreateTrigStmt *
_copyCreateTrigStmt(const CreateTrigStmt *from)
{
@ -4568,6 +4582,9 @@ copyObject(const void *from)
case T_ImportForeignSchemaStmt:
retval = _copyImportForeignSchemaStmt(from);
break;
case T_CreateTransformStmt:
retval = _copyCreateTransformStmt(from);
break;
case T_CreateTrigStmt:
retval = _copyCreateTrigStmt(from);
break;

View File

@ -1779,6 +1779,18 @@ _equalImportForeignSchemaStmt(const ImportForeignSchemaStmt *a, const ImportFore
return true;
}
static bool
_equalCreateTransformStmt(const CreateTransformStmt *a, const CreateTransformStmt *b)
{
COMPARE_SCALAR_FIELD(replace);
COMPARE_NODE_FIELD(type_name);
COMPARE_STRING_FIELD(lang);
COMPARE_NODE_FIELD(fromsql);
COMPARE_NODE_FIELD(tosql);
return true;
}
static bool
_equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
{
@ -2991,6 +3003,9 @@ equal(const void *a, const void *b)
case T_ImportForeignSchemaStmt:
retval = _equalImportForeignSchemaStmt(a, b);
break;
case T_CreateTransformStmt:
retval = _equalCreateTransformStmt(a, b);
break;
case T_CreateTrigStmt:
retval = _equalCreateTrigStmt(a, b);
break;

View File

@ -241,12 +241,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
DropPolicyStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropTransformStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
@ -366,6 +367,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_enum_val_list enum_val_list table_func_column_list
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@ -611,12 +613,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP
TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P
TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
TRUNCATE TRUSTED TYPE_P TYPES_P
UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
@ -790,6 +792,7 @@ stmt :
| CreateSeqStmt
| CreateStmt
| CreateTableSpaceStmt
| CreateTransformStmt
| CreateTrigStmt
| CreateEventTrigStmt
| CreateRoleStmt
@ -815,6 +818,7 @@ stmt :
| DropRuleStmt
| DropStmt
| DropTableSpaceStmt
| DropTransformStmt
| DropTrigStmt
| DropRoleStmt
| DropUserStmt
@ -4083,6 +4087,16 @@ AlterExtensionContentsStmt:
n->objname = list_make1(makeString($6));
$$ = (Node *)n;
}
| ALTER EXTENSION name add_drop TRANSFORM FOR Typename LANGUAGE name
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
n->extname = $3;
n->action = $4;
n->objtype = OBJECT_TRANSFORM;
n->objname = list_make1($7);
n->objargs = list_make1($9);
$$ = (Node *)n;
}
| ALTER EXTENSION name add_drop TYPE_P Typename
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
@ -5736,6 +5750,15 @@ CommentStmt:
n->comment = $6;
$$ = (Node *) n;
}
| COMMENT ON TRANSFORM FOR Typename LANGUAGE name IS comment_text
{
CommentStmt *n = makeNode(CommentStmt);
n->objtype = OBJECT_TRANSFORM;
n->objname = list_make1($5);
n->objargs = list_make1($7);
n->comment = $9;
$$ = (Node *) n;
}
| COMMENT ON TRIGGER name ON any_name IS comment_text
{
CommentStmt *n = makeNode(CommentStmt);
@ -7015,6 +7038,10 @@ createfunc_opt_item:
{
$$ = makeDefElem("language", (Node *)makeString($2));
}
| TRANSFORM transform_type_list
{
$$ = makeDefElem("transform", (Node *)$2);
}
| WINDOW
{
$$ = makeDefElem("window", (Node *)makeInteger(TRUE));
@ -7032,6 +7059,11 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
;
opt_definition:
WITH definition { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
@ -7297,6 +7329,56 @@ opt_if_exists: IF_P EXISTS { $$ = TRUE; }
;
/*****************************************************************************
*
* CREATE TRANSFORM / DROP TRANSFORM
*
*****************************************************************************/
CreateTransformStmt: CREATE opt_or_replace TRANSFORM FOR Typename LANGUAGE name '(' transform_element_list ')'
{
CreateTransformStmt *n = makeNode(CreateTransformStmt);
n->replace = $2;
n->type_name = $5;
n->lang = $7;
n->fromsql = linitial($9);
n->tosql = lsecond($9);
$$ = (Node *)n;
}
;
transform_element_list: FROM SQL_P WITH FUNCTION function_with_argtypes ',' TO SQL_P WITH FUNCTION function_with_argtypes
{
$$ = list_make2($5, $11);
}
| TO SQL_P WITH FUNCTION function_with_argtypes ',' FROM SQL_P WITH FUNCTION function_with_argtypes
{
$$ = list_make2($11, $5);
}
| FROM SQL_P WITH FUNCTION function_with_argtypes
{
$$ = list_make2($5, NULL);
}
| TO SQL_P WITH FUNCTION function_with_argtypes
{
$$ = list_make2(NULL, $5);
}
;
DropTransformStmt: DROP TRANSFORM opt_if_exists FOR Typename LANGUAGE name opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_TRANSFORM;
n->objects = list_make1(list_make1($5));
n->arguments = list_make1(list_make1($7));
n->behavior = $8;
n->missing_ok = $3;
$$ = (Node *)n;
}
;
/*****************************************************************************
*
* QUERY:
@ -13460,6 +13542,7 @@ unreserved_keyword:
| SIMPLE
| SKIP
| SNAPSHOT
| SQL_P
| STABLE
| STANDALONE_P
| START
@ -13479,6 +13562,7 @@ unreserved_keyword:
| TEMPORARY
| TEXT_P
| TRANSACTION
| TRANSFORM
| TRIGGER
| TRUNCATE
| TRUSTED

View File

@ -174,6 +174,7 @@ check_xact_readonly(Node *parsetree)
case T_CreateTableAsStmt:
case T_RefreshMatViewStmt:
case T_CreateTableSpaceStmt:
case T_CreateTransformStmt:
case T_CreateTrigStmt:
case T_CompositeTypeStmt:
case T_CreateEnumStmt:
@ -1314,6 +1315,10 @@ ProcessUtilitySlow(Node *parsetree,
DefineOpFamily((CreateOpFamilyStmt *) parsetree);
break;
case T_CreateTransformStmt:
CreateTransform((CreateTransformStmt *) parsetree);
break;
case T_AlterOpFamilyStmt:
AlterOpFamily((AlterOpFamilyStmt *) parsetree);
break;
@ -2004,6 +2009,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_POLICY:
tag = "DROP POLICY";
break;
case OBJECT_TRANSFORM:
tag = "DROP TRANSFORM";
break;
default:
tag = "???";
}
@ -2263,6 +2271,10 @@ CreateCommandTag(Node *parsetree)
}
break;
case T_CreateTransformStmt:
tag = "CREATE TRANSFORM";
break;
case T_CreateTrigStmt:
tag = "CREATE TRIGGER";
break;
@ -2888,6 +2900,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
case T_CreateTransformStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterOpFamilyStmt:
lev = LOGSTMT_DDL;
break;

View File

@ -306,6 +306,7 @@ static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static bool refname_is_unique(char *refname, deparse_namespace *dpns,
@ -1912,9 +1913,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
StringInfoData buf;
StringInfoData dq;
HeapTuple proctup;
HeapTuple langtup;
Form_pg_proc proc;
Form_pg_language lang;
Datum tmp;
bool isnull;
const char *prosrc;
@ -1937,12 +1936,6 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an aggregate function", name)));
/* Need its pg_language tuple for the language name */
langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
if (!HeapTupleIsValid(langtup))
elog(ERROR, "cache lookup failed for language %u", proc->prolang);
lang = (Form_pg_language) GETSTRUCT(langtup);
/*
* We always qualify the function name, to ensure the right function gets
* replaced.
@ -1953,8 +1946,11 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
(void) print_function_arguments(&buf, proctup, false, true);
appendStringInfoString(&buf, ")\n RETURNS ");
print_function_rettype(&buf, proctup);
print_function_trftypes(&buf, proctup);
appendStringInfo(&buf, "\n LANGUAGE %s\n",
quote_identifier(NameStr(lang->lanname)));
quote_identifier(get_language_name(proc->prolang, false)));
/* Emit some miscellaneous options on one line */
oldlen = buf.len;
@ -2074,7 +2070,6 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendStringInfoChar(&buf, '\n');
ReleaseSysCache(langtup);
ReleaseSysCache(proctup);
PG_RETURN_TEXT_P(string_to_text(buf.data));
@ -2350,6 +2345,30 @@ is_input_argument(int nth, const char *argmodes)
|| argmodes[nth] == PROARGMODE_VARIADIC);
}
/*
* Append used transformated types to specified buffer
*/
static void
print_function_trftypes(StringInfo buf, HeapTuple proctup)
{
Oid *trftypes;
int ntypes;
ntypes = get_func_trftypes(proctup, &trftypes);
if (ntypes > 0)
{
int i;
appendStringInfoString(buf, "\n TRANSFORM ");
for (i = 0; i < ntypes; i++)
{
if (i != 0)
appendStringInfoString(buf, ", ");
appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i]));
}
}
}
/*
* Get textual representation of a function argument's default value. The
* second argument of this function is the argument number among all arguments

View File

@ -24,12 +24,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"
@ -977,6 +979,30 @@ get_constraint_name(Oid conoid)
return NULL;
}
/* ---------- LANGUAGE CACHE ---------- */
char *
get_language_name(Oid langoid, bool missing_ok)
{
HeapTuple tp;
tp = SearchSysCache1(LANGOID, ObjectIdGetDatum(langoid));
if (HeapTupleIsValid(tp))
{
Form_pg_language lantup = (Form_pg_language) GETSTRUCT(tp);
char *result;
result = pstrdup(NameStr(lantup->lanname));
ReleaseSysCache(tp);
return result;
}
if (!missing_ok)
elog(ERROR, "cache lookup failed for language %u",
langoid);
return NULL;
}
/* ---------- OPCLASS CACHE ---------- */
/*
@ -1743,6 +1769,51 @@ get_rel_tablespace(Oid relid)
}
/* ---------- TRANSFORM CACHE ---------- */
Oid
get_transform_fromsql(Oid typid, Oid langid, List *trftypes)
{
HeapTuple tup;
if (!list_member_oid(trftypes, typid))
return InvalidOid;
tup = SearchSysCache2(TRFTYPELANG, typid, langid);
if (HeapTupleIsValid(tup))
{
Oid funcid;
funcid = ((Form_pg_transform) GETSTRUCT(tup))->trffromsql;
ReleaseSysCache(tup);
return funcid;
}
else
return InvalidOid;
}
Oid
get_transform_tosql(Oid typid, Oid langid, List *trftypes)
{
HeapTuple tup;
if (!list_member_oid(trftypes, typid))
return InvalidOid;
tup = SearchSysCache2(TRFTYPELANG, typid, langid);
if (HeapTupleIsValid(tup))
{
Oid funcid;
funcid = ((Form_pg_transform) GETSTRUCT(tup))->trftosql;
ReleaseSysCache(tup);
return funcid;
}
else
return InvalidOid;
}
/* ---------- TYPE CACHE ---------- */
/*

View File

@ -56,6 +56,7 @@
#include "catalog/pg_shseclabel.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_config_map.h"
#include "catalog/pg_ts_dict.h"
@ -653,6 +654,28 @@ static const struct cachedesc cacheinfo[] = {
},
4
},
{TransformRelationId, /* TRFOID */
TransformOidIndexId,
1,
{
ObjectIdAttributeNumber,
0,
0,
0,
},
16
},
{TransformRelationId, /* TRFTYPELANG */
TransformTypeLangIndexId,
2,
{
Anum_pg_transform_trftype,
Anum_pg_transform_trflang,
0,
0,
},
16
},
{TSConfigMapRelationId, /* TSCONFIGMAP */
TSConfigMapIndexId,
3,

View File

@ -877,6 +877,50 @@ get_func_arg_info(HeapTuple procTup,
return numargs;
}
/*
* get_func_trftypes
*
* Returns a number of transformated types used by function.
*/
int
get_func_trftypes(HeapTuple procTup,
Oid **p_trftypes)
{
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
Datum protrftypes;
ArrayType *arr;
int nelems;
bool isNull;
protrftypes = SysCacheGetAttr(PROCOID, procTup,
Anum_pg_proc_protrftypes,
&isNull);
if (!isNull)
{
/*
* We expect the arrays to be 1-D arrays of the right types; verify
* that. For the OID and char arrays, we don't need to use
* deconstruct_array() since the array data is just going to look like
* a C array of values.
*/
arr = DatumGetArrayTypeP(protrftypes); /* ensure not toasted */
nelems = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelems < 0 ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "protrftypes is not a 1-D Oid array");
Assert(nelems >= procStruct->pronargs);
*p_trftypes = (Oid *) palloc(nelems * sizeof(Oid));
memcpy(*p_trftypes, ARR_DATA_PTR(arr),
nelems * sizeof(Oid));
return nelems;
}
else
return 0;
}
/*
* get_func_input_arg_names

View File

@ -92,6 +92,7 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
int numRules;
int numProcLangs;
int numCasts;
int numTransforms;
int numOpclasses;
int numOpfamilies;
int numConversions;
@ -201,6 +202,10 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
write_msg(NULL, "reading type casts\n");
getCasts(fout, dopt, &numCasts);
if (g_verbose)
write_msg(NULL, "reading transforms\n");
getTransforms(fout, &numTransforms);
if (g_verbose)
write_msg(NULL, "reading table inheritance information\n");
inhinfo = getInherits(fout, &numInherits);

View File

@ -165,6 +165,7 @@ static void dumpShellType(Archive *fout, DumpOptions *dopt, ShellTypeInfo *stinf
static void dumpProcLang(Archive *fout, DumpOptions *dopt, ProcLangInfo *plang);
static void dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo);
static void dumpCast(Archive *fout, DumpOptions *dopt, CastInfo *cast);
static void dumpTransform(Archive *fout, DumpOptions *dopt, TransformInfo *transform);
static void dumpOpr(Archive *fout, DumpOptions *dopt, OprInfo *oprinfo);
static void dumpOpclass(Archive *fout, DumpOptions *dopt, OpclassInfo *opcinfo);
static void dumpOpfamily(Archive *fout, DumpOptions *dopt, OpfamilyInfo *opfinfo);
@ -6566,6 +6567,110 @@ getCasts(Archive *fout, DumpOptions *dopt, int *numCasts)
return castinfo;
}
static char *
get_language_name(Archive *fout, Oid langid)
{
PQExpBuffer query;
PGresult *res;
char *lanname;
query = createPQExpBuffer();
appendPQExpBuffer(query, "SELECT lanname FROM pg_language WHERE oid = %u", langid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
lanname = pg_strdup(fmtId(PQgetvalue(res, 0, 0)));
destroyPQExpBuffer(query);
PQclear(res);
return lanname;
}
/*
* getTransforms
* get basic information about every transform in the system
*
* numTransforms is set to the number of transforms read in
*/
TransformInfo *
getTransforms(Archive *fout, int *numTransforms)
{
PGresult *res;
int ntups;
int i;
PQExpBuffer query = createPQExpBuffer();
TransformInfo *transforminfo;
int i_tableoid;
int i_oid;
int i_trftype;
int i_trflang;
int i_trffromsql;
int i_trftosql;
/* Transforms didn't exist pre-9.5 */
if (fout->remoteVersion < 90500)
{
*numTransforms = 0;
return NULL;
}
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
appendPQExpBuffer(query, "SELECT tableoid, oid, "
"trftype, trflang, trffromsql::oid, trftosql::oid "
"FROM pg_transform "
"ORDER BY 3,4");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
ntups = PQntuples(res);
*numTransforms = ntups;
transforminfo = (TransformInfo *) pg_malloc(ntups * sizeof(TransformInfo));
i_tableoid = PQfnumber(res, "tableoid");
i_oid = PQfnumber(res, "oid");
i_trftype = PQfnumber(res, "trftype");
i_trflang = PQfnumber(res, "trflang");
i_trffromsql = PQfnumber(res, "trffromsql");
i_trftosql = PQfnumber(res, "trftosql");
for (i = 0; i < ntups; i++)
{
PQExpBufferData namebuf;
TypeInfo *typeInfo;
char *lanname;
transforminfo[i].dobj.objType = DO_TRANSFORM;
transforminfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
transforminfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
AssignDumpId(&transforminfo[i].dobj);
transforminfo[i].trftype = atooid(PQgetvalue(res, i, i_trftype));
transforminfo[i].trflang = atooid(PQgetvalue(res, i, i_trflang));
transforminfo[i].trffromsql = atooid(PQgetvalue(res, i, i_trffromsql));
transforminfo[i].trftosql = atooid(PQgetvalue(res, i, i_trftosql));
/*
* Try to name transform as concatenation of type and language name.
* This is only used for purposes of sorting. If we fail to find
* either, the name will be an empty string.
*/
initPQExpBuffer(&namebuf);
typeInfo = findTypeByOid(transforminfo[i].trftype);
lanname = get_language_name(fout, transforminfo[i].trflang);
if (typeInfo && lanname)
appendPQExpBuffer(&namebuf, "%s %s",
typeInfo->dobj.name, lanname);
transforminfo[i].dobj.name = namebuf.data;
}
PQclear(res);
destroyPQExpBuffer(query);
return transforminfo;
}
/*
* getTableAttrs -
* for each interesting table, read info about its attributes
@ -8182,6 +8287,9 @@ dumpDumpableObject(Archive *fout, DumpOptions *dopt, DumpableObject *dobj)
case DO_CAST:
dumpCast(fout, dopt, (CastInfo *) dobj);
break;
case DO_TRANSFORM:
dumpTransform(fout, dopt, (TransformInfo *) dobj);
break;
case DO_TABLE_DATA:
if (((TableDataInfo *) dobj)->tdtable->relkind == RELKIND_SEQUENCE)
dumpSequenceData(fout, (TableDataInfo *) dobj);
@ -9989,6 +10097,7 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
char *proallargtypes;
char *proargmodes;
char *proargnames;
char *protrftypes;
char *proiswindow;
char *provolatile;
char *proisstrict;
@ -10021,10 +10130,28 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
selectSourceSchema(fout, finfo->dobj.namespace->dobj.name);
/* Fetch function-specific details */
if (fout->remoteVersion >= 90200)
if (fout->remoteVersion >= 90500)
{
/*
* proleakproof was added at v9.2
* protrftypes was added in 9.5
*/
appendPQExpBuffer(query,
"SELECT proretset, prosrc, probin, "
"pg_catalog.pg_get_function_arguments(oid) AS funcargs, "
"pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs, "
"pg_catalog.pg_get_function_result(oid) AS funcresult, "
"array_to_string(protrftypes, ' ') AS protrftypes, "
"proiswindow, provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
finfo->dobj.catId.oid);
}
else if (fout->remoteVersion >= 90200)
{
/*
* proleakproof was added in 9.2
*/
appendPQExpBuffer(query,
"SELECT proretset, prosrc, probin, "
@ -10173,6 +10300,10 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
proargnames = PQgetvalue(res, 0, PQfnumber(res, "proargnames"));
funcargs = funciargs = funcresult = NULL;
}
if (PQfnumber(res, "protrftypes") != -1)
protrftypes = PQgetvalue(res, 0, PQfnumber(res, "protrftypes"));
else
protrftypes = NULL;
proiswindow = PQgetvalue(res, 0, PQfnumber(res, "proiswindow"));
provolatile = PQgetvalue(res, 0, PQfnumber(res, "provolatile"));
proisstrict = PQgetvalue(res, 0, PQfnumber(res, "proisstrict"));
@ -10316,6 +10447,22 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
appendPQExpBuffer(q, "\n LANGUAGE %s", fmtId(lanname));
if (protrftypes != NULL && strcmp(protrftypes, "") != 0)
{
Oid *typeids = palloc(FUNC_MAX_ARGS * sizeof(Oid));
int i;
appendPQExpBufferStr(q, " TRANSFORM ");
parseOidArray(protrftypes, typeids, FUNC_MAX_ARGS);
for (i = 0; typeids[i]; i++)
{
if (i != 0)
appendPQExpBufferStr(q, ", ");
appendPQExpBuffer(q, "FOR TYPE %s",
getFormattedTypeName(fout, typeids[i], zeroAsNone));
}
}
if (proiswindow[0] == 't')
appendPQExpBufferStr(q, " WINDOW");
@ -10539,6 +10686,127 @@ dumpCast(Archive *fout, DumpOptions *dopt, CastInfo *cast)
destroyPQExpBuffer(labelq);
}
/*
* Dump a transform
*/
static void
dumpTransform(Archive *fout, DumpOptions *dopt, TransformInfo *transform)
{
PQExpBuffer defqry;
PQExpBuffer delqry;
PQExpBuffer labelq;
FuncInfo *fromsqlFuncInfo = NULL;
FuncInfo *tosqlFuncInfo = NULL;
char *lanname;
/* Skip if not to be dumped */
if (!transform->dobj.dump || dopt->dataOnly)
return;
/* Cannot dump if we don't have the transform functions' info */
if (OidIsValid(transform->trffromsql))
{
fromsqlFuncInfo = findFuncByOid(transform->trffromsql);
if (fromsqlFuncInfo == NULL)
return;
}
if (OidIsValid(transform->trftosql))
{
tosqlFuncInfo = findFuncByOid(transform->trftosql);
if (tosqlFuncInfo == NULL)
return;
}
/* Make sure we are in proper schema (needed for getFormattedTypeName) */
selectSourceSchema(fout, "pg_catalog");
defqry = createPQExpBuffer();
delqry = createPQExpBuffer();
labelq = createPQExpBuffer();
lanname = get_language_name(fout, transform->trflang);
appendPQExpBuffer(delqry, "DROP TRANSFORM FOR %s LANGUAGE %s;\n",
getFormattedTypeName(fout, transform->trftype, zeroAsNone),
lanname);
appendPQExpBuffer(defqry, "CREATE TRANSFORM FOR %s LANGUAGE %s (",
getFormattedTypeName(fout, transform->trftype, zeroAsNone),
lanname);
if (!transform->trffromsql && !transform->trftosql)
write_msg(NULL, "WARNING: bogus transform definition, at least one of trffromsql and trftosql should be nonzero\n");
if (transform->trffromsql)
{
if (fromsqlFuncInfo)
{
char *fsig = format_function_signature(fout, fromsqlFuncInfo, true);
/*
* Always qualify the function name, in case it is not in
* pg_catalog schema (format_function_signature won't qualify
* it).
*/
appendPQExpBuffer(defqry, "FROM SQL WITH FUNCTION %s.%s",
fmtId(fromsqlFuncInfo->dobj.namespace->dobj.name), fsig);
free(fsig);
}
else
write_msg(NULL, "WARNING: bogus value in pg_transform.trffromsql field\n");
}
if (transform->trftosql)
{
if (transform->trffromsql)
appendPQExpBuffer(defqry, ", ");
if (tosqlFuncInfo)
{
char *fsig = format_function_signature(fout, tosqlFuncInfo, true);
/*
* Always qualify the function name, in case it is not in
* pg_catalog schema (format_function_signature won't qualify
* it).
*/
appendPQExpBuffer(defqry, "TO SQL WITH FUNCTION %s.%s",
fmtId(tosqlFuncInfo->dobj.namespace->dobj.name), fsig);
free(fsig);
}
else
write_msg(NULL, "WARNING: bogus value in pg_transform.trftosql field\n");
}
appendPQExpBuffer(defqry, ");\n");
appendPQExpBuffer(labelq, "TRANSFORM FOR %s LANGUAGE %s",
getFormattedTypeName(fout, transform->trftype, zeroAsNone),
lanname);
if (dopt->binary_upgrade)
binary_upgrade_extension_member(defqry, &transform->dobj, labelq->data);
ArchiveEntry(fout, transform->dobj.catId, transform->dobj.dumpId,
labelq->data,
"pg_catalog", NULL, "",
false, "TRANSFORM", SECTION_PRE_DATA,
defqry->data, delqry->data, NULL,
transform->dobj.dependencies, transform->dobj.nDeps,
NULL, NULL);
/* Dump Transform Comments */
dumpComment(fout, dopt, labelq->data,
NULL, "",
transform->dobj.catId, 0, transform->dobj.dumpId);
free(lanname);
destroyPQExpBuffer(defqry);
destroyPQExpBuffer(delqry);
destroyPQExpBuffer(labelq);
}
/*
* dumpOpr
* write out a single operator definition
@ -15658,6 +15926,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_TSCONFIG:
case DO_FDW:
case DO_FOREIGN_SERVER:
case DO_TRANSFORM:
case DO_BLOB:
/* Pre-data objects: must come before the pre-data boundary */
addObjectDependency(preDataBound, dobj->dumpId);

View File

@ -70,6 +70,7 @@ typedef enum
DO_FDW,
DO_FOREIGN_SERVER,
DO_DEFAULT_ACL,
DO_TRANSFORM,
DO_BLOB,
DO_BLOB_DATA,
DO_PRE_DATA_BOUNDARY,
@ -376,6 +377,15 @@ typedef struct _castInfo
char castmethod;
} CastInfo;
typedef struct _transformInfo
{
DumpableObject dobj;
Oid trftype;
Oid trflang;
Oid trffromsql;
Oid trftosql;
} TransformInfo;
/* InhInfo isn't a DumpableObject, just temporary state */
typedef struct _inhInfo
{
@ -534,6 +544,7 @@ extern RuleInfo *getRules(Archive *fout, int *numRules);
extern void getTriggers(Archive *fout, TableInfo tblinfo[], int numTables);
extern ProcLangInfo *getProcLangs(Archive *fout, int *numProcLangs);
extern CastInfo *getCasts(Archive *fout, DumpOptions *dopt, int *numCasts);
extern TransformInfo *getTransforms(Archive *fout, int *numTransforms);
extern void getTableAttrs(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo, int numTables);
extern bool shouldPrintColumn(DumpOptions *dopt, TableInfo *tbinfo, int colno);
extern TSParserInfo *getTSParsers(Archive *fout, int *numTSParsers);

View File

@ -28,7 +28,7 @@ static const char *modulename = gettext_noop("sorter");
* by OID. (This is a relatively crude hack to provide semi-reasonable
* behavior for old databases without full dependency info.) Note: collations,
* extensions, text search, foreign-data, materialized view, event trigger,
* policies, and default ACL objects can't really happen here, so the rather
* policies, transforms, and default ACL objects can't really happen here, so the rather
* bogus priorities for them don't matter.
*
* NOTE: object-type priorities must match the section assignments made in
@ -67,6 +67,7 @@ static const int oldObjectTypePriority[] =
4, /* DO_FDW */
4, /* DO_FOREIGN_SERVER */
19, /* DO_DEFAULT_ACL */
4, /* DO_TRANSFORM */
9, /* DO_BLOB */
12, /* DO_BLOB_DATA */
10, /* DO_PRE_DATA_BOUNDARY */
@ -116,6 +117,7 @@ static const int newObjectTypePriority[] =
16, /* DO_FDW */
17, /* DO_FOREIGN_SERVER */
31, /* DO_DEFAULT_ACL */
3, /* DO_TRANSFORM */
21, /* DO_BLOB */
24, /* DO_BLOB_DATA */
22, /* DO_PRE_DATA_BOUNDARY */
@ -1400,6 +1402,13 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
((CastInfo *) obj)->casttarget,
obj->dumpId, obj->catId.oid);
return;
case DO_TRANSFORM:
snprintf(buf, bufsize,
"TRANSFORM %u lang %u (ID %d OID %u)",
((TransformInfo *) obj)->trftype,
((TransformInfo *) obj)->trflang,
obj->dumpId, obj->catId.oid);
return;
case DO_TABLE_DATA:
snprintf(buf, bufsize,
"TABLE DATA %s (ID %d OID %u)",

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201504171
#define CATALOG_VERSION_NO 201504261
#endif

View File

@ -148,6 +148,7 @@ typedef enum ObjectClass
OCLASS_EXTENSION, /* pg_extension */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_TRANSFORM, /* pg_transform */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;

View File

@ -219,6 +219,11 @@ DECLARE_UNIQUE_INDEX(pg_tablespace_oid_index, 2697, on pg_tablespace using btree
DECLARE_UNIQUE_INDEX(pg_tablespace_spcname_index, 2698, on pg_tablespace using btree(spcname name_ops));
#define TablespaceNameIndexId 2698
DECLARE_UNIQUE_INDEX(pg_transform_oid_index, 3574, on pg_transform using btree(oid oid_ops));
#define TransformOidIndexId 3574
DECLARE_UNIQUE_INDEX(pg_transform_type_lang_index, 3575, on pg_transform using btree(trftype oid_ops, trflang oid_ops));
#define TransformTypeLangIndexId 3575
DECLARE_INDEX(pg_trigger_tgconstraint_index, 2699, on pg_trigger using btree(tgconstraint oid_ops));
#define TriggerConstraintIndexId 2699
DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using btree(tgrelid oid_ops, tgname name_ops));

View File

@ -144,7 +144,7 @@ DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t
DESCR("");
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f t n 3 1 _null_ _null_ ));
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
DESCR("");

File diff suppressed because it is too large Load Diff

View File

@ -38,10 +38,13 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
Datum parameterModes,
Datum parameterNames,
List *parameterDefaults,
Datum trftypes,
Datum proconfig,
float4 procost,
float4 prorows);
extern bool function_parse_error_transpose(const char *prosrc);
extern List *oid_array_to_list(Datum datum);
#endif /* PG_PROC_FN_H */

View File

@ -0,0 +1,47 @@
/*-------------------------------------------------------------------------
*
* pg_transform.h
*
* Copyright (c) 2012-2015, 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 3576
CATALOG(pg_transform,3576)
{
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 */

View File

@ -50,10 +50,13 @@ extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
extern ObjectAddress AlterFunction(AlterFunctionStmt *stmt);
extern ObjectAddress CreateCast(CreateCastStmt *stmt);
extern void DropCastById(Oid castOid);
extern Oid CreateTransform(CreateTransformStmt *stmt);
extern void DropTransformById(Oid transformOid);
extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
oidvector *proargtypes, Oid nspOid);
extern void ExecuteDoStmt(DoStmt *stmt);
extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok);
extern Oid get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok);
extern void interpret_function_parameter_list(List *parameters,
Oid languageOid,
bool is_aggregate,

View File

@ -176,6 +176,7 @@ extern int get_func_arg_info(HeapTuple procTup,
extern int get_func_input_arg_names(Datum proargnames, Datum proargmodes,
char ***arg_names);
extern int get_func_trftypes(HeapTuple procTup, Oid **p_trftypes);
extern char *get_func_result_name(Oid functionId);
extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,

View File

@ -371,6 +371,7 @@ typedef enum NodeTag
T_AlterSystemStmt,
T_CreatePolicyStmt,
T_AlterPolicyStmt,
T_CreateTransformStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)

View File

@ -1265,6 +1265,7 @@ typedef enum ObjectType
OBJECT_TABCONSTRAINT,
OBJECT_TABLE,
OBJECT_TABLESPACE,
OBJECT_TRANSFORM,
OBJECT_TRIGGER,
OBJECT_TSCONFIGURATION,
OBJECT_TSDICTIONARY,
@ -2789,6 +2790,20 @@ typedef struct CreateCastStmt
bool inout;
} CreateCastStmt;
/* ----------------------
* CREATE TRANSFORM Statement
* ----------------------
*/
typedef struct CreateTransformStmt
{
NodeTag type;
bool replace;
TypeName *type_name;
char *lang;
FuncWithArgs *fromsql;
FuncWithArgs *tosql;
} CreateTransformStmt;
/* ----------------------
* PREPARE Statement
* ----------------------

View File

@ -350,6 +350,7 @@ PG_KEYWORD("skip", SKIP, 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)
@ -377,6 +378,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)

View File

@ -70,6 +70,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);
@ -101,6 +102,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, List *trftypes);
extern Oid get_transform_tosql(Oid typid, Oid langid, List *trftypes);
extern bool get_typisdefined(Oid typid);
extern int16 get_typlen(Oid typid);
extern bool get_typbyval(Oid typid);

View File

@ -80,6 +80,8 @@ enum SysCacheIdentifier
RULERELNAME,
STATRELATTINH,
TABLESPACEOID,
TRFOID,
TRFTYPELANG,
TSCONFIGMAP,
TSCONFIGNAMENSP,
TSCONFIGOID,

View File

@ -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

View File

@ -1005,7 +1005,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;
@ -1017,7 +1017,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;
@ -1494,7 +1494,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"); }

View File

@ -63,8 +63,6 @@ static const ScanKeyword ECPGScanKeywords[] = {
{"section", SQL_SECTION, 0},
{"short", SQL_SHORT, 0},
{"signed", SQL_SIGNED, 0},
{"sql", SQL_SQL, 0}, /* strange thing, used for into sql descriptor
* MYDESC; */
{"sqlerror", SQL_SQLERROR, 0},
{"sqlprint", SQL_SQLPRINT, 0},
{"sqlwarning", SQL_SQLWARNING, 0},

View File

@ -99,15 +99,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

View File

@ -20,6 +20,7 @@
#include "access/xact.h"
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
#include "catalog/pg_type.h"
#include "commands/event_trigger.h"
#include "commands/trigger.h"
@ -110,6 +111,8 @@ typedef struct plperl_proc_desc
SV *reference; /* CODE reference for Perl sub */
plperl_interp_desc *interp; /* interpreter it's created in */
bool fn_readonly; /* is function readonly (not volatile)? */
Oid lang_oid;
List *trftypes;
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 +213,7 @@ typedef struct plperl_array_info
bool *nulls;
int *nelems;
FmgrInfo proc;
FmgrInfo transform_proc;
} plperl_array_info;
/**********************************************************************
@ -1272,6 +1276,7 @@ plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
bool *isnull)
{
FmgrInfo tmp;
Oid funcid;
/* we might recurse */
check_stack_depth();
@ -1295,6 +1300,8 @@ plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
/* must call typinput in case it wants to reject NULL */
return InputFunctionCall(finfo, NULL, typioparam, typmod);
}
else if ((funcid = get_transform_tosql(typid, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
return OidFunctionCall1(funcid, PointerGetDatum(sv));
else if (SvROK(sv))
{
/* handle references */
@ -1407,6 +1414,7 @@ plperl_ref_from_pg_array(Datum arg, Oid typid)
typdelim;
Oid typioparam;
Oid typoutputfunc;
Oid transform_funcid;
int i,
nitems,
*dims;
@ -1414,14 +1422,17 @@ plperl_ref_from_pg_array(Datum arg, Oid typid)
SV *av;
HV *hv;
info = palloc(sizeof(plperl_array_info));
info = palloc0(sizeof(plperl_array_info));
/* get element type information, including output conversion function */
get_type_io_data(elementtype, IOFunc_output,
&typlen, &typbyval, &typalign,
&typdelim, &typioparam, &typoutputfunc);
perm_fmgr_info(typoutputfunc, &info->proc);
if ((transform_funcid = get_transform_fromsql(elementtype, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
perm_fmgr_info(transform_funcid, &info->transform_proc);
else
perm_fmgr_info(typoutputfunc, &info->proc);
info->elem_is_rowtype = type_is_rowtype(elementtype);
@ -1502,8 +1513,10 @@ make_array_ref(plperl_array_info *info, int first, int last)
{
Datum itemvalue = info->elements[i];
/* Handle composite type elements */
if (info->elem_is_rowtype)
if (info->transform_proc.fn_oid)
av_push(result, (SV *) DatumGetPointer(FunctionCall1(&info->transform_proc, itemvalue)));
else if (info->elem_is_rowtype)
/* Handle composite type elements */
av_push(result, plperl_hash_from_datum(itemvalue));
else
{
@ -1812,6 +1825,8 @@ plperl_inline_handler(PG_FUNCTION_ARGS)
desc.proname = "inline_code_block";
desc.fn_readonly = false;
desc.lang_oid = codeblock->langOid;
desc.trftypes = NIL;
desc.lanpltrusted = codeblock->langIsTrusted;
desc.fn_retistuple = false;
@ -2076,6 +2091,8 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
SV *retval;
int i;
int count;
Oid *argtypes = NULL;
int nargs = 0;
ENTER;
SAVETMPS;
@ -2083,6 +2100,9 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
PUSHMARK(SP);
EXTEND(sp, desc->nargs);
if (fcinfo->flinfo->fn_oid)
get_func_signature(fcinfo->flinfo->fn_oid, &argtypes, &nargs);
for (i = 0; i < desc->nargs; i++)
{
if (fcinfo->argnull[i])
@ -2096,9 +2116,12 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
else
{
SV *sv;
Oid funcid;
if (OidIsValid(desc->arg_arraytype[i]))
sv = plperl_ref_from_pg_array(fcinfo->arg[i], desc->arg_arraytype[i]);
else if ((funcid = get_transform_fromsql(argtypes[i], current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, fcinfo->arg[i]));
else
{
char *tmp;
@ -2569,6 +2592,7 @@ free_plperl_function(plperl_proc_desc *prodesc)
/* (FmgrInfo subsidiary info will get leaked ...) */
if (prodesc->proname)
free(prodesc->proname);
list_free(prodesc->trftypes);
free(prodesc);
}
@ -2631,6 +2655,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
HeapTuple typeTup;
Form_pg_language langStruct;
Form_pg_type typeStruct;
Datum protrftypes_datum;
Datum prosrcdatum;
bool isnull;
char *proc_source;
@ -2661,6 +2686,16 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
prodesc->fn_readonly =
(procStruct->provolatile != PROVOLATILE_VOLATILE);
{
MemoryContext oldcxt;
protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
Anum_pg_proc_protrftypes, &isnull);
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
prodesc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
MemoryContextSwitchTo(oldcxt);
}
/************************************************************
* Lookup the pg_language tuple by Oid
************************************************************/
@ -2673,6 +2708,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
procStruct->prolang);
}
langStruct = (Form_pg_language) GETSTRUCT(langTup);
prodesc->lang_oid = HeapTupleGetOid(langTup);
prodesc->lanpltrusted = langStruct->lanpltrusted;
ReleaseSysCache(langTup);
@ -2906,9 +2942,12 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
else
{
SV *sv;
Oid funcid;
if (OidIsValid(get_base_element_type(tupdesc->attrs[i]->atttypid)))
sv = plperl_ref_from_pg_array(attr, tupdesc->attrs[i]->atttypid);
else if ((funcid = get_transform_fromsql(tupdesc->attrs[i]->atttypid, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, attr));
else
{
char *outputstr;

View File

@ -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
*

View File

@ -123,54 +123,22 @@ all: all-lib
install: all install-lib install-data
installdirs: installdirs-lib
$(MKDIR_P) '$(DESTDIR)$(datadir)/extension'
$(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)'
uninstall: uninstall-lib uninstall-data
install-data: installdirs
$(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
$(INSTALL_DATA) $(srcdir)/plpython.h $(srcdir)/plpy_util.h '$(DESTDIR)$(includedir_server)'
uninstall-data:
rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA)))
rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, plpython.h plpy_util.h)
.PHONY: install-data uninstall-data
ifeq ($(python_majorversion),3)
# Adjust regression tests for Python 3 compatibility
#
# Mention those regression test files that need to be mangled in the
# variable REGRESS_PLPYTHON3_MANGLE. They will be copied to a
# subdirectory python3/ and have their Python syntax and other bits
# adjusted to work with Python 3.
# Note that the order of the tests needs to be preserved in this
# expression.
REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_MANGLE)),python3/$(test),$(test)))
.PHONY: pgregress-python3-mangle
pgregress-python3-mangle:
$(MKDIR_P) sql/python3 expected/python3 results/python3
for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \
sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \
-e "s/<type 'exceptions\.\([[:alpha:]]*\)'>/<class '\1'>/g" \
-e "s/<type 'long'>/<class 'int'>/g" \
-e "s/\([0-9][0-9]*\)L/\1/g" \
-e 's/\([ [{]\)u"/\1"/g' \
-e "s/\([ [{]\)u'/\1'/g" \
-e "s/def next/def __next__/g" \
-e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \
-e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \
-e "s/EXTENSION plpythonu/EXTENSION plpython3u/g" \
-e "s/EXTENSION plpython2u/EXTENSION plpython3u/g" \
$$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \
done
check installcheck: pgregress-python3-mangle
pg_regress_clean_files += sql/python3/ expected/python3/ results/python3/
endif # Python 3
include $(srcdir)/regress-python3-mangle.mk
check: all submake

View File

@ -278,6 +278,7 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
MemSet(&proc, 0, sizeof(PLyProcedure));
proc.pyname = PLy_strdup("__plpython_inline_block");
proc.langid = codeblock->langOid;
proc.result.out.d.typoid = VOIDOID;
/*

View File

@ -10,9 +10,12 @@
#include "access/transam.h"
#include "funcapi.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "plpython.h"
@ -26,6 +29,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);
@ -41,6 +45,29 @@ init_procedure_caches(void)
hash_ctl.entrysize = sizeof(PLyProcedureEntry);
PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
HASH_ELEM | HASH_BLOBS);
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");
}
}
/*
@ -165,6 +192,16 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
for (i = 0; i < FUNC_MAX_ARGS; i++)
PLy_typeinfo_init(&proc->args[i]);
proc->nargs = 0;
proc->langid = procStruct->prolang;
{
MemoryContext oldcxt;
Datum protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
Anum_pg_proc_protrftypes, &isnull);
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
MemoryContextSwitchTo(oldcxt);
}
proc->code = proc->statics = NULL;
proc->globals = NULL;
proc->is_setof = procStruct->proretset;
@ -219,7 +256,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
else
{
/* do the real work */
PLy_output_datum_func(&proc->result, rvTypeTup);
PLy_output_datum_func(&proc->result, rvTypeTup, proc->langid, proc->trftypes);
}
ReleaseSysCache(rvTypeTup);
@ -293,7 +330,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
default:
PLy_input_datum_func(&(proc->args[pos]),
types[i],
argTypeTup);
argTypeTup,
proc->langid,
proc->trftypes);
break;
}

View File

@ -27,6 +27,8 @@ typedef struct PLyProcedure
char **argnames; /* Argument names */
PLyTypeInfo args[FUNC_MAX_ARGS];
int nargs;
Oid langid; /* OID of plpython pg_language entry */
List *trftypes; /* OID list of transform types */
PyObject *code; /* compiled procedure code */
PyObject *statics; /* data saved across calls, local scope */
PyObject *globals; /* data saved across calls, global scope */

View File

@ -76,6 +76,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PG_TRY();
{
int i;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
/*
* the other loop might throw an exception, if PLyTypeInfo member
@ -128,7 +129,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
optr = NULL;
plan->types[i] = typeId;
PLy_output_datum_func(&plan->args[i], typeTup);
PLy_output_datum_func(&plan->args[i], typeTup, exec_ctx->curr_proc->langid, exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
}

View File

@ -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, List *trftypes);
static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *trftypes);
/* conversion from Datums to Python objects */
static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
@ -43,6 +43,7 @@ static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d);
static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d);
static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d);
static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
/* conversion from Python objects to Datums */
@ -50,6 +51,7 @@ static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
/* conversion from Python objects to composite Datums (used by triggers and SRFs) */
@ -102,27 +104,28 @@ PLy_typeinfo_dealloc(PLyTypeInfo *arg)
* PostgreSQL, and vice versa.
*/
void
PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup)
PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes)
{
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, trftypes);
}
void
PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup)
PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes)
{
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, trftypes);
}
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,9 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
PLy_input_datum_func2(&(arg->in.r.atts[i]),
desc->attrs[i]->atttypid,
typeTup);
typeTup,
exec_ctx->curr_proc->langid,
exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
}
@ -191,6 +196,7 @@ void
PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
int i;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
if (arg->is_rowtype == 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
@ -243,7 +249,9 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
elog(ERROR, "cache lookup failed for type %u",
desc->attrs[i]->atttypid);
PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup);
PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup,
exec_ctx->curr_proc->langid,
exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
}
@ -362,10 +370,12 @@ PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
}
static void
PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *trftypes)
{
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 +384,24 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
arg->typbyval = typeStruct->typbyval;
element_type = get_base_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, trftypes)))
{
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 +414,6 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
break;
}
/* Composite types need their own input routine, though */
if (typeStruct->typtype == TYPTYPE_COMPOSITE)
{
arg->func = PLyObject_ToComposite;
}
if (element_type)
{
char dummy_delim;
@ -408,6 +424,7 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
arg->elm = PLy_malloc0(sizeof(*arg->elm));
arg->elm->func = arg->func;
arg->elm->typtransform = arg->typtransform;
arg->func = PLySequence_ToArray;
arg->elm->typoid = element_type;
@ -420,12 +437,12 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
}
static void
PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes)
{
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
/* It's safe to handle domains of array types as its base array type. */
Oid element_type = get_base_element_type(typeOid);
Oid element_type;
Oid base_type;
Oid funcid;
/* Get the type's conversion information */
perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
@ -437,7 +454,17 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
arg->typalign = typeStruct->typalign;
/* Determine which kind of Python object we will convert to */
switch (getBaseType(element_type ? element_type : typeOid))
element_type = get_base_element_type(typeOid);
base_type = getBaseType(element_type ? element_type : typeOid);
if ((funcid = get_transform_fromsql(base_type, langid, trftypes)))
{
arg->func = PLyObject_FromTransform;
perm_fmgr_info(funcid, &arg->typtransform);
}
else
switch (base_type)
{
case BOOLOID:
arg->func = PLyBool_FromBool;
@ -478,6 +505,7 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
arg->elm = PLy_malloc0(sizeof(*arg->elm));
arg->elm->func = arg->func;
arg->elm->typtransform = arg->typtransform;
arg->func = PLyList_FromArray;
arg->elm->typoid = element_type;
arg->elm->typmod = -1;
@ -596,6 +624,12 @@ PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
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)
{
@ -747,16 +781,15 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
/*
* Generic conversion function: Convert PyObject to cstring and
* cstring into PostgreSQL type.
* Convert Python object to C string in server encoding.
*/
static Datum
PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
char *
PLyObject_AsString(PyObject *plrv)
{
PyObject *volatile plrv_bo = NULL;
Datum rv;
Assert(plrv != Py_None);
PyObject *plrv_bo;
char *plrv_sc;
size_t plen;
size_t slen;
if (PyUnicode_Check(plrv))
plrv_bo = PLyUnicode_Bytes(plrv);
@ -786,36 +819,47 @@ PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
if (!plrv_bo)
PLy_elog(ERROR, "could not create string representation of Python object");
PG_TRY();
{
char *plrv_sc = PyBytes_AsString(plrv_bo);
size_t plen = PyBytes_Size(plrv_bo);
size_t slen = strlen(plrv_sc);
if (slen < plen)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
else if (slen > plen)
elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
pg_verifymbstr(plrv_sc, slen, false);
rv = InputFunctionCall(&arg->typfunc,
plrv_sc,
arg->typioparam,
typmod);
}
PG_CATCH();
{
Py_XDECREF(plrv_bo);
PG_RE_THROW();
}
PG_END_TRY();
plrv_sc = pstrdup(PyBytes_AsString(plrv_bo));
plen = PyBytes_Size(plrv_bo);
slen = strlen(plrv_sc);
Py_XDECREF(plrv_bo);
return rv;
if (slen < plen)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
else if (slen > plen)
elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
pg_verifymbstr(plrv_sc, slen, false);
return plrv_sc;
}
/*
* Generic conversion function: Convert PyObject to cstring and
* cstring into PostgreSQL type.
*/
static Datum
PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
{
Assert(plrv != Py_None);
return InputFunctionCall(&arg->typfunc,
PLyObject_AsString(plrv),
arg->typioparam,
typmod);
}
static Datum
PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
{
return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv));
}
static Datum
PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
{
@ -869,12 +913,15 @@ static Datum
PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
{
HeapTuple typeTup;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid));
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid);
PLy_output_datum_func2(&info->out.d, typeTup);
PLy_output_datum_func2(&info->out.d, typeTup,
exec_ctx->curr_proc->langid,
exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);

View File

@ -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, List *trftypes);
extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes);
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 */

View File

@ -142,19 +142,30 @@ PLyUnicode_AsString(PyObject *unicode)
* unicode object. Reference ownership is passed to the caller.
*/
PyObject *
PLyUnicode_FromString(const char *s)
PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size)
{
char *utf8string;
PyObject *o;
utf8string = pg_server_to_any(s, strlen(s), PG_UTF8);
utf8string = pg_server_to_any(s, size, 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 */

View File

@ -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 */

View File

@ -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
/*

View File

@ -0,0 +1,35 @@
ifeq ($(python_majorversion),3)
# Adjust regression tests for Python 3 compatibility
#
# Mention those regression test files that need to be mangled in the
# variable REGRESS_PLPYTHON3_MANGLE. They will be copied to a
# subdirectory python3/ and have their Python syntax and other bits
# adjusted to work with Python 3.
# Note that the order of the tests needs to be preserved in this
# expression.
REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_MANGLE)),python3/$(test),$(test)))
.PHONY: pgregress-python3-mangle
pgregress-python3-mangle:
$(MKDIR_P) sql/python3 expected/python3 results/python3
for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \
sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \
-e "s/<type 'exceptions\.\([[:alpha:]]*\)'>/<class '\1'>/g" \
-e "s/<type 'long'>/<class 'int'>/g" \
-e "s/\([0-9][0-9]*\)L/\1/g" \
-e 's/\([ [{]\)u"/\1"/g' \
-e "s/\([ [{]\)u'/\1'/g" \
-e "s/def next/def __next__/g" \
-e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \
-e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \
-e "s/EXTENSION \([^ ]*_\)*plpythonu/EXTENSION \1plpython3u/g" \
-e "s/EXTENSION \([^ ]*_\)*plpython2u/EXTENSION \1plpython3u/g" \
$$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \
done
check installcheck: pgregress-python3-mangle
pg_regress_clean_files += sql/python3/ expected/python3/ results/python3/
endif # Python 3

Some files were not shown because too many files have changed in this diff Show More