mirror of https://github.com/postgres/postgres
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 Freundpull/14/head
parent
f320cbb615
commit
cac7658205
@ -0,0 +1,4 @@ |
|||||||
|
# Generated subdirectories |
||||||
|
/log/ |
||||||
|
/results/ |
||||||
|
/tmp_check/ |
@ -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 |
@ -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; |
@ -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; |
@ -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) |
||||||
|
); |
@ -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); |
||||||
|
} |
@ -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' |
@ -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) |
||||||
|
); |
@ -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' |
@ -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; |
@ -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; |
@ -0,0 +1,6 @@ |
|||||||
|
# Generated subdirectories |
||||||
|
/expected/python3/ |
||||||
|
/log/ |
||||||
|
/results/ |
||||||
|
/sql/python3/ |
||||||
|
/tmp_check/ |
@ -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 |
@ -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) |
||||||
|
|
@ -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); |
||||||
|
} |
@ -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'; |
@ -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' |
@ -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'; |
@ -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' |
@ -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'; |
@ -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' |
@ -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; |
@ -0,0 +1,6 @@ |
|||||||
|
# Generated subdirectories |
||||||
|
/expected/python3/ |
||||||
|
/log/ |
||||||
|
/results/ |
||||||
|
/sql/python3/ |
||||||
|
/tmp_check/ |
@ -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 |
@ -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" |
@ -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); |
||||||
|
} |
@ -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) |
||||||
|
); |
@ -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' |
@ -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) |
||||||
|
); |
@ -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' |
@ -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) |
||||||
|
); |
@ -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' |
@ -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(); |
@ -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> |
@ -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> |
File diff suppressed because it is too large
Load Diff
@ -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 */ |
@ -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