Commit cac76582 authored by Peter Eisentraut's avatar Peter Eisentraut

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
parent f320cbb6
......@@ -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)
......
# Generated subdirectories
/log/
/results/
/tmp_check/
# 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
-- 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;
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;
-- 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)
);
#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);
}
# hstore_plperl extension
comment = 'transform between hstore and plperl'
default_version = '1.0'
module_pathname = '$libdir/hstore_plperl'
relocatable = true
requires = 'hstore,plperl'
-- 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)
);
# hstore_plperlu extension
comment = 'transform between hstore and plperlu'
default_version = '1.0'
module_pathname = '$libdir/hstore_plperl'
relocatable = true
requires = 'hstore,plperlu'
-- 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;
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;
# Generated subdirectories
/expected/python3/
/log/
/results/
/sql/python3/
/tmp_check/
# 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
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)
#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);
}
-- 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';
# hstore_plpython2u extension
comment = 'transform between hstore and plpython2u'
default_version = '1.0'
module_pathname = '$libdir/hstore_plpython2'
relocatable = true
requires = 'hstore,plpython2u'
-- 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';
# hstore_plpython3u extension
comment = 'transform between hstore and plpython3u'
default_version = '1.0'
module_pathname = '$libdir/hstore_plpython3'
relocatable = true
requires = 'hstore,plpython3u'
-- 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';
# hstore_plpythonu extension
comment = 'transform between hstore and plpythonu'
default_version = '1.0'
module_pathname = '$libdir/hstore_plpython2'
relocatable = true
requires = 'hstore,plpythonu'
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;
# Generated subdirectories
/expected/python3/
/log/
/results/
/sql/python3/
/tmp_check/
# 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
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"
#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);
}
-- 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)
);
# ltree_plpython2u extension
comment = 'transform between ltree and plpython2u'
default_version = '1.0'
module_pathname = '$libdir/ltree_plpython2'
relocatable = true
requires = 'ltree,plpython2u'
-- 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)
);
# ltree_plpython3u extension
comment = 'transform between ltree and plpython3u'
default_version = '1.0'
module_pathname = '$libdir/ltree_plpython3'
relocatable = true
requires = 'ltree,plpython3u'
-- 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)
);
# ltree_plpythonu extension
comment = 'transform between ltree and plpythonu'
default_version = '1.0'
module_pathname = '$libdir/ltree_plpython2'
relocatable = true
requires = 'ltree,plpythonu'
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();
......@@ -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>
......
......@@ -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>
......
......@@ -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>
......
......@@ -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>
......
......@@ -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">
......
......@@ -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>
......
......@@ -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';
......
......@@ -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>
......
<!-- 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>
<!-- 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>
......@@ -111,6 +111,7 @@
&createTSDictionary;
&createTSParser;
&createTSTemplate;
&createTransform;
&createTrigger;
&createType;
&createUser;
......@@ -152,6 +153,7 @@
&dropTSDictionary;
&dropTSParser;
&dropTSTemplate;
&dropTransform;
&dropTrigger;
&dropType;
&dropUser;
......
......@@ -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)
......
......@@ -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 \
)
......
......@@ -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 */
......
......@@ -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
;
/*
......
......@@ -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;
......
......@@ -545,6 +545,7 @@ AggregateCreate(const char *aggName,
parameterModes, /* parameterModes */
parameterNames, /* parameterNames */
parameterDefaults, /* parameterDefaults */
PointerGetDatum(NULL), /* trftypes */
PointerGetDatum(NULL), /* proconfig */
1, /* procost */
0); /* prorows */
......
......@@ -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;
}
......@@ -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))
{
......
......@@ -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:
......
This diff is collapsed.
......@@ -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;
......
......@@ -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 */
......
......@@ -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;
......
......@@ -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;
......
......@@ -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
......
......@@ -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;
......
......@@ -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
......
......@@ -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 ---------- */
/*
......
......@@ -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,
......
......@@ -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
......
......@@ -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);
......
This diff is collapsed.
......@@ -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);
......
......@@ -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)",
......
......@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201504171
#define CATALOG_VERSION_NO 201504261
#endif
......@@ -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;
......
......@@ -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));
......
......@@ -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("");
......
This diff is collapsed.
......@@ -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 */
/*-------------------------------------------------------------------------
*
* 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 */
......@@ -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,
......
......@@ -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,
......
......@@ -371,6 +371,7 @@ typedef enum NodeTag
T_AlterSystemStmt,
T_CreatePolicyStmt,
T_AlterPolicyStmt,
T_CreateTransformStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
......
......@@ -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
* ----------------------
......
......@@ -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)
......
......@@ -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);
......
......@@ -80,6 +80,8 @@ enum SysCacheIdentifier
RULERELNAME,
STATRELATTINH,
TABLESPACEOID,
TRFOID,
TRFTYPELANG,
TSCONFIGMAP,
TSCONFIGNAMENSP,
TSCONFIGOID,
......
......@@ -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
......
......@@ -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"); }
......
......@@ -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},
......
......@@ -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
......
......@@ -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;
......
#ifndef PL_PERL_HELPERS_H
#define PL_PERL_HELPERS_H
#include "mb/pg_wchar.h"
/*
* convert from utf8 to database encoding
*
......
......@@ -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
......
......@@ -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;
/*
......
......@@ -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;
}
......
......@@ -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 */
......
......@@ -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);
}
......
This diff is collapsed.
......@@ -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 */
......@@ -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);
o = PyUnicode_FromString(utf8string);
utf8string = pg_server_to_any(s, size, PG_UTF8);
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 */
......@@ -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 */
......@@ -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
/*
......
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
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment