Add transforms feature
This provides a mechanism for specifying conversions between SQL data types and procedural languages. As examples, there are transforms for hstore and ltree for PL/Perl and PL/Python. reviews by Pavel Stěhule and Andres Freund
This commit is contained in:
parent
f320cbb615
commit
cac7658205
@ -71,6 +71,18 @@ else
|
||||
ALWAYS_SUBDIRS += sepgsql
|
||||
endif
|
||||
|
||||
ifeq ($(with_perl),yes)
|
||||
SUBDIRS += hstore_plperl
|
||||
else
|
||||
ALWAYS_SUBDIRS += hstore_plperl
|
||||
endif
|
||||
|
||||
ifeq ($(with_python),yes)
|
||||
SUBDIRS += hstore_plpython ltree_plpython
|
||||
else
|
||||
ALWAYS_SUBDIRS += hstore_plpython ltree_plpython
|
||||
endif
|
||||
|
||||
# Missing:
|
||||
# start-scripts \ (does not have a makefile)
|
||||
|
||||
|
4
contrib/hstore_plperl/.gitignore
vendored
Normal file
4
contrib/hstore_plperl/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Generated subdirectories
|
||||
/log/
|
||||
/results/
|
||||
/tmp_check/
|
24
contrib/hstore_plperl/Makefile
Normal file
24
contrib/hstore_plperl/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
# contrib/hstore_plperl/Makefile
|
||||
|
||||
MODULE_big = hstore_plperl
|
||||
OBJS = hstore_plperl.o
|
||||
|
||||
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl -I$(perl_archlibexp)/CORE -I$(top_srcdir)/contrib/hstore
|
||||
|
||||
EXTENSION = hstore_plperl hstore_plperlu
|
||||
DATA = hstore_plperl--1.0.sql hstore_plperlu--1.0.sql
|
||||
|
||||
REGRESS = hstore_plperl create_transform
|
||||
REGRESS_OPTS = --load-extension=hstore --load-extension=plperl --load-extension=plperlu
|
||||
EXTRA_INSTALL = contrib/hstore
|
||||
|
||||
ifdef USE_PGXS
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
else
|
||||
subdir = contrib/hstore_plperl
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
include $(top_srcdir)/contrib/contrib-global.mk
|
||||
endif
|
74
contrib/hstore_plperl/expected/create_transform.out
Normal file
74
contrib/hstore_plperl/expected/create_transform.out
Normal file
@ -0,0 +1,74 @@
|
||||
-- general regression test for transforms
|
||||
DROP EXTENSION IF EXISTS hstore CASCADE;
|
||||
NOTICE: extension "hstore" does not exist, skipping
|
||||
DROP EXTENSION IF EXISTS plperl CASCADE;
|
||||
NOTICE: extension "plperl" does not exist, skipping
|
||||
DROP EXTENSION IF EXISTS hstore_plperl CASCADE;
|
||||
NOTICE: extension "hstore_plperl" does not exist, skipping
|
||||
CREATE EXTENSION hstore;
|
||||
CREATE EXTENSION plperl;
|
||||
CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS '$libdir/hstore_plperl';
|
||||
CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS '$libdir/hstore_plperl';
|
||||
CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
ERROR: type "foo" does not exist
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
ERROR: language "foo" does not exist
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
ERROR: return data type of FROM SQL function must be "internal"
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
ERROR: first argument of transform function must be type "internal"
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
ERROR: transform for type hstore language plperl already exists
|
||||
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
|
||||
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok
|
||||
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
|
||||
DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl;
|
||||
NOTICE: type "fake_type" does not exist, skipping
|
||||
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang;
|
||||
NOTICE: transform for type hstore language fake_lang does not exist, skipping
|
||||
DROP TRANSFORM FOR foo LANGUAGE plperl;
|
||||
ERROR: type "foo" does not exist
|
||||
DROP TRANSFORM FOR hstore LANGUAGE foo;
|
||||
ERROR: language "foo" does not exist
|
||||
DROP TRANSFORM FOR hstore LANGUAGE plperl;
|
||||
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl;
|
||||
NOTICE: transform for type hstore language plperl does not exist, skipping
|
||||
DROP FUNCTION hstore_to_plperl(val internal);
|
||||
DROP FUNCTION plperl_to_hstore(val internal);
|
||||
CREATE EXTENSION hstore_plperl;
|
||||
\dx+ hstore_plperl
|
||||
Objects in extension "hstore_plperl"
|
||||
Object Description
|
||||
--------------------------------------
|
||||
function hstore_to_plperl(internal)
|
||||
function plperl_to_hstore(internal)
|
||||
transform for hstore language plperl
|
||||
(3 rows)
|
||||
|
||||
ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl;
|
||||
\dx+ hstore_plperl
|
||||
Objects in extension "hstore_plperl"
|
||||
Object Description
|
||||
-------------------------------------
|
||||
function hstore_to_plperl(internal)
|
||||
function plperl_to_hstore(internal)
|
||||
(2 rows)
|
||||
|
||||
ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl;
|
||||
\dx+ hstore_plperl
|
||||
Objects in extension "hstore_plperl"
|
||||
Object Description
|
||||
--------------------------------------
|
||||
function hstore_to_plperl(internal)
|
||||
function plperl_to_hstore(internal)
|
||||
transform for hstore language plperl
|
||||
(3 rows)
|
||||
|
||||
DROP EXTENSION hstore CASCADE;
|
||||
NOTICE: drop cascades to extension hstore_plperl
|
||||
DROP EXTENSION plperl CASCADE;
|
213
contrib/hstore_plperl/expected/hstore_plperl.out
Normal file
213
contrib/hstore_plperl/expected/hstore_plperl.out
Normal file
@ -0,0 +1,213 @@
|
||||
CREATE EXTENSION hstore_plperl;
|
||||
CREATE EXTENSION hstore_plperlu;
|
||||
SELECT transforms.udt_schema, transforms.udt_name,
|
||||
routine_schema, routine_name,
|
||||
group_name, transform_type
|
||||
FROM information_schema.transforms JOIN information_schema.routines
|
||||
USING (specific_catalog, specific_schema, specific_name)
|
||||
ORDER BY 1, 2, 5, 6;
|
||||
udt_schema | udt_name | routine_schema | routine_name | group_name | transform_type
|
||||
------------+----------+----------------+-------------------+------------+----------------
|
||||
public | hstore | public | hstore_to_plperl | plperl | FROM SQL
|
||||
public | hstore | public | plperl_to_hstore | plperl | TO SQL
|
||||
public | hstore | public | hstore_to_plperlu | plperlu | FROM SQL
|
||||
public | hstore | public | plperlu_to_hstore | plperlu | TO SQL
|
||||
(4 rows)
|
||||
|
||||
-- test hstore -> perl
|
||||
CREATE FUNCTION test1(val hstore) RETURNS int
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_[0]));
|
||||
return scalar(keys %{$_[0]});
|
||||
$$;
|
||||
SELECT test1('aa=>bb, cc=>NULL'::hstore);
|
||||
INFO: $VAR1 = {
|
||||
'aa' => 'bb',
|
||||
'cc' => undef
|
||||
};
|
||||
|
||||
CONTEXT: PL/Perl function "test1"
|
||||
test1
|
||||
-------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
CREATE FUNCTION test1none(val hstore) RETURNS int
|
||||
LANGUAGE plperlu
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_[0]));
|
||||
return scalar(keys %{$_[0]});
|
||||
$$;
|
||||
SELECT test1none('aa=>bb, cc=>NULL'::hstore);
|
||||
INFO: $VAR1 = '"aa"=>"bb", "cc"=>NULL';
|
||||
|
||||
CONTEXT: PL/Perl function "test1none"
|
||||
test1none
|
||||
-----------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
CREATE FUNCTION test1list(val hstore) RETURNS int
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_[0]));
|
||||
return scalar(keys %{$_[0]});
|
||||
$$;
|
||||
SELECT test1list('aa=>bb, cc=>NULL'::hstore);
|
||||
INFO: $VAR1 = {
|
||||
'aa' => 'bb',
|
||||
'cc' => undef
|
||||
};
|
||||
|
||||
CONTEXT: PL/Perl function "test1list"
|
||||
test1list
|
||||
-----------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
-- test hstore[] -> perl
|
||||
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_[0]->[0], $_[0]->[1]));
|
||||
return scalar(keys %{$_[0]});
|
||||
$$;
|
||||
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
|
||||
INFO: $VAR1 = {
|
||||
'aa' => 'bb',
|
||||
'cc' => undef
|
||||
};
|
||||
$VAR2 = {
|
||||
'dd' => 'ee'
|
||||
};
|
||||
|
||||
CONTEXT: PL/Perl function "test1arr"
|
||||
test1arr
|
||||
----------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
-- test perl -> hstore
|
||||
CREATE FUNCTION test2() RETURNS hstore
|
||||
LANGUAGE plperl
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
$val = {a => 1, b => 'boo', c => undef};
|
||||
return $val;
|
||||
$$;
|
||||
SELECT test2();
|
||||
test2
|
||||
---------------------------------
|
||||
"a"=>"1", "b"=>"boo", "c"=>NULL
|
||||
(1 row)
|
||||
|
||||
-- test perl -> hstore[]
|
||||
CREATE FUNCTION test2arr() RETURNS hstore[]
|
||||
LANGUAGE plperl
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}];
|
||||
return $val;
|
||||
$$;
|
||||
SELECT test2arr();
|
||||
test2arr
|
||||
--------------------------------------------------------------
|
||||
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
|
||||
(1 row)
|
||||
|
||||
-- test as part of prepare/execute
|
||||
CREATE FUNCTION test3() RETURNS void
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
|
||||
$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1});
|
||||
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
|
||||
|
||||
$val = {a => 1, b => 'boo', c => undef};
|
||||
$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore");
|
||||
$rv = spi_exec_prepared($plan, {}, $val);
|
||||
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
|
||||
$$;
|
||||
SELECT test3();
|
||||
INFO: $VAR1 = {
|
||||
'aa' => 'bb',
|
||||
'cc' => undef
|
||||
};
|
||||
|
||||
CONTEXT: PL/Perl function "test3"
|
||||
INFO: $VAR1 = '"a"=>"1", "b"=>"boo", "c"=>NULL';
|
||||
|
||||
CONTEXT: PL/Perl function "test3"
|
||||
test3
|
||||
-------
|
||||
|
||||
(1 row)
|
||||
|
||||
-- test trigger
|
||||
CREATE TABLE test1 (a int, b hstore);
|
||||
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
|
||||
SELECT * FROM test1;
|
||||
a | b
|
||||
---+------------------------
|
||||
1 | "aa"=>"bb", "cc"=>NULL
|
||||
(1 row)
|
||||
|
||||
CREATE FUNCTION test4() RETURNS trigger
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_TD->{new}));
|
||||
if ($_TD->{new}{a} == 1) {
|
||||
$_TD->{new}{b} = {a => 1, b => 'boo', c => undef};
|
||||
}
|
||||
|
||||
return "MODIFY";
|
||||
$$;
|
||||
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
|
||||
UPDATE test1 SET a = a;
|
||||
INFO: $VAR1 = {
|
||||
'a' => '1',
|
||||
'b' => {
|
||||
'aa' => 'bb',
|
||||
'cc' => undef
|
||||
}
|
||||
};
|
||||
|
||||
CONTEXT: PL/Perl function "test4"
|
||||
SELECT * FROM test1;
|
||||
a | b
|
||||
---+---------------------------------
|
||||
1 | "a"=>"1", "b"=>"boo", "c"=>NULL
|
||||
(1 row)
|
||||
|
||||
DROP TABLE test1;
|
||||
DROP FUNCTION test1(hstore);
|
||||
DROP FUNCTION test1none(hstore);
|
||||
DROP FUNCTION test1list(hstore);
|
||||
DROP FUNCTION test1arr(hstore[]);
|
||||
DROP FUNCTION test2();
|
||||
DROP FUNCTION test2arr();
|
||||
DROP FUNCTION test3();
|
||||
DROP FUNCTION test4();
|
||||
DROP EXTENSION hstore_plperl;
|
||||
DROP EXTENSION hstore_plperlu;
|
||||
DROP EXTENSION hstore;
|
||||
DROP EXTENSION plperl;
|
||||
DROP EXTENSION plperlu;
|
17
contrib/hstore_plperl/hstore_plperl--1.0.sql
Normal file
17
contrib/hstore_plperl/hstore_plperl--1.0.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '' LANGUAGE plperl;
|
||||
SELECT NULL::hstore;
|
||||
|
||||
|
||||
CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME';
|
||||
|
||||
CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME';
|
||||
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperl (
|
||||
FROM SQL WITH FUNCTION hstore_to_plperl(internal),
|
||||
TO SQL WITH FUNCTION plperl_to_hstore(internal)
|
||||
);
|
90
contrib/hstore_plperl/hstore_plperl.c
Normal file
90
contrib/hstore_plperl/hstore_plperl.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include "postgres.h"
|
||||
#undef _
|
||||
#include "fmgr.h"
|
||||
#include "plperl.h"
|
||||
#include "plperl_helpers.h"
|
||||
#include "hstore.h"
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
|
||||
PG_FUNCTION_INFO_V1(hstore_to_plperl);
|
||||
Datum hstore_to_plperl(PG_FUNCTION_ARGS);
|
||||
|
||||
Datum
|
||||
hstore_to_plperl(PG_FUNCTION_ARGS)
|
||||
{
|
||||
HStore *in = PG_GETARG_HS(0);
|
||||
int i;
|
||||
int count = HS_COUNT(in);
|
||||
char *base = STRPTR(in);
|
||||
HEntry *entries = ARRPTR(in);
|
||||
HV *hv;
|
||||
|
||||
hv = newHV();
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
const char *key;
|
||||
SV *value;
|
||||
|
||||
key = pnstrdup(HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
|
||||
value = HS_VALISNULL(entries, i) ? newSV(0) : cstr2sv(pnstrdup(HS_VAL(entries, base,i), HS_VALLEN(entries, i)));
|
||||
|
||||
(void) hv_store(hv, key, strlen(key), value, 0);
|
||||
}
|
||||
|
||||
return PointerGetDatum(newRV((SV *) hv));
|
||||
}
|
||||
|
||||
|
||||
PG_FUNCTION_INFO_V1(plperl_to_hstore);
|
||||
Datum plperl_to_hstore(PG_FUNCTION_ARGS);
|
||||
|
||||
Datum
|
||||
plperl_to_hstore(PG_FUNCTION_ARGS)
|
||||
{
|
||||
HV *hv;
|
||||
HE *he;
|
||||
int32 buflen;
|
||||
int32 i;
|
||||
int32 pcount;
|
||||
HStore *out;
|
||||
Pairs *pairs;
|
||||
|
||||
hv = (HV *) SvRV((SV *) PG_GETARG_POINTER(0));
|
||||
|
||||
pcount = hv_iterinit(hv);
|
||||
|
||||
pairs = palloc(pcount * sizeof(Pairs));
|
||||
|
||||
i = 0;
|
||||
while ((he = hv_iternext(hv)))
|
||||
{
|
||||
char *key = sv2cstr(HeSVKEY_force(he));
|
||||
SV *value = HeVAL(he);
|
||||
|
||||
pairs[i].key = pstrdup(key);
|
||||
pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key));
|
||||
pairs[i].needfree = true;
|
||||
|
||||
if (!SvOK(value))
|
||||
{
|
||||
pairs[i].val = NULL;
|
||||
pairs[i].vallen = 0;
|
||||
pairs[i].isnull = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pairs[i].val = pstrdup(sv2cstr(value));
|
||||
pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val));
|
||||
pairs[i].isnull = false;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
pcount = hstoreUniquePairs(pairs, pcount, &buflen);
|
||||
out = hstorePairs(pairs, pcount, buflen);
|
||||
PG_RETURN_POINTER(out);
|
||||
}
|
6
contrib/hstore_plperl/hstore_plperl.control
Normal file
6
contrib/hstore_plperl/hstore_plperl.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plperl extension
|
||||
comment = 'transform between hstore and plperl'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plperl'
|
||||
relocatable = true
|
||||
requires = 'hstore,plperl'
|
17
contrib/hstore_plperl/hstore_plperlu--1.0.sql
Normal file
17
contrib/hstore_plperl/hstore_plperlu--1.0.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '' LANGUAGE plperlu;
|
||||
SELECT NULL::hstore;
|
||||
|
||||
|
||||
CREATE FUNCTION hstore_to_plperlu(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'hstore_to_plperl';
|
||||
|
||||
CREATE FUNCTION plperlu_to_hstore(val internal) RETURNS hstore
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'plperl_to_hstore';
|
||||
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperlu (
|
||||
FROM SQL WITH FUNCTION hstore_to_plperlu(internal),
|
||||
TO SQL WITH FUNCTION plperlu_to_hstore(internal)
|
||||
);
|
6
contrib/hstore_plperl/hstore_plperlu.control
Normal file
6
contrib/hstore_plperl/hstore_plperlu.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plperlu extension
|
||||
comment = 'transform between hstore and plperlu'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plperl'
|
||||
relocatable = true
|
||||
requires = 'hstore,plperlu'
|
47
contrib/hstore_plperl/sql/create_transform.sql
Normal file
47
contrib/hstore_plperl/sql/create_transform.sql
Normal file
@ -0,0 +1,47 @@
|
||||
-- general regression test for transforms
|
||||
|
||||
DROP EXTENSION IF EXISTS hstore CASCADE;
|
||||
DROP EXTENSION IF EXISTS plperl CASCADE;
|
||||
DROP EXTENSION IF EXISTS hstore_plperl CASCADE;
|
||||
|
||||
CREATE EXTENSION hstore;
|
||||
CREATE EXTENSION plperl;
|
||||
|
||||
CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS '$libdir/hstore_plperl';
|
||||
|
||||
CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS '$libdir/hstore_plperl';
|
||||
|
||||
CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail
|
||||
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
|
||||
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok
|
||||
CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok
|
||||
|
||||
DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl;
|
||||
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang;
|
||||
DROP TRANSFORM FOR foo LANGUAGE plperl;
|
||||
DROP TRANSFORM FOR hstore LANGUAGE foo;
|
||||
DROP TRANSFORM FOR hstore LANGUAGE plperl;
|
||||
DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl;
|
||||
|
||||
DROP FUNCTION hstore_to_plperl(val internal);
|
||||
DROP FUNCTION plperl_to_hstore(val internal);
|
||||
|
||||
CREATE EXTENSION hstore_plperl;
|
||||
\dx+ hstore_plperl
|
||||
ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl;
|
||||
\dx+ hstore_plperl
|
||||
ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl;
|
||||
\dx+ hstore_plperl
|
||||
|
||||
DROP EXTENSION hstore CASCADE;
|
||||
DROP EXTENSION plperl CASCADE;
|
148
contrib/hstore_plperl/sql/hstore_plperl.sql
Normal file
148
contrib/hstore_plperl/sql/hstore_plperl.sql
Normal file
@ -0,0 +1,148 @@
|
||||
CREATE EXTENSION hstore_plperl;
|
||||
CREATE EXTENSION hstore_plperlu;
|
||||
|
||||
SELECT transforms.udt_schema, transforms.udt_name,
|
||||
routine_schema, routine_name,
|
||||
group_name, transform_type
|
||||
FROM information_schema.transforms JOIN information_schema.routines
|
||||
USING (specific_catalog, specific_schema, specific_name)
|
||||
ORDER BY 1, 2, 5, 6;
|
||||
|
||||
|
||||
-- test hstore -> perl
|
||||
CREATE FUNCTION test1(val hstore) RETURNS int
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_[0]));
|
||||
return scalar(keys %{$_[0]});
|
||||
$$;
|
||||
|
||||
SELECT test1('aa=>bb, cc=>NULL'::hstore);
|
||||
|
||||
CREATE FUNCTION test1none(val hstore) RETURNS int
|
||||
LANGUAGE plperlu
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_[0]));
|
||||
return scalar(keys %{$_[0]});
|
||||
$$;
|
||||
|
||||
SELECT test1none('aa=>bb, cc=>NULL'::hstore);
|
||||
|
||||
CREATE FUNCTION test1list(val hstore) RETURNS int
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_[0]));
|
||||
return scalar(keys %{$_[0]});
|
||||
$$;
|
||||
|
||||
SELECT test1list('aa=>bb, cc=>NULL'::hstore);
|
||||
|
||||
|
||||
-- test hstore[] -> perl
|
||||
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_[0]->[0], $_[0]->[1]));
|
||||
return scalar(keys %{$_[0]});
|
||||
$$;
|
||||
|
||||
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
|
||||
|
||||
|
||||
-- test perl -> hstore
|
||||
CREATE FUNCTION test2() RETURNS hstore
|
||||
LANGUAGE plperl
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
$val = {a => 1, b => 'boo', c => undef};
|
||||
return $val;
|
||||
$$;
|
||||
|
||||
SELECT test2();
|
||||
|
||||
|
||||
-- test perl -> hstore[]
|
||||
CREATE FUNCTION test2arr() RETURNS hstore[]
|
||||
LANGUAGE plperl
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}];
|
||||
return $val;
|
||||
$$;
|
||||
|
||||
SELECT test2arr();
|
||||
|
||||
|
||||
-- test as part of prepare/execute
|
||||
CREATE FUNCTION test3() RETURNS void
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
|
||||
$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1});
|
||||
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
|
||||
|
||||
$val = {a => 1, b => 'boo', c => undef};
|
||||
$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore");
|
||||
$rv = spi_exec_prepared($plan, {}, $val);
|
||||
elog(INFO, Dumper($rv->{rows}[0]->{col1}));
|
||||
$$;
|
||||
|
||||
SELECT test3();
|
||||
|
||||
|
||||
-- test trigger
|
||||
CREATE TABLE test1 (a int, b hstore);
|
||||
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
|
||||
SELECT * FROM test1;
|
||||
|
||||
CREATE FUNCTION test4() RETURNS trigger
|
||||
LANGUAGE plperlu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
elog(INFO, Dumper($_TD->{new}));
|
||||
if ($_TD->{new}{a} == 1) {
|
||||
$_TD->{new}{b} = {a => 1, b => 'boo', c => undef};
|
||||
}
|
||||
|
||||
return "MODIFY";
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
|
||||
|
||||
UPDATE test1 SET a = a;
|
||||
SELECT * FROM test1;
|
||||
|
||||
|
||||
DROP TABLE test1;
|
||||
|
||||
DROP FUNCTION test1(hstore);
|
||||
DROP FUNCTION test1none(hstore);
|
||||
DROP FUNCTION test1list(hstore);
|
||||
DROP FUNCTION test1arr(hstore[]);
|
||||
DROP FUNCTION test2();
|
||||
DROP FUNCTION test2arr();
|
||||
DROP FUNCTION test3();
|
||||
DROP FUNCTION test4();
|
||||
|
||||
|
||||
DROP EXTENSION hstore_plperl;
|
||||
DROP EXTENSION hstore_plperlu;
|
||||
DROP EXTENSION hstore;
|
||||
DROP EXTENSION plperl;
|
||||
DROP EXTENSION plperlu;
|
6
contrib/hstore_plpython/.gitignore
vendored
Normal file
6
contrib/hstore_plpython/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Generated subdirectories
|
||||
/expected/python3/
|
||||
/log/
|
||||
/results/
|
||||
/sql/python3/
|
||||
/tmp_check/
|
31
contrib/hstore_plpython/Makefile
Normal file
31
contrib/hstore_plpython/Makefile
Normal file
@ -0,0 +1,31 @@
|
||||
# contrib/hstore_plpython/Makefile
|
||||
|
||||
MODULE_big = hstore_plpython$(python_majorversion)
|
||||
OBJS = hstore_plpython.o
|
||||
|
||||
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/hstore
|
||||
|
||||
EXTENSION = hstore_plpythonu hstore_plpython2u hstore_plpython3u
|
||||
DATA = hstore_plpythonu--1.0.sql hstore_plpython2u--1.0.sql hstore_plpython3u--1.0.sql
|
||||
|
||||
REGRESS = hstore_plpython
|
||||
REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
|
||||
|
||||
ifdef USE_PGXS
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
else
|
||||
subdir = contrib/hstore_plpython
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
include $(top_srcdir)/contrib/contrib-global.mk
|
||||
endif
|
||||
|
||||
REGRESS_OPTS = --load-extension=hstore
|
||||
ifeq ($(python_majorversion),2)
|
||||
REGRESS_OPTS += --load-extension=plpythonu --load-extension=hstore_plpythonu
|
||||
endif
|
||||
EXTRA_INSTALL = contrib/hstore
|
||||
|
||||
include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk
|
132
contrib/hstore_plpython/expected/hstore_plpython.out
Normal file
132
contrib/hstore_plpython/expected/hstore_plpython.out
Normal file
@ -0,0 +1,132 @@
|
||||
CREATE EXTENSION plpython2u;
|
||||
CREATE EXTENSION hstore_plpython2u;
|
||||
-- test hstore -> python
|
||||
CREATE FUNCTION test1(val hstore) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
assert isinstance(val, dict)
|
||||
plpy.info(sorted(val.items()))
|
||||
return len(val)
|
||||
$$;
|
||||
SELECT test1('aa=>bb, cc=>NULL'::hstore);
|
||||
INFO: [('aa', 'bb'), ('cc', None)]
|
||||
CONTEXT: PL/Python function "test1"
|
||||
test1
|
||||
-------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
-- the same with the versioned language name
|
||||
CREATE FUNCTION test1n(val hstore) RETURNS int
|
||||
LANGUAGE plpython2u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
assert isinstance(val, dict)
|
||||
plpy.info(sorted(val.items()))
|
||||
return len(val)
|
||||
$$;
|
||||
SELECT test1n('aa=>bb, cc=>NULL'::hstore);
|
||||
INFO: [('aa', 'bb'), ('cc', None)]
|
||||
CONTEXT: PL/Python function "test1n"
|
||||
test1n
|
||||
--------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
-- test hstore[] -> python
|
||||
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
|
||||
INFO: [{'aa': 'bb', 'cc': None}, {'dd': 'ee'}]
|
||||
CONTEXT: PL/Python function "test1arr"
|
||||
test1arr
|
||||
----------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
-- test python -> hstore
|
||||
CREATE FUNCTION test2() RETURNS hstore
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
val = {'a': 1, 'b': 'boo', 'c': None}
|
||||
return val
|
||||
$$;
|
||||
SELECT test2();
|
||||
test2
|
||||
---------------------------------
|
||||
"a"=>"1", "b"=>"boo", "c"=>NULL
|
||||
(1 row)
|
||||
|
||||
-- test python -> hstore[]
|
||||
CREATE FUNCTION test2arr() RETURNS hstore[]
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
|
||||
return val
|
||||
$$;
|
||||
SELECT test2arr();
|
||||
test2arr
|
||||
--------------------------------------------------------------
|
||||
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
|
||||
(1 row)
|
||||
|
||||
-- test as part of prepare/execute
|
||||
CREATE FUNCTION test3() RETURNS void
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1")
|
||||
plpy.info(repr(rv[0]["col1"]))
|
||||
|
||||
val = {'a': 1, 'b': 'boo', 'c': None}
|
||||
plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"])
|
||||
rv = plpy.execute(plan, [val])
|
||||
plpy.info(repr(rv[0]["col1"]))
|
||||
$$;
|
||||
SELECT test3();
|
||||
INFO: {'aa': 'bb', 'cc': None}
|
||||
CONTEXT: PL/Python function "test3"
|
||||
INFO: '"a"=>"1", "b"=>"boo", "c"=>NULL'
|
||||
CONTEXT: PL/Python function "test3"
|
||||
test3
|
||||
-------
|
||||
|
||||
(1 row)
|
||||
|
||||
-- test trigger
|
||||
CREATE TABLE test1 (a int, b hstore);
|
||||
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
|
||||
SELECT * FROM test1;
|
||||
a | b
|
||||
---+------------------------
|
||||
1 | "aa"=>"bb", "cc"=>NULL
|
||||
(1 row)
|
||||
|
||||
CREATE FUNCTION test4() RETURNS trigger
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
plpy.info("Trigger row: {'a': %r, 'b': %r}" % (TD["new"]["a"], TD["new"]["b"]))
|
||||
if TD["new"]["a"] == 1:
|
||||
TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None}
|
||||
|
||||
return "MODIFY"
|
||||
$$;
|
||||
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
|
||||
UPDATE test1 SET a = a;
|
||||
INFO: Trigger row: {'a': 1, 'b': {'aa': 'bb', 'cc': None}}
|
||||
CONTEXT: PL/Python function "test4"
|
||||
SELECT * FROM test1;
|
||||
a | b
|
||||
---+---------------------------------
|
||||
1 | "a"=>"1", "b"=>"boo", "c"=>NULL
|
||||
(1 row)
|
||||
|
116
contrib/hstore_plpython/hstore_plpython.c
Normal file
116
contrib/hstore_plpython/hstore_plpython.c
Normal file
@ -0,0 +1,116 @@
|
||||
#include "postgres.h"
|
||||
#include "fmgr.h"
|
||||
#include "plpython.h"
|
||||
#include "plpy_typeio.h"
|
||||
#include "hstore.h"
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
|
||||
PG_FUNCTION_INFO_V1(hstore_to_plpython);
|
||||
Datum hstore_to_plpython(PG_FUNCTION_ARGS);
|
||||
|
||||
Datum
|
||||
hstore_to_plpython(PG_FUNCTION_ARGS)
|
||||
{
|
||||
HStore *in = PG_GETARG_HS(0);
|
||||
int i;
|
||||
int count = HS_COUNT(in);
|
||||
char *base = STRPTR(in);
|
||||
HEntry *entries = ARRPTR(in);
|
||||
PyObject *dict;
|
||||
|
||||
dict = PyDict_New();
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
PyObject *key;
|
||||
|
||||
key = PyString_FromStringAndSize(HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
|
||||
if (HS_VALISNULL(entries, i))
|
||||
PyDict_SetItem(dict, key, Py_None);
|
||||
else
|
||||
{
|
||||
PyObject *value;
|
||||
|
||||
value = PyString_FromStringAndSize(HS_VAL(entries, base,i), HS_VALLEN(entries, i));
|
||||
PyDict_SetItem(dict, key, value);
|
||||
Py_XDECREF(value);
|
||||
}
|
||||
Py_XDECREF(key);
|
||||
}
|
||||
|
||||
return PointerGetDatum(dict);
|
||||
}
|
||||
|
||||
|
||||
PG_FUNCTION_INFO_V1(plpython_to_hstore);
|
||||
Datum plpython_to_hstore(PG_FUNCTION_ARGS);
|
||||
|
||||
Datum
|
||||
plpython_to_hstore(PG_FUNCTION_ARGS)
|
||||
{
|
||||
PyObject *dict;
|
||||
volatile PyObject *items_v = NULL;
|
||||
int32 pcount;
|
||||
HStore *out;
|
||||
|
||||
dict = (PyObject *) PG_GETARG_POINTER(0);
|
||||
if (!PyMapping_Check(dict))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("not a Python mapping")));
|
||||
|
||||
pcount = PyMapping_Size(dict);
|
||||
items_v = PyMapping_Items(dict);
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
int32 buflen;
|
||||
int32 i;
|
||||
Pairs *pairs;
|
||||
PyObject *items = (PyObject *) items_v;
|
||||
|
||||
pairs = palloc(pcount * sizeof(*pairs));
|
||||
|
||||
for (i = 0; i < pcount; i++)
|
||||
{
|
||||
PyObject *tuple;
|
||||
PyObject *key;
|
||||
PyObject *value;
|
||||
|
||||
tuple = PyList_GetItem(items, i);
|
||||
key = PyTuple_GetItem(tuple, 0);
|
||||
value = PyTuple_GetItem(tuple, 1);
|
||||
|
||||
pairs[i].key = PLyObject_AsString(key);
|
||||
pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key));
|
||||
pairs[i].needfree = true;
|
||||
|
||||
if (value == Py_None)
|
||||
{
|
||||
pairs[i].val = NULL;
|
||||
pairs[i].vallen = 0;
|
||||
pairs[i].isnull = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pairs[i].val = PLyObject_AsString(value);
|
||||
pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val));
|
||||
pairs[i].isnull = false;
|
||||
}
|
||||
}
|
||||
Py_DECREF(items_v);
|
||||
|
||||
pcount = hstoreUniquePairs(pairs, pcount, &buflen);
|
||||
out = hstorePairs(pairs, pcount, buflen);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
Py_DECREF(items_v);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
PG_RETURN_POINTER(out);
|
||||
}
|
19
contrib/hstore_plpython/hstore_plpython2u--1.0.sql
Normal file
19
contrib/hstore_plpython/hstore_plpython2u--1.0.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpython2u;
|
||||
SELECT NULL::hstore;
|
||||
|
||||
|
||||
CREATE FUNCTION hstore_to_plpython2(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'hstore_to_plpython';
|
||||
|
||||
CREATE FUNCTION plpython2_to_hstore(val internal) RETURNS hstore
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'plpython_to_hstore';
|
||||
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plpython2u (
|
||||
FROM SQL WITH FUNCTION hstore_to_plpython2(internal),
|
||||
TO SQL WITH FUNCTION plpython2_to_hstore(internal)
|
||||
);
|
||||
|
||||
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython2u IS 'transform between hstore and Python dict';
|
6
contrib/hstore_plpython/hstore_plpython2u.control
Normal file
6
contrib/hstore_plpython/hstore_plpython2u.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plpython2u extension
|
||||
comment = 'transform between hstore and plpython2u'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plpython2'
|
||||
relocatable = true
|
||||
requires = 'hstore,plpython2u'
|
19
contrib/hstore_plpython/hstore_plpython3u--1.0.sql
Normal file
19
contrib/hstore_plpython/hstore_plpython3u--1.0.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpython3u;
|
||||
SELECT NULL::hstore;
|
||||
|
||||
|
||||
CREATE FUNCTION hstore_to_plpython3(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'hstore_to_plpython';
|
||||
|
||||
CREATE FUNCTION plpython3_to_hstore(val internal) RETURNS hstore
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'plpython_to_hstore';
|
||||
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plpython3u (
|
||||
FROM SQL WITH FUNCTION hstore_to_plpython3(internal),
|
||||
TO SQL WITH FUNCTION plpython3_to_hstore(internal)
|
||||
);
|
||||
|
||||
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython3u IS 'transform between hstore and Python dict';
|
6
contrib/hstore_plpython/hstore_plpython3u.control
Normal file
6
contrib/hstore_plpython/hstore_plpython3u.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plpython3u extension
|
||||
comment = 'transform between hstore and plpython3u'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plpython3'
|
||||
relocatable = true
|
||||
requires = 'hstore,plpython3u'
|
19
contrib/hstore_plpython/hstore_plpythonu--1.0.sql
Normal file
19
contrib/hstore_plpython/hstore_plpythonu--1.0.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpythonu;
|
||||
SELECT NULL::hstore;
|
||||
|
||||
|
||||
CREATE FUNCTION hstore_to_plpython(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME';
|
||||
|
||||
CREATE FUNCTION plpython_to_hstore(val internal) RETURNS hstore
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME';
|
||||
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plpythonu (
|
||||
FROM SQL WITH FUNCTION hstore_to_plpython(internal),
|
||||
TO SQL WITH FUNCTION plpython_to_hstore(internal)
|
||||
);
|
||||
|
||||
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpythonu IS 'transform between hstore and Python dict';
|
6
contrib/hstore_plpython/hstore_plpythonu.control
Normal file
6
contrib/hstore_plpython/hstore_plpythonu.control
Normal file
@ -0,0 +1,6 @@
|
||||
# hstore_plpythonu extension
|
||||
comment = 'transform between hstore and plpythonu'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/hstore_plpython2'
|
||||
relocatable = true
|
||||
requires = 'hstore,plpythonu'
|
103
contrib/hstore_plpython/sql/hstore_plpython.sql
Normal file
103
contrib/hstore_plpython/sql/hstore_plpython.sql
Normal file
@ -0,0 +1,103 @@
|
||||
CREATE EXTENSION plpython2u;
|
||||
CREATE EXTENSION hstore_plpython2u;
|
||||
|
||||
|
||||
-- test hstore -> python
|
||||
CREATE FUNCTION test1(val hstore) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
assert isinstance(val, dict)
|
||||
plpy.info(sorted(val.items()))
|
||||
return len(val)
|
||||
$$;
|
||||
|
||||
SELECT test1('aa=>bb, cc=>NULL'::hstore);
|
||||
|
||||
|
||||
-- the same with the versioned language name
|
||||
CREATE FUNCTION test1n(val hstore) RETURNS int
|
||||
LANGUAGE plpython2u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
assert isinstance(val, dict)
|
||||
plpy.info(sorted(val.items()))
|
||||
return len(val)
|
||||
$$;
|
||||
|
||||
SELECT test1n('aa=>bb, cc=>NULL'::hstore);
|
||||
|
||||
|
||||
-- test hstore[] -> python
|
||||
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
|
||||
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
|
||||
|
||||
|
||||
-- test python -> hstore
|
||||
CREATE FUNCTION test2() RETURNS hstore
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
val = {'a': 1, 'b': 'boo', 'c': None}
|
||||
return val
|
||||
$$;
|
||||
|
||||
SELECT test2();
|
||||
|
||||
|
||||
-- test python -> hstore[]
|
||||
CREATE FUNCTION test2arr() RETURNS hstore[]
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
|
||||
return val
|
||||
$$;
|
||||
|
||||
SELECT test2arr();
|
||||
|
||||
|
||||
-- test as part of prepare/execute
|
||||
CREATE FUNCTION test3() RETURNS void
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1")
|
||||
plpy.info(repr(rv[0]["col1"]))
|
||||
|
||||
val = {'a': 1, 'b': 'boo', 'c': None}
|
||||
plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"])
|
||||
rv = plpy.execute(plan, [val])
|
||||
plpy.info(repr(rv[0]["col1"]))
|
||||
$$;
|
||||
|
||||
SELECT test3();
|
||||
|
||||
|
||||
-- test trigger
|
||||
CREATE TABLE test1 (a int, b hstore);
|
||||
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
|
||||
SELECT * FROM test1;
|
||||
|
||||
CREATE FUNCTION test4() RETURNS trigger
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
plpy.info("Trigger row: {'a': %r, 'b': %r}" % (TD["new"]["a"], TD["new"]["b"]))
|
||||
if TD["new"]["a"] == 1:
|
||||
TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None}
|
||||
|
||||
return "MODIFY"
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
|
||||
|
||||
UPDATE test1 SET a = a;
|
||||
SELECT * FROM test1;
|
6
contrib/ltree_plpython/.gitignore
vendored
Normal file
6
contrib/ltree_plpython/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Generated subdirectories
|
||||
/expected/python3/
|
||||
/log/
|
||||
/results/
|
||||
/sql/python3/
|
||||
/tmp_check/
|
31
contrib/ltree_plpython/Makefile
Normal file
31
contrib/ltree_plpython/Makefile
Normal file
@ -0,0 +1,31 @@
|
||||
# contrib/ltree_plpython/Makefile
|
||||
|
||||
MODULE_big = ltree_plpython$(python_majorversion)
|
||||
OBJS = ltree_plpython.o
|
||||
|
||||
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -I$(top_srcdir)/contrib/ltree
|
||||
|
||||
EXTENSION = ltree_plpythonu ltree_plpython2u ltree_plpython3u
|
||||
DATA = ltree_plpythonu--1.0.sql ltree_plpython2u--1.0.sql ltree_plpython3u--1.0.sql
|
||||
|
||||
REGRESS = ltree_plpython
|
||||
REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
|
||||
|
||||
ifdef USE_PGXS
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
else
|
||||
subdir = contrib/ltree_plpython
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
include $(top_srcdir)/contrib/contrib-global.mk
|
||||
endif
|
||||
|
||||
REGRESS_OPTS = --load-extension=ltree
|
||||
ifeq ($(python_majorversion),2)
|
||||
REGRESS_OPTS += --load-extension=plpythonu --load-extension=ltree_plpythonu
|
||||
endif
|
||||
EXTRA_INSTALL = contrib/ltree
|
||||
|
||||
include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk
|
45
contrib/ltree_plpython/expected/ltree_plpython.out
Normal file
45
contrib/ltree_plpython/expected/ltree_plpython.out
Normal file
@ -0,0 +1,45 @@
|
||||
CREATE EXTENSION plpython2u;
|
||||
CREATE EXTENSION ltree_plpython2u;
|
||||
CREATE FUNCTION test1(val ltree) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
SELECT test1('aa.bb.cc'::ltree);
|
||||
INFO: ['aa', 'bb', 'cc']
|
||||
CONTEXT: PL/Python function "test1"
|
||||
test1
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
CREATE FUNCTION test1n(val ltree) RETURNS int
|
||||
LANGUAGE plpython2u
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
SELECT test1n('aa.bb.cc'::ltree);
|
||||
INFO: ['aa', 'bb', 'cc']
|
||||
CONTEXT: PL/Python function "test1n"
|
||||
test1n
|
||||
--------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
CREATE FUNCTION test2() RETURNS ltree
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
return ['foo', 'bar', 'baz']
|
||||
$$;
|
||||
-- plpython to ltree is not yet implemented, so this will fail,
|
||||
-- because it will try to parse the Python list as an ltree input
|
||||
-- string.
|
||||
SELECT test2();
|
||||
ERROR: syntax error at position 0
|
||||
CONTEXT: while creating return value
|
||||
PL/Python function "test2"
|
32
contrib/ltree_plpython/ltree_plpython.c
Normal file
32
contrib/ltree_plpython/ltree_plpython.c
Normal file
@ -0,0 +1,32 @@
|
||||
#include "postgres.h"
|
||||
#include "fmgr.h"
|
||||
#include "plpython.h"
|
||||
#include "ltree.h"
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
|
||||
PG_FUNCTION_INFO_V1(ltree_to_plpython);
|
||||
Datum ltree_to_plpython(PG_FUNCTION_ARGS);
|
||||
|
||||
Datum
|
||||
ltree_to_plpython(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ltree *in = PG_GETARG_LTREE(0);
|
||||
int i;
|
||||
PyObject *list;
|
||||
ltree_level *curlevel;
|
||||
|
||||
list = PyList_New(in->numlevel);
|
||||
|
||||
curlevel = LTREE_FIRST(in);
|
||||
for (i = 0; i < in->numlevel; i++)
|
||||
{
|
||||
PyList_SetItem(list, i, PyString_FromStringAndSize(curlevel->name, curlevel->len));
|
||||
curlevel = LEVEL_NEXT(curlevel);
|
||||
}
|
||||
|
||||
PG_FREE_IF_COPY(in, 0);
|
||||
|
||||
return PointerGetDatum(list);
|
||||
}
|
12
contrib/ltree_plpython/ltree_plpython2u--1.0.sql
Normal file
12
contrib/ltree_plpython/ltree_plpython2u--1.0.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpython2u;
|
||||
SELECT NULL::ltree;
|
||||
|
||||
|
||||
CREATE FUNCTION ltree_to_plpython2(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'ltree_to_plpython';
|
||||
|
||||
CREATE TRANSFORM FOR ltree LANGUAGE plpython2u (
|
||||
FROM SQL WITH FUNCTION ltree_to_plpython2(internal)
|
||||
);
|
6
contrib/ltree_plpython/ltree_plpython2u.control
Normal file
6
contrib/ltree_plpython/ltree_plpython2u.control
Normal file
@ -0,0 +1,6 @@
|
||||
# ltree_plpython2u extension
|
||||
comment = 'transform between ltree and plpython2u'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/ltree_plpython2'
|
||||
relocatable = true
|
||||
requires = 'ltree,plpython2u'
|
12
contrib/ltree_plpython/ltree_plpython3u--1.0.sql
Normal file
12
contrib/ltree_plpython/ltree_plpython3u--1.0.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpython3u;
|
||||
SELECT NULL::ltree;
|
||||
|
||||
|
||||
CREATE FUNCTION ltree_to_plpython3(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME', 'ltree_to_plpython';
|
||||
|
||||
CREATE TRANSFORM FOR ltree LANGUAGE plpython3u (
|
||||
FROM SQL WITH FUNCTION ltree_to_plpython3(internal)
|
||||
);
|
6
contrib/ltree_plpython/ltree_plpython3u.control
Normal file
6
contrib/ltree_plpython/ltree_plpython3u.control
Normal file
@ -0,0 +1,6 @@
|
||||
# ltree_plpython3u extension
|
||||
comment = 'transform between ltree and plpython3u'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/ltree_plpython3'
|
||||
relocatable = true
|
||||
requires = 'ltree,plpython3u'
|
12
contrib/ltree_plpython/ltree_plpythonu--1.0.sql
Normal file
12
contrib/ltree_plpython/ltree_plpythonu--1.0.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- make sure the prerequisite libraries are loaded
|
||||
DO '1' LANGUAGE plpythonu;
|
||||
SELECT NULL::ltree;
|
||||
|
||||
|
||||
CREATE FUNCTION ltree_to_plpython(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS 'MODULE_PATHNAME';
|
||||
|
||||
CREATE TRANSFORM FOR ltree LANGUAGE plpythonu (
|
||||
FROM SQL WITH FUNCTION ltree_to_plpython(internal)
|
||||
);
|
6
contrib/ltree_plpython/ltree_plpythonu.control
Normal file
6
contrib/ltree_plpython/ltree_plpythonu.control
Normal file
@ -0,0 +1,6 @@
|
||||
# ltree_plpythonu extension
|
||||
comment = 'transform between ltree and plpythonu'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/ltree_plpython2'
|
||||
relocatable = true
|
||||
requires = 'ltree,plpythonu'
|
37
contrib/ltree_plpython/sql/ltree_plpython.sql
Normal file
37
contrib/ltree_plpython/sql/ltree_plpython.sql
Normal file
@ -0,0 +1,37 @@
|
||||
CREATE EXTENSION plpython2u;
|
||||
CREATE EXTENSION ltree_plpython2u;
|
||||
|
||||
|
||||
CREATE FUNCTION test1(val ltree) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
|
||||
SELECT test1('aa.bb.cc'::ltree);
|
||||
|
||||
|
||||
CREATE FUNCTION test1n(val ltree) RETURNS int
|
||||
LANGUAGE plpython2u
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
plpy.info(repr(val))
|
||||
return len(val)
|
||||
$$;
|
||||
|
||||
SELECT test1n('aa.bb.cc'::ltree);
|
||||
|
||||
|
||||
CREATE FUNCTION test2() RETURNS ltree
|
||||
LANGUAGE plpythonu
|
||||
TRANSFORM FOR TYPE ltree
|
||||
AS $$
|
||||
return ['foo', 'bar', 'baz']
|
||||
$$;
|
||||
|
||||
-- plpython to ltree is not yet implemented, so this will fail,
|
||||
-- because it will try to parse the Python list as an ltree input
|
||||
-- string.
|
||||
SELECT test2();
|
@ -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=> SELECT ins_label(path,2,'Space') FROM test WHERE path <@ '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>
|
||||
|
||||
|
207
doc/src/sgml/ref/create_transform.sgml
Normal file
207
doc/src/sgml/ref/create_transform.sgml
Normal file
@ -0,0 +1,207 @@
|
||||
<!-- doc/src/sgml/ref/create_transform.sgml -->
|
||||
|
||||
<refentry id="SQL-CREATETRANSFORM">
|
||||
<indexterm zone="sql-createtransform">
|
||||
<primary>CREATE TRANSFORM</primary>
|
||||
</indexterm>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>CREATE TRANSFORM</refentrytitle>
|
||||
<manvolnum>7</manvolnum>
|
||||
<refmiscinfo>SQL - Language Statements</refmiscinfo>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>CREATE TRANSFORM</refname>
|
||||
<refpurpose>define a new transform</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<synopsis>
|
||||
CREATE [ OR REPLACE ] TRANSFORM FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable> (
|
||||
FROM SQL WITH FUNCTION <replaceable>from_sql_function_name</replaceable> (<replaceable>argument_type</replaceable> [, ...]),
|
||||
TO SQL WITH FUNCTION <replaceable>to_sql_function_name</replaceable> (<replaceable>argument_type</replaceable> [, ...])
|
||||
);
|
||||
</synopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1 id="sql-createtransform-description">
|
||||
<title>Description</title>
|
||||
|
||||
<para>
|
||||
<command>CREATE TRANSFORM</command> defines a new transform.
|
||||
<command>CREATE OR REPLACE TRANSFORM</command> will either create a new
|
||||
transform, or replace an existing definition.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A transform specifies how to adapt a data type to a procedural language.
|
||||
For example, when writing a function in PL/Python using
|
||||
the <type>hstore</type> type, PL/Python has no prior knowledge how to
|
||||
present <type>hstore</type> values in the Python environment. Language
|
||||
implementations usually default to using the text representation, but that
|
||||
is inconvenient when, for example, an associative array or a list would be
|
||||
more appropriate.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A transform specifies two functions:
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
A <quote>from SQL</quote> function that converts the type from the SQL
|
||||
environment to the language. This function will be invoked on the
|
||||
arguments of a function written in the language.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
A <quote>to SQL</quote> function that converts the type from the
|
||||
language to the SQL environment. This function will be invoked on the
|
||||
return value of a function written in the language.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
It is not necessary to provide both of these functions. If one is not
|
||||
specified, the language-specific default behavior will be used if
|
||||
necessary. (To prevent a transformation in a certain direction from
|
||||
happening at all, you could also write a transform function that always
|
||||
errors out.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To be able to create a transform, you must own and
|
||||
have <literal>USAGE</literal> privilege on the type, have
|
||||
<literal>USAGE</literal> privilege on the language, and own and
|
||||
have <literal>EXECUTE</literal> privilege on the from-SQL and to-SQL
|
||||
functions, if specified.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Parameters</title>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><replaceable>type_name</replaceable></term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The name of the data type of the transform.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><replaceable>lang_name</replaceable></term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The name of the language of the transform.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><replaceable>from_sql_function_name</replaceable>(<replaceable>argument_type</replaceable> [, ...])</term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The name of the function for converting the type from the SQL
|
||||
environment to the language. It must take one argument of
|
||||
type <type>internal</type> and return type <type>internal</type>. The
|
||||
actual argument will be of the type for the transform, and the function
|
||||
should be coded as if it were. (But it is not allowed to declare an
|
||||
SQL-level function function returning <type>internal</type> without at
|
||||
least one argument of type <type>internal</type>.) The actual return
|
||||
value will be something specific to the language implementation.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><replaceable>to_sql_function_name</replaceable>(<replaceable>argument_type</replaceable> [, ...])</term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The name of the function for converting the type from the language to
|
||||
the SQL environment. It must take one argument of type
|
||||
<type>internal</type> and return the type that is the type for the
|
||||
transform. The actual argument value will be something specific to the
|
||||
language implementation.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="sql-createtransform-notes">
|
||||
<title>Notes</title>
|
||||
|
||||
<para>
|
||||
Use <xref linkend="sql-droptransform"> to remove transforms.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="sql-createtransform-examples">
|
||||
<title>Examples</title>
|
||||
|
||||
<para>
|
||||
To create a transform for type <type>hstore</type> and language
|
||||
<literal>plpythonu</literal>, first set up the type and the language:
|
||||
<programlisting>
|
||||
CREATE TYPE hstore ...;
|
||||
|
||||
CREATE LANGUAGE plpythonu ...;
|
||||
</programlisting>
|
||||
Then create the necessary functions:
|
||||
<programlisting>
|
||||
CREATE FUNCTION hstore_to_plpython(val internal) RETURNS internal
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS ...;
|
||||
|
||||
CREATE FUNCTION plpython_to_hstore(val internal) RETURNS hstore
|
||||
LANGUAGE C STRICT IMMUTABLE
|
||||
AS ...;
|
||||
</programlisting>
|
||||
And finally create the transform to connect them all together:
|
||||
<programlisting>
|
||||
CREATE TRANSFORM FOR hstore LANGUAGE plpythonu (
|
||||
FROM SQL WITH FUNCTION hstore_to_plpython(internal),
|
||||
TO SQL WITH FUNCTION plpython_to_hstore(internal)
|
||||
);
|
||||
</programlisting>
|
||||
In practice, these commands would be wrapped up in extensions.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The <filename>contrib</filename> section contains a number of extensions
|
||||
that provide transforms, which can serve as real-world examples.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="sql-createtransform-compat">
|
||||
<title>Compatibility</title>
|
||||
|
||||
<para>
|
||||
This form of <command>CREATE TRANSFORM</command> is a
|
||||
<productname>PostgreSQL</productname> extension. There is a <command>CREATE
|
||||
TRANSFORM</command> command in the <acronym>SQL</acronym> standard, but it
|
||||
is for adapting data types to client languages. That usage is not supported
|
||||
by <productname>PostgreSQL</productname>.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="sql-createtransform-seealso">
|
||||
<title>See Also</title>
|
||||
|
||||
<para>
|
||||
<xref linkend="sql-createfunction">,
|
||||
<xref linkend="sql-createlanguage">,
|
||||
<xref linkend="sql-createtype">,
|
||||
<xref linkend="sql-droptransform">
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
123
doc/src/sgml/ref/drop_transform.sgml
Normal file
123
doc/src/sgml/ref/drop_transform.sgml
Normal file
@ -0,0 +1,123 @@
|
||||
<!-- doc/src/sgml/ref/drop_transform.sgml -->
|
||||
|
||||
<refentry id="SQL-DROPTRANSFORM">
|
||||
<indexterm zone="sql-droptransform">
|
||||
<primary>DROP TRANSFORM</primary>
|
||||
</indexterm>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>DROP TRANSFORM</refentrytitle>
|
||||
<manvolnum>7</manvolnum>
|
||||
<refmiscinfo>SQL - Language Statements</refmiscinfo>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>DROP TRANSFORM</refname>
|
||||
<refpurpose>remove a transform</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<synopsis>
|
||||
DROP TRANSFORM [ IF EXISTS ] FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable>
|
||||
</synopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1 id="sql-droptransform-description">
|
||||
<title>Description</title>
|
||||
|
||||
<para>
|
||||
<command>DROP TRANSFORM</command> removes a previously defined transform.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To be able to drop a transform, you must own the type and the language.
|
||||
These are the same privileges that are required to create a transform.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Parameters</title>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>IF EXISTS</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Do not throw an error if the transform does not exist. A notice is issued
|
||||
in this case.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><replaceable>type_name</replaceable></term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The name of the data type of the transform.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><replaceable>lang_name</replaceable></term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The name of the language of the transform.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>CASCADE</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Automatically drop objects that depend on the transform.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>RESTRICT</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Refuse to drop the transform if any objects depend on it. This is the
|
||||
default.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="sql-droptransform-examples">
|
||||
<title>Examples</title>
|
||||
|
||||
<para>
|
||||
To drop the transform for type <type>hstore</type> and language
|
||||
<literal>plpythonu</literal>:
|
||||
<programlisting>
|
||||
DROP TRANSFORM FOR hstore LANGUAGE plpythonu;
|
||||
</programlisting></para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="sql-droptransform-compat">
|
||||
<title>Compatibility</title>
|
||||
|
||||
<para>
|
||||
This form of <command>DROP TRANSFORM</command> is a
|
||||
<productname>PostgreSQL</productname> extension. See <xref
|
||||
linkend="sql-createtransform"> for details.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
|
||||
<simplelist type="inline">
|
||||
<member><xref linkend="sql-createtransform"></member>
|
||||
</simplelist>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
@ -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:
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "catalog/pg_namespace.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
#include "catalog/pg_proc_fn.h"
|
||||
#include "catalog/pg_transform.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "catalog/pg_type_fn.h"
|
||||
#include "commands/alter.h"
|
||||
@ -583,6 +584,7 @@ static void
|
||||
compute_attributes_sql_style(List *options,
|
||||
List **as,
|
||||
char **language,
|
||||
Node **transform,
|
||||
bool *windowfunc_p,
|
||||
char *volatility_p,
|
||||
bool *strict_p,
|
||||
@ -595,6 +597,7 @@ compute_attributes_sql_style(List *options,
|
||||
ListCell *option;
|
||||
DefElem *as_item = NULL;
|
||||
DefElem *language_item = NULL;
|
||||
DefElem *transform_item = NULL;
|
||||
DefElem *windowfunc_item = NULL;
|
||||
DefElem *volatility_item = NULL;
|
||||
DefElem *strict_item = NULL;
|
||||
@ -624,6 +627,14 @@ compute_attributes_sql_style(List *options,
|
||||
errmsg("conflicting or redundant options")));
|
||||
language_item = defel;
|
||||
}
|
||||
else if (strcmp(defel->defname, "transform") == 0)
|
||||
{
|
||||
if (transform_item)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("conflicting or redundant options")));
|
||||
transform_item = defel;
|
||||
}
|
||||
else if (strcmp(defel->defname, "window") == 0)
|
||||
{
|
||||
if (windowfunc_item)
|
||||
@ -671,6 +682,8 @@ compute_attributes_sql_style(List *options,
|
||||
}
|
||||
|
||||
/* process optional items */
|
||||
if (transform_item)
|
||||
*transform = transform_item->arg;
|
||||
if (windowfunc_item)
|
||||
*windowfunc_p = intVal(windowfunc_item->arg);
|
||||
if (volatility_item)
|
||||
@ -807,7 +820,6 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CreateFunction
|
||||
* Execute a CREATE FUNCTION utility statement.
|
||||
@ -822,6 +834,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
|
||||
char *language;
|
||||
Oid languageOid;
|
||||
Oid languageValidator;
|
||||
Node *transformDefElem = NULL;
|
||||
char *funcname;
|
||||
Oid namespaceId;
|
||||
AclResult aclresult;
|
||||
@ -831,6 +844,8 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
|
||||
ArrayType *parameterNames;
|
||||
List *parameterDefaults;
|
||||
Oid variadicArgType;
|
||||
List *trftypes_list = NIL;
|
||||
ArrayType *trftypes;
|
||||
Oid requiredResultType;
|
||||
bool isWindowFunc,
|
||||
isStrict,
|
||||
@ -866,7 +881,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
|
||||
|
||||
/* override attributes from explicit list */
|
||||
compute_attributes_sql_style(stmt->options,
|
||||
&as_clause, &language,
|
||||
&as_clause, &language, &transformDefElem,
|
||||
&isWindowFunc, &volatility,
|
||||
&isStrict, &security, &isLeakProof,
|
||||
&proconfig, &procost, &prorows);
|
||||
@ -915,6 +930,23 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
|
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||
errmsg("only superuser can define a leakproof function")));
|
||||
|
||||
if (transformDefElem)
|
||||
{
|
||||
ListCell *lc;
|
||||
|
||||
Assert(IsA(transformDefElem, List));
|
||||
|
||||
foreach (lc, (List *) transformDefElem)
|
||||
{
|
||||
Oid typeid = typenameTypeId(NULL, lfirst(lc));
|
||||
Oid elt = get_base_element_type(typeid);
|
||||
typeid = elt ? elt : typeid;
|
||||
|
||||
get_transform_oid(typeid, languageOid, false);
|
||||
trftypes_list = lappend_oid(trftypes_list, typeid);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert remaining parameters of CREATE to form wanted by
|
||||
* ProcedureCreate.
|
||||
@ -958,6 +990,25 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
|
||||
returnsSet = false;
|
||||
}
|
||||
|
||||
if (list_length(trftypes_list) > 0)
|
||||
{
|
||||
ListCell *lc;
|
||||
Datum *arr;
|
||||
int i;
|
||||
|
||||
arr = palloc(list_length(trftypes_list) * sizeof(Datum));
|
||||
i = 0;
|
||||
foreach (lc, trftypes_list)
|
||||
arr[i++] = ObjectIdGetDatum(lfirst_oid(lc));
|
||||
trftypes = construct_array(arr, list_length(trftypes_list),
|
||||
OIDOID, sizeof(Oid), true, 'i');
|
||||
}
|
||||
else
|
||||
{
|
||||
/* store SQL NULL instead of emtpy array */
|
||||
trftypes = NULL;
|
||||
}
|
||||
|
||||
compute_attributes_with_style(stmt->withClause, &isStrict, &volatility);
|
||||
|
||||
interpret_AS_clause(languageOid, language, funcname, as_clause,
|
||||
@ -1014,6 +1065,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
|
||||
PointerGetDatum(parameterModes),
|
||||
PointerGetDatum(parameterNames),
|
||||
parameterDefaults,
|
||||
PointerGetDatum(trftypes),
|
||||
PointerGetDatum(proconfig),
|
||||
procost,
|
||||
prorows);
|
||||
@ -1653,6 +1705,293 @@ DropCastById(Oid castOid)
|
||||
heap_close(relation, RowExclusiveLock);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
check_transform_function(Form_pg_proc procstruct)
|
||||
{
|
||||
if (procstruct->provolatile == PROVOLATILE_VOLATILE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("transform function must not be volatile")));
|
||||
if (procstruct->proisagg)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("transform function must not be an aggregate function")));
|
||||
if (procstruct->proiswindow)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("transform function must not be a window function")));
|
||||
if (procstruct->proretset)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("transform function must not return a set")));
|
||||
if (procstruct->pronargs != 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("transform function must take one argument")));
|
||||
if (procstruct->proargtypes.values[0] != INTERNALOID)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("first argument of transform function must be type \"internal\"")));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CREATE TRANSFORM
|
||||
*/
|
||||
Oid
|
||||
CreateTransform(CreateTransformStmt *stmt)
|
||||
{
|
||||
Oid typeid;
|
||||
char typtype;
|
||||
Oid langid;
|
||||
Oid fromsqlfuncid;
|
||||
Oid tosqlfuncid;
|
||||
AclResult aclresult;
|
||||
Form_pg_proc procstruct;
|
||||
Datum values[Natts_pg_transform];
|
||||
bool nulls[Natts_pg_transform];
|
||||
bool replaces[Natts_pg_transform];
|
||||
Oid transformid;
|
||||
HeapTuple tuple;
|
||||
HeapTuple newtuple;
|
||||
Relation relation;
|
||||
ObjectAddress myself,
|
||||
referenced;
|
||||
bool is_replace;
|
||||
|
||||
/*
|
||||
* Get the type
|
||||
*/
|
||||
typeid = typenameTypeId(NULL, stmt->type_name);
|
||||
typtype = get_typtype(typeid);
|
||||
|
||||
if (typtype == TYPTYPE_PSEUDO)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("data type %s is a pseudo-type",
|
||||
TypeNameToString(stmt->type_name))));
|
||||
|
||||
if (typtype == TYPTYPE_DOMAIN)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("data type %s is a domain",
|
||||
TypeNameToString(stmt->type_name))));
|
||||
|
||||
if (!pg_type_ownercheck(typeid, GetUserId()))
|
||||
aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
|
||||
|
||||
aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE);
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error_type(aclresult, typeid);
|
||||
|
||||
/*
|
||||
* Get the language
|
||||
*/
|
||||
langid = get_language_oid(stmt->lang, false);
|
||||
|
||||
aclresult = pg_language_aclcheck(langid, GetUserId(), ACL_USAGE);
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, ACL_KIND_LANGUAGE, stmt->lang);
|
||||
|
||||
/*
|
||||
* Get the functions
|
||||
*/
|
||||
if (stmt->fromsql)
|
||||
{
|
||||
fromsqlfuncid = LookupFuncNameTypeNames(stmt->fromsql->funcname, stmt->fromsql->funcargs, false);
|
||||
|
||||
if (!pg_proc_ownercheck(fromsqlfuncid, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
|
||||
|
||||
aclresult = pg_proc_aclcheck(fromsqlfuncid, GetUserId(), ACL_EXECUTE);
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
|
||||
|
||||
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fromsqlfuncid));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "cache lookup failed for function %u", fromsqlfuncid);
|
||||
procstruct = (Form_pg_proc) GETSTRUCT(tuple);
|
||||
if (procstruct->prorettype != INTERNALOID)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("return data type of FROM SQL function must be \"internal\"")));
|
||||
check_transform_function(procstruct);
|
||||
ReleaseSysCache(tuple);
|
||||
}
|
||||
else
|
||||
fromsqlfuncid = InvalidOid;
|
||||
|
||||
if (stmt->tosql)
|
||||
{
|
||||
tosqlfuncid = LookupFuncNameTypeNames(stmt->tosql->funcname, stmt->tosql->funcargs, false);
|
||||
|
||||
if (!pg_proc_ownercheck(tosqlfuncid, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
|
||||
|
||||
aclresult = pg_proc_aclcheck(tosqlfuncid, GetUserId(), ACL_EXECUTE);
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
|
||||
|
||||
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(tosqlfuncid));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "cache lookup failed for function %u", tosqlfuncid);
|
||||
procstruct = (Form_pg_proc) GETSTRUCT(tuple);
|
||||
if (procstruct->prorettype != typeid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("return data type of TO SQL function must be the transform data type")));
|
||||
check_transform_function(procstruct);
|
||||
ReleaseSysCache(tuple);
|
||||
}
|
||||
else
|
||||
tosqlfuncid = InvalidOid;
|
||||
|
||||
/*
|
||||
* Ready to go
|
||||
*/
|
||||
values[Anum_pg_transform_trftype - 1] = ObjectIdGetDatum(typeid);
|
||||
values[Anum_pg_transform_trflang - 1] = ObjectIdGetDatum(langid);
|
||||
values[Anum_pg_transform_trffromsql - 1] = ObjectIdGetDatum(fromsqlfuncid);
|
||||
values[Anum_pg_transform_trftosql - 1] = ObjectIdGetDatum(tosqlfuncid);
|
||||
|
||||
MemSet(nulls, false, sizeof(nulls));
|
||||
|
||||
relation = heap_open(TransformRelationId, RowExclusiveLock);
|
||||
|
||||
tuple = SearchSysCache2(TRFTYPELANG,
|
||||
ObjectIdGetDatum(typeid),
|
||||
ObjectIdGetDatum(langid));
|
||||
if (HeapTupleIsValid(tuple))
|
||||
{
|
||||
if (!stmt->replace)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
||||
errmsg("transform for type %s language %s already exists",
|
||||
format_type_be(typeid),
|
||||
stmt->lang)));
|
||||
|
||||
MemSet(replaces, false, sizeof(replaces));
|
||||
replaces[Anum_pg_transform_trffromsql - 1] = true;
|
||||
replaces[Anum_pg_transform_trftosql - 1] = true;
|
||||
|
||||
newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);
|
||||
simple_heap_update(relation, &newtuple->t_self, newtuple);
|
||||
|
||||
transformid = HeapTupleGetOid(tuple);
|
||||
ReleaseSysCache(tuple);
|
||||
is_replace = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
newtuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
|
||||
transformid = simple_heap_insert(relation, newtuple);
|
||||
is_replace = false;
|
||||
}
|
||||
|
||||
CatalogUpdateIndexes(relation, newtuple);
|
||||
|
||||
if (is_replace)
|
||||
deleteDependencyRecordsFor(TransformRelationId, transformid, true);
|
||||
|
||||
/* make dependency entries */
|
||||
myself.classId = TransformRelationId;
|
||||
myself.objectId = transformid;
|
||||
myself.objectSubId = 0;
|
||||
|
||||
/* dependency on language */
|
||||
referenced.classId = LanguageRelationId;
|
||||
referenced.objectId = langid;
|
||||
referenced.objectSubId = 0;
|
||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
||||
|
||||
/* dependency on type */
|
||||
referenced.classId = TypeRelationId;
|
||||
referenced.objectId = typeid;
|
||||
referenced.objectSubId = 0;
|
||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
||||
|
||||
/* dependencies on functions */
|
||||
if (OidIsValid(fromsqlfuncid))
|
||||
{
|
||||
referenced.classId = ProcedureRelationId;
|
||||
referenced.objectId = fromsqlfuncid;
|
||||
referenced.objectSubId = 0;
|
||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
||||
}
|
||||
if (OidIsValid(tosqlfuncid))
|
||||
{
|
||||
referenced.classId = ProcedureRelationId;
|
||||
referenced.objectId = tosqlfuncid;
|
||||
referenced.objectSubId = 0;
|
||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
||||
}
|
||||
|
||||
/* dependency on extension */
|
||||
recordDependencyOnCurrentExtension(&myself, is_replace);
|
||||
|
||||
/* Post creation hook for new transform */
|
||||
InvokeObjectPostCreateHook(TransformRelationId, transformid, 0);
|
||||
|
||||
heap_freetuple(newtuple);
|
||||
|
||||
heap_close(relation, RowExclusiveLock);
|
||||
|
||||
return transformid;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_transform_oid - given type OID and language OID, look up a transform OID
|
||||
*
|
||||
* If missing_ok is false, throw an error if the transform is not found. If
|
||||
* true, just return InvalidOid.
|
||||
*/
|
||||
Oid
|
||||
get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok)
|
||||
{
|
||||
Oid oid;
|
||||
|
||||
oid = GetSysCacheOid2(TRFTYPELANG,
|
||||
ObjectIdGetDatum(type_id),
|
||||
ObjectIdGetDatum(lang_id));
|
||||
if (!OidIsValid(oid) && !missing_ok)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("transform for type %s language \"%s\" does not exist",
|
||||
format_type_be(type_id),
|
||||
get_language_name(lang_id, false))));
|
||||
return oid;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DropTransformById(Oid transformOid)
|
||||
{
|
||||
Relation relation;
|
||||
ScanKeyData scankey;
|
||||
SysScanDesc scan;
|
||||
HeapTuple tuple;
|
||||
|
||||
relation = heap_open(TransformRelationId, RowExclusiveLock);
|
||||
|
||||
ScanKeyInit(&scankey,
|
||||
ObjectIdAttributeNumber,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(transformOid));
|
||||
scan = systable_beginscan(relation, TransformOidIndexId, true,
|
||||
NULL, 1, &scankey);
|
||||
|
||||
tuple = systable_getnext(scan);
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "could not find tuple for transform %u", transformOid);
|
||||
simple_heap_delete(relation, &tuple->t_self);
|
||||
|
||||
systable_endscan(scan);
|
||||
heap_close(relation, RowExclusiveLock);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Subroutine for ALTER FUNCTION/AGGREGATE SET SCHEMA/RENAME
|
||||
*
|
||||
|
@ -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
|
||||
|
71
src/backend/utils/cache/lsyscache.c
vendored
71
src/backend/utils/cache/lsyscache.c
vendored
@ -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 ---------- */
|
||||
|
||||
/*
|
||||
|
23
src/backend/utils/cache/syscache.c
vendored
23
src/backend/utils/cache/syscache.c
vendored
@ -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);
|
||||
|
@ -165,6 +165,7 @@ static void dumpShellType(Archive *fout, DumpOptions *dopt, ShellTypeInfo *stinf
|
||||
static void dumpProcLang(Archive *fout, DumpOptions *dopt, ProcLangInfo *plang);
|
||||
static void dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo);
|
||||
static void dumpCast(Archive *fout, DumpOptions *dopt, CastInfo *cast);
|
||||
static void dumpTransform(Archive *fout, DumpOptions *dopt, TransformInfo *transform);
|
||||
static void dumpOpr(Archive *fout, DumpOptions *dopt, OprInfo *oprinfo);
|
||||
static void dumpOpclass(Archive *fout, DumpOptions *dopt, OpclassInfo *opcinfo);
|
||||
static void dumpOpfamily(Archive *fout, DumpOptions *dopt, OpfamilyInfo *opfinfo);
|
||||
@ -6566,6 +6567,110 @@ getCasts(Archive *fout, DumpOptions *dopt, int *numCasts)
|
||||
return castinfo;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_language_name(Archive *fout, Oid langid)
|
||||
{
|
||||
PQExpBuffer query;
|
||||
PGresult *res;
|
||||
char *lanname;
|
||||
|
||||
query = createPQExpBuffer();
|
||||
appendPQExpBuffer(query, "SELECT lanname FROM pg_language WHERE oid = %u", langid);
|
||||
res = ExecuteSqlQueryForSingleRow(fout, query->data);
|
||||
lanname = pg_strdup(fmtId(PQgetvalue(res, 0, 0)));
|
||||
destroyPQExpBuffer(query);
|
||||
PQclear(res);
|
||||
|
||||
return lanname;
|
||||
}
|
||||
|
||||
/*
|
||||
* getTransforms
|
||||
* get basic information about every transform in the system
|
||||
*
|
||||
* numTransforms is set to the number of transforms read in
|
||||
*/
|
||||
TransformInfo *
|
||||
getTransforms(Archive *fout, int *numTransforms)
|
||||
{
|
||||
PGresult *res;
|
||||
int ntups;
|
||||
int i;
|
||||
PQExpBuffer query = createPQExpBuffer();
|
||||
TransformInfo *transforminfo;
|
||||
int i_tableoid;
|
||||
int i_oid;
|
||||
int i_trftype;
|
||||
int i_trflang;
|
||||
int i_trffromsql;
|
||||
int i_trftosql;
|
||||
|
||||
/* Transforms didn't exist pre-9.5 */
|
||||
if (fout->remoteVersion < 90500)
|
||||
{
|
||||
*numTransforms = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Make sure we are in proper schema */
|
||||
selectSourceSchema(fout, "pg_catalog");
|
||||
|
||||
appendPQExpBuffer(query, "SELECT tableoid, oid, "
|
||||
"trftype, trflang, trffromsql::oid, trftosql::oid "
|
||||
"FROM pg_transform "
|
||||
"ORDER BY 3,4");
|
||||
|
||||
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
|
||||
|
||||
ntups = PQntuples(res);
|
||||
|
||||
*numTransforms = ntups;
|
||||
|
||||
transforminfo = (TransformInfo *) pg_malloc(ntups * sizeof(TransformInfo));
|
||||
|
||||
i_tableoid = PQfnumber(res, "tableoid");
|
||||
i_oid = PQfnumber(res, "oid");
|
||||
i_trftype = PQfnumber(res, "trftype");
|
||||
i_trflang = PQfnumber(res, "trflang");
|
||||
i_trffromsql = PQfnumber(res, "trffromsql");
|
||||
i_trftosql = PQfnumber(res, "trftosql");
|
||||
|
||||
for (i = 0; i < ntups; i++)
|
||||
{
|
||||
PQExpBufferData namebuf;
|
||||
TypeInfo *typeInfo;
|
||||
char *lanname;
|
||||
|
||||
transforminfo[i].dobj.objType = DO_TRANSFORM;
|
||||
transforminfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
|
||||
transforminfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
|
||||
AssignDumpId(&transforminfo[i].dobj);
|
||||
transforminfo[i].trftype = atooid(PQgetvalue(res, i, i_trftype));
|
||||
transforminfo[i].trflang = atooid(PQgetvalue(res, i, i_trflang));
|
||||
transforminfo[i].trffromsql = atooid(PQgetvalue(res, i, i_trffromsql));
|
||||
transforminfo[i].trftosql = atooid(PQgetvalue(res, i, i_trftosql));
|
||||
|
||||
/*
|
||||
* Try to name transform as concatenation of type and language name.
|
||||
* This is only used for purposes of sorting. If we fail to find
|
||||
* either, the name will be an empty string.
|
||||
*/
|
||||
initPQExpBuffer(&namebuf);
|
||||
typeInfo = findTypeByOid(transforminfo[i].trftype);
|
||||
lanname = get_language_name(fout, transforminfo[i].trflang);
|
||||
if (typeInfo && lanname)
|
||||
appendPQExpBuffer(&namebuf, "%s %s",
|
||||
typeInfo->dobj.name, lanname);
|
||||
transforminfo[i].dobj.name = namebuf.data;
|
||||
}
|
||||
|
||||
PQclear(res);
|
||||
|
||||
destroyPQExpBuffer(query);
|
||||
|
||||
return transforminfo;
|
||||
}
|
||||
|
||||
/*
|
||||
* getTableAttrs -
|
||||
* for each interesting table, read info about its attributes
|
||||
@ -8182,6 +8287,9 @@ dumpDumpableObject(Archive *fout, DumpOptions *dopt, DumpableObject *dobj)
|
||||
case DO_CAST:
|
||||
dumpCast(fout, dopt, (CastInfo *) dobj);
|
||||
break;
|
||||
case DO_TRANSFORM:
|
||||
dumpTransform(fout, dopt, (TransformInfo *) dobj);
|
||||
break;
|
||||
case DO_TABLE_DATA:
|
||||
if (((TableDataInfo *) dobj)->tdtable->relkind == RELKIND_SEQUENCE)
|
||||
dumpSequenceData(fout, (TableDataInfo *) dobj);
|
||||
@ -9989,6 +10097,7 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
|
||||
char *proallargtypes;
|
||||
char *proargmodes;
|
||||
char *proargnames;
|
||||
char *protrftypes;
|
||||
char *proiswindow;
|
||||
char *provolatile;
|
||||
char *proisstrict;
|
||||
@ -10021,10 +10130,28 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
|
||||
selectSourceSchema(fout, finfo->dobj.namespace->dobj.name);
|
||||
|
||||
/* Fetch function-specific details */
|
||||
if (fout->remoteVersion >= 90200)
|
||||
if (fout->remoteVersion >= 90500)
|
||||
{
|
||||
/*
|
||||
* proleakproof was added at v9.2
|
||||
* protrftypes was added in 9.5
|
||||
*/
|
||||
appendPQExpBuffer(query,
|
||||
"SELECT proretset, prosrc, probin, "
|
||||
"pg_catalog.pg_get_function_arguments(oid) AS funcargs, "
|
||||
"pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs, "
|
||||
"pg_catalog.pg_get_function_result(oid) AS funcresult, "
|
||||
"array_to_string(protrftypes, ' ') AS protrftypes, "
|
||||
"proiswindow, provolatile, proisstrict, prosecdef, "
|
||||
"proleakproof, proconfig, procost, prorows, "
|
||||
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
|
||||
"FROM pg_catalog.pg_proc "
|
||||
"WHERE oid = '%u'::pg_catalog.oid",
|
||||
finfo->dobj.catId.oid);
|
||||
}
|
||||
else if (fout->remoteVersion >= 90200)
|
||||
{
|
||||
/*
|
||||
* proleakproof was added in 9.2
|
||||
*/
|
||||
appendPQExpBuffer(query,
|
||||
"SELECT proretset, prosrc, probin, "
|
||||
@ -10173,6 +10300,10 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
|
||||
proargnames = PQgetvalue(res, 0, PQfnumber(res, "proargnames"));
|
||||
funcargs = funciargs = funcresult = NULL;
|
||||
}
|
||||
if (PQfnumber(res, "protrftypes") != -1)
|
||||
protrftypes = PQgetvalue(res, 0, PQfnumber(res, "protrftypes"));
|
||||
else
|
||||
protrftypes = NULL;
|
||||
proiswindow = PQgetvalue(res, 0, PQfnumber(res, "proiswindow"));
|
||||
provolatile = PQgetvalue(res, 0, PQfnumber(res, "provolatile"));
|
||||
proisstrict = PQgetvalue(res, 0, PQfnumber(res, "proisstrict"));
|
||||
@ -10316,6 +10447,22 @@ dumpFunc(Archive *fout, DumpOptions *dopt, FuncInfo *finfo)
|
||||
|
||||
appendPQExpBuffer(q, "\n LANGUAGE %s", fmtId(lanname));
|
||||
|
||||
if (protrftypes != NULL && strcmp(protrftypes, "") != 0)
|
||||
{
|
||||
Oid *typeids = palloc(FUNC_MAX_ARGS * sizeof(Oid));
|
||||
int i;
|
||||
|
||||
appendPQExpBufferStr(q, " TRANSFORM ");
|
||||
parseOidArray(protrftypes, typeids, FUNC_MAX_ARGS);
|
||||
for (i = 0; typeids[i]; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
appendPQExpBufferStr(q, ", ");
|
||||
appendPQExpBuffer(q, "FOR TYPE %s",
|
||||
getFormattedTypeName(fout, typeids[i], zeroAsNone));
|
||||
}
|
||||
}
|
||||
|
||||
if (proiswindow[0] == 't')
|
||||
appendPQExpBufferStr(q, " WINDOW");
|
||||
|
||||
@ -10539,6 +10686,127 @@ dumpCast(Archive *fout, DumpOptions *dopt, CastInfo *cast)
|
||||
destroyPQExpBuffer(labelq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump a transform
|
||||
*/
|
||||
static void
|
||||
dumpTransform(Archive *fout, DumpOptions *dopt, TransformInfo *transform)
|
||||
{
|
||||
PQExpBuffer defqry;
|
||||
PQExpBuffer delqry;
|
||||
PQExpBuffer labelq;
|
||||
FuncInfo *fromsqlFuncInfo = NULL;
|
||||
FuncInfo *tosqlFuncInfo = NULL;
|
||||
char *lanname;
|
||||
|
||||
/* Skip if not to be dumped */
|
||||
if (!transform->dobj.dump || dopt->dataOnly)
|
||||
return;
|
||||
|
||||
/* Cannot dump if we don't have the transform functions' info */
|
||||
if (OidIsValid(transform->trffromsql))
|
||||
{
|
||||
fromsqlFuncInfo = findFuncByOid(transform->trffromsql);
|
||||
if (fromsqlFuncInfo == NULL)
|
||||
return;
|
||||
}
|
||||
if (OidIsValid(transform->trftosql))
|
||||
{
|
||||
tosqlFuncInfo = findFuncByOid(transform->trftosql);
|
||||
if (tosqlFuncInfo == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make sure we are in proper schema (needed for getFormattedTypeName) */
|
||||
selectSourceSchema(fout, "pg_catalog");
|
||||
|
||||
defqry = createPQExpBuffer();
|
||||
delqry = createPQExpBuffer();
|
||||
labelq = createPQExpBuffer();
|
||||
|
||||
lanname = get_language_name(fout, transform->trflang);
|
||||
|
||||
appendPQExpBuffer(delqry, "DROP TRANSFORM FOR %s LANGUAGE %s;\n",
|
||||
getFormattedTypeName(fout, transform->trftype, zeroAsNone),
|
||||
lanname);
|
||||
|
||||
appendPQExpBuffer(defqry, "CREATE TRANSFORM FOR %s LANGUAGE %s (",
|
||||
getFormattedTypeName(fout, transform->trftype, zeroAsNone),
|
||||
lanname);
|
||||
|
||||
if (!transform->trffromsql && !transform->trftosql)
|
||||
write_msg(NULL, "WARNING: bogus transform definition, at least one of trffromsql and trftosql should be nonzero\n");
|
||||
|
||||
if (transform->trffromsql)
|
||||
{
|
||||
if (fromsqlFuncInfo)
|
||||
{
|
||||
char *fsig = format_function_signature(fout, fromsqlFuncInfo, true);
|
||||
|
||||
/*
|
||||
* Always qualify the function name, in case it is not in
|
||||
* pg_catalog schema (format_function_signature won't qualify
|
||||
* it).
|
||||
*/
|
||||
appendPQExpBuffer(defqry, "FROM SQL WITH FUNCTION %s.%s",
|
||||
fmtId(fromsqlFuncInfo->dobj.namespace->dobj.name), fsig);
|
||||
free(fsig);
|
||||
}
|
||||
else
|
||||
write_msg(NULL, "WARNING: bogus value in pg_transform.trffromsql field\n");
|
||||
}
|
||||
|
||||
if (transform->trftosql)
|
||||
{
|
||||
if (transform->trffromsql)
|
||||
appendPQExpBuffer(defqry, ", ");
|
||||
|
||||
if (tosqlFuncInfo)
|
||||
{
|
||||
char *fsig = format_function_signature(fout, tosqlFuncInfo, true);
|
||||
|
||||
/*
|
||||
* Always qualify the function name, in case it is not in
|
||||
* pg_catalog schema (format_function_signature won't qualify
|
||||
* it).
|
||||
*/
|
||||
appendPQExpBuffer(defqry, "TO SQL WITH FUNCTION %s.%s",
|
||||
fmtId(tosqlFuncInfo->dobj.namespace->dobj.name), fsig);
|
||||
free(fsig);
|
||||
}
|
||||
else
|
||||
write_msg(NULL, "WARNING: bogus value in pg_transform.trftosql field\n");
|
||||
}
|
||||
|
||||
appendPQExpBuffer(defqry, ");\n");
|
||||
|
||||
appendPQExpBuffer(labelq, "TRANSFORM FOR %s LANGUAGE %s",
|
||||
getFormattedTypeName(fout, transform->trftype, zeroAsNone),
|
||||
lanname);
|
||||
|
||||
if (dopt->binary_upgrade)
|
||||
binary_upgrade_extension_member(defqry, &transform->dobj, labelq->data);
|
||||
|
||||
ArchiveEntry(fout, transform->dobj.catId, transform->dobj.dumpId,
|
||||
labelq->data,
|
||||
"pg_catalog", NULL, "",
|
||||
false, "TRANSFORM", SECTION_PRE_DATA,
|
||||
defqry->data, delqry->data, NULL,
|
||||
transform->dobj.dependencies, transform->dobj.nDeps,
|
||||
NULL, NULL);
|
||||
|
||||
/* Dump Transform Comments */
|
||||
dumpComment(fout, dopt, labelq->data,
|
||||
NULL, "",
|
||||
transform->dobj.catId, 0, transform->dobj.dumpId);
|
||||
|
||||
free(lanname);
|
||||
destroyPQExpBuffer(defqry);
|
||||
destroyPQExpBuffer(delqry);
|
||||
destroyPQExpBuffer(labelq);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* dumpOpr
|
||||
* write out a single operator definition
|
||||
@ -15658,6 +15926,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
|
||||
case DO_TSCONFIG:
|
||||
case DO_FDW:
|
||||
case DO_FOREIGN_SERVER:
|
||||
case DO_TRANSFORM:
|
||||
case DO_BLOB:
|
||||
/* Pre-data objects: must come before the pre-data boundary */
|
||||
addObjectDependency(preDataBound, dobj->dumpId);
|
||||
|
@ -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("");
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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 */
|
||||
|
47
src/include/catalog/pg_transform.h
Normal file
47
src/include/catalog/pg_transform.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pg_transform.h
|
||||
*
|
||||
* Copyright (c) 2012-2015, PostgreSQL Global Development Group
|
||||
*
|
||||
* src/include/catalog/pg_transform.h
|
||||
*
|
||||
* NOTES
|
||||
* the genbki.pl script reads this file and generates .bki
|
||||
* information from the DATA() statements.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef PG_TRANSFORM_H
|
||||
#define PG_TRANSFORM_H
|
||||
|
||||
#include "catalog/genbki.h"
|
||||
|
||||
/* ----------------
|
||||
* pg_transform definition. cpp turns this into
|
||||
* typedef struct FormData_pg_transform
|
||||
* ----------------
|
||||
*/
|
||||
#define TransformRelationId 3576
|
||||
|
||||
CATALOG(pg_transform,3576)
|
||||
{
|
||||
Oid trftype;
|
||||
Oid trflang;
|
||||
regproc trffromsql;
|
||||
regproc trftosql;
|
||||
} FormData_pg_transform;
|
||||
|
||||
typedef FormData_pg_transform *Form_pg_transform;
|
||||
|
||||
/* ----------------
|
||||
* compiler constants for pg_transform
|
||||
* ----------------
|
||||
*/
|
||||
#define Natts_pg_transform 4
|
||||
#define Anum_pg_transform_trftype 1
|
||||
#define Anum_pg_transform_trflang 2
|
||||
#define Anum_pg_transform_trffromsql 3
|
||||
#define Anum_pg_transform_trftosql 4
|
||||
|
||||
#endif /* PG_TRANSFORM_H */
|
@ -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;
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef PL_PERL_HELPERS_H
|
||||
#define PL_PERL_HELPERS_H
|
||||
|
||||
#include "mb/pg_wchar.h"
|
||||
|
||||
/*
|
||||
* convert from utf8 to database encoding
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -29,8 +29,8 @@
|
||||
|
||||
|
||||
/* I/O function caching */
|
||||
static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup);
|
||||
static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup);
|
||||
static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
|
||||
static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *trftypes);
|
||||
|
||||
/* conversion from Datums to Python objects */
|
||||
static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
|
||||
@ -43,6 +43,7 @@ static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
|
||||
static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d);
|
||||
static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
|
||||
static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d);
|
||||
static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d);
|
||||
static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
|
||||
|
||||
/* conversion from Python objects to Datums */
|
||||
@ -50,6 +51,7 @@ static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
||||
static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
||||
static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
||||
static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
||||
static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
||||
static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
||||
|
||||
/* conversion from Python objects to composite Datums (used by triggers and SRFs) */
|
||||
@ -102,27 +104,28 @@ PLy_typeinfo_dealloc(PLyTypeInfo *arg)
|
||||
* PostgreSQL, and vice versa.
|
||||
*/
|
||||
void
|
||||
PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup)
|
||||
PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes)
|
||||
{
|
||||
if (arg->is_rowtype > 0)
|
||||
elog(ERROR, "PLyTypeInfo struct is initialized for Tuple");
|
||||
arg->is_rowtype = 0;
|
||||
PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup);
|
||||
PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup, langid, trftypes);
|
||||
}
|
||||
|
||||
void
|
||||
PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup)
|
||||
PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes)
|
||||
{
|
||||
if (arg->is_rowtype > 0)
|
||||
elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple");
|
||||
arg->is_rowtype = 0;
|
||||
PLy_output_datum_func2(&(arg->out.d), typeTup);
|
||||
PLy_output_datum_func2(&(arg->out.d), typeTup, langid, trftypes);
|
||||
}
|
||||
|
||||
void
|
||||
PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
|
||||
{
|
||||
int i;
|
||||
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
|
||||
|
||||
if (arg->is_rowtype == 0)
|
||||
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
|
||||
@ -181,7 +184,9 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
|
||||
|
||||
PLy_input_datum_func2(&(arg->in.r.atts[i]),
|
||||
desc->attrs[i]->atttypid,
|
||||
typeTup);
|
||||
typeTup,
|
||||
exec_ctx->curr_proc->langid,
|
||||
exec_ctx->curr_proc->trftypes);
|
||||
|
||||
ReleaseSysCache(typeTup);
|
||||
}
|
||||
@ -191,6 +196,7 @@ void
|
||||
PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
|
||||
{
|
||||
int i;
|
||||
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
|
||||
|
||||
if (arg->is_rowtype == 0)
|
||||
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
|
||||
@ -243,7 +249,9 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
|
||||
elog(ERROR, "cache lookup failed for type %u",
|
||||
desc->attrs[i]->atttypid);
|
||||
|
||||
PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup);
|
||||
PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup,
|
||||
exec_ctx->curr_proc->langid,
|
||||
exec_ctx->curr_proc->trftypes);
|
||||
|
||||
ReleaseSysCache(typeTup);
|
||||
}
|
||||
@ -362,10 +370,12 @@ PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
|
||||
}
|
||||
|
||||
static void
|
||||
PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
|
||||
PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *trftypes)
|
||||
{
|
||||
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
||||
Oid element_type;
|
||||
Oid base_type;
|
||||
Oid funcid;
|
||||
|
||||
perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
|
||||
arg->typoid = HeapTupleGetOid(typeTup);
|
||||
@ -374,12 +384,24 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
|
||||
arg->typbyval = typeStruct->typbyval;
|
||||
|
||||
element_type = get_base_element_type(arg->typoid);
|
||||
base_type = getBaseType(element_type ? element_type : arg->typoid);
|
||||
|
||||
/*
|
||||
* Select a conversion function to convert Python objects to PostgreSQL
|
||||
* datums. Most data types can go through the generic function.
|
||||
* datums.
|
||||
*/
|
||||
switch (getBaseType(element_type ? element_type : arg->typoid))
|
||||
|
||||
if ((funcid = get_transform_tosql(base_type, langid, trftypes)))
|
||||
{
|
||||
arg->func = PLyObject_ToTransform;
|
||||
perm_fmgr_info(funcid, &arg->typtransform);
|
||||
}
|
||||
else if (typeStruct->typtype == TYPTYPE_COMPOSITE)
|
||||
{
|
||||
arg->func = PLyObject_ToComposite;
|
||||
}
|
||||
else
|
||||
switch (base_type)
|
||||
{
|
||||
case BOOLOID:
|
||||
arg->func = PLyObject_ToBool;
|
||||
@ -392,12 +414,6 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Composite types need their own input routine, though */
|
||||
if (typeStruct->typtype == TYPTYPE_COMPOSITE)
|
||||
{
|
||||
arg->func = PLyObject_ToComposite;
|
||||
}
|
||||
|
||||
if (element_type)
|
||||
{
|
||||
char dummy_delim;
|
||||
@ -408,6 +424,7 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
|
||||
|
||||
arg->elm = PLy_malloc0(sizeof(*arg->elm));
|
||||
arg->elm->func = arg->func;
|
||||
arg->elm->typtransform = arg->typtransform;
|
||||
arg->func = PLySequence_ToArray;
|
||||
|
||||
arg->elm->typoid = element_type;
|
||||
@ -420,12 +437,12 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
|
||||
}
|
||||
|
||||
static void
|
||||
PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
|
||||
PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes)
|
||||
{
|
||||
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
||||
|
||||
/* It's safe to handle domains of array types as its base array type. */
|
||||
Oid element_type = get_base_element_type(typeOid);
|
||||
Oid element_type;
|
||||
Oid base_type;
|
||||
Oid funcid;
|
||||
|
||||
/* Get the type's conversion information */
|
||||
perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
|
||||
@ -437,7 +454,17 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
|
||||
arg->typalign = typeStruct->typalign;
|
||||
|
||||
/* Determine which kind of Python object we will convert to */
|
||||
switch (getBaseType(element_type ? element_type : typeOid))
|
||||
|
||||
element_type = get_base_element_type(typeOid);
|
||||
base_type = getBaseType(element_type ? element_type : typeOid);
|
||||
|
||||
if ((funcid = get_transform_fromsql(base_type, langid, trftypes)))
|
||||
{
|
||||
arg->func = PLyObject_FromTransform;
|
||||
perm_fmgr_info(funcid, &arg->typtransform);
|
||||
}
|
||||
else
|
||||
switch (base_type)
|
||||
{
|
||||
case BOOLOID:
|
||||
arg->func = PLyBool_FromBool;
|
||||
@ -478,6 +505,7 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
|
||||
|
||||
arg->elm = PLy_malloc0(sizeof(*arg->elm));
|
||||
arg->elm->func = arg->func;
|
||||
arg->elm->typtransform = arg->typtransform;
|
||||
arg->func = PLyList_FromArray;
|
||||
arg->elm->typoid = element_type;
|
||||
arg->elm->typmod = -1;
|
||||
@ -596,6 +624,12 @@ PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
|
||||
return r;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
PLyObject_FromTransform(PLyDatumToOb *arg, Datum d)
|
||||
{
|
||||
return (PyObject *) DatumGetPointer(FunctionCall1(&arg->typtransform, d));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
PLyList_FromArray(PLyDatumToOb *arg, Datum d)
|
||||
{
|
||||
@ -747,16 +781,15 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
||||
|
||||
|
||||
/*
|
||||
* Generic conversion function: Convert PyObject to cstring and
|
||||
* cstring into PostgreSQL type.
|
||||
* Convert Python object to C string in server encoding.
|
||||
*/
|
||||
static Datum
|
||||
PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
||||
char *
|
||||
PLyObject_AsString(PyObject *plrv)
|
||||
{
|
||||
PyObject *volatile plrv_bo = NULL;
|
||||
Datum rv;
|
||||
|
||||
Assert(plrv != Py_None);
|
||||
PyObject *plrv_bo;
|
||||
char *plrv_sc;
|
||||
size_t plen;
|
||||
size_t slen;
|
||||
|
||||
if (PyUnicode_Check(plrv))
|
||||
plrv_bo = PLyUnicode_Bytes(plrv);
|
||||
@ -786,36 +819,47 @@ PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
||||
if (!plrv_bo)
|
||||
PLy_elog(ERROR, "could not create string representation of Python object");
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
char *plrv_sc = PyBytes_AsString(plrv_bo);
|
||||
size_t plen = PyBytes_Size(plrv_bo);
|
||||
size_t slen = strlen(plrv_sc);
|
||||
|
||||
if (slen < plen)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
|
||||
else if (slen > plen)
|
||||
elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
|
||||
pg_verifymbstr(plrv_sc, slen, false);
|
||||
rv = InputFunctionCall(&arg->typfunc,
|
||||
plrv_sc,
|
||||
arg->typioparam,
|
||||
typmod);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
Py_XDECREF(plrv_bo);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
plrv_sc = pstrdup(PyBytes_AsString(plrv_bo));
|
||||
plen = PyBytes_Size(plrv_bo);
|
||||
slen = strlen(plrv_sc);
|
||||
|
||||
Py_XDECREF(plrv_bo);
|
||||
|
||||
return rv;
|
||||
if (slen < plen)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
|
||||
else if (slen > plen)
|
||||
elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
|
||||
pg_verifymbstr(plrv_sc, slen, false);
|
||||
|
||||
return plrv_sc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generic conversion function: Convert PyObject to cstring and
|
||||
* cstring into PostgreSQL type.
|
||||
*/
|
||||
static Datum
|
||||
PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
||||
{
|
||||
Assert(plrv != Py_None);
|
||||
|
||||
return InputFunctionCall(&arg->typfunc,
|
||||
PLyObject_AsString(plrv),
|
||||
arg->typioparam,
|
||||
typmod);
|
||||
}
|
||||
|
||||
|
||||
static Datum
|
||||
PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
||||
{
|
||||
return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv));
|
||||
}
|
||||
|
||||
|
||||
static Datum
|
||||
PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
||||
{
|
||||
@ -869,12 +913,15 @@ static Datum
|
||||
PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
|
||||
{
|
||||
HeapTuple typeTup;
|
||||
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
|
||||
|
||||
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid));
|
||||
if (!HeapTupleIsValid(typeTup))
|
||||
elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid);
|
||||
|
||||
PLy_output_datum_func2(&info->out.d, typeTup);
|
||||
PLy_output_datum_func2(&info->out.d, typeTup,
|
||||
exec_ctx->curr_proc->langid,
|
||||
exec_ctx->curr_proc->trftypes);
|
||||
|
||||
ReleaseSysCache(typeTup);
|
||||
|
||||
|
@ -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);
|
||||
utf8string = pg_server_to_any(s, size, PG_UTF8);
|
||||
|
||||
o = PyUnicode_FromString(utf8string);
|
||||
|
||||
if (utf8string != s)
|
||||
if (utf8string == s)
|
||||
{
|
||||
o = PyUnicode_FromStringAndSize(s, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
o = PyUnicode_FromString(utf8string);
|
||||
pfree(utf8string);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
PLyUnicode_FromString(const char *s)
|
||||
{
|
||||
return PLyUnicode_FromStringAndSize(s, strlen(s));
|
||||
}
|
||||
|
||||
#endif /* PY_MAJOR_VERSION >= 3 */
|
||||
|
@ -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
|
||||
|
||||
/*
|
||||
|
35
src/pl/plpython/regress-python3-mangle.mk
Normal file
35
src/pl/plpython/regress-python3-mangle.mk
Normal file
@ -0,0 +1,35 @@
|
||||
ifeq ($(python_majorversion),3)
|
||||
# Adjust regression tests for Python 3 compatibility
|
||||
#
|
||||
# Mention those regression test files that need to be mangled in the
|
||||
# variable REGRESS_PLPYTHON3_MANGLE. They will be copied to a
|
||||
# subdirectory python3/ and have their Python syntax and other bits
|
||||
# adjusted to work with Python 3.
|
||||
|
||||
# Note that the order of the tests needs to be preserved in this
|
||||
# expression.
|
||||
REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_MANGLE)),python3/$(test),$(test)))
|
||||
|
||||
.PHONY: pgregress-python3-mangle
|
||||
pgregress-python3-mangle:
|
||||
$(MKDIR_P) sql/python3 expected/python3 results/python3
|
||||
for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \
|
||||
sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \
|
||||
-e "s/<type 'exceptions\.\([[:alpha:]]*\)'>/<class '\1'>/g" \
|
||||
-e "s/<type 'long'>/<class 'int'>/g" \
|
||||
-e "s/\([0-9][0-9]*\)L/\1/g" \
|
||||
-e 's/\([ [{]\)u"/\1"/g' \
|
||||
-e "s/\([ [{]\)u'/\1'/g" \
|
||||
-e "s/def next/def __next__/g" \
|
||||
-e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \
|
||||
-e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \
|
||||
-e "s/EXTENSION \([^ ]*_\)*plpythonu/EXTENSION \1plpython3u/g" \
|
||||
-e "s/EXTENSION \([^ ]*_\)*plpython2u/EXTENSION \1plpython3u/g" \
|
||||
$$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \
|
||||
done
|
||||
|
||||
check installcheck: pgregress-python3-mangle
|
||||
|
||||
pg_regress_clean_files += sql/python3/ expected/python3/ results/python3/
|
||||
|
||||
endif # Python 3
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user