mirror of
https://github.com/postgres/postgres.git
synced 2025-06-13 07:41:39 +03:00
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:
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;
|
Reference in New Issue
Block a user