diff options
Diffstat (limited to 'contrib/hstore_plpython')
-rw-r--r-- | contrib/hstore_plpython/.gitignore | 6 | ||||
-rw-r--r-- | contrib/hstore_plpython/Makefile | 31 | ||||
-rw-r--r-- | contrib/hstore_plpython/expected/hstore_plpython.out | 132 | ||||
-rw-r--r-- | contrib/hstore_plpython/hstore_plpython.c | 116 | ||||
-rw-r--r-- | contrib/hstore_plpython/hstore_plpython2u--1.0.sql | 19 | ||||
-rw-r--r-- | contrib/hstore_plpython/hstore_plpython2u.control | 6 | ||||
-rw-r--r-- | contrib/hstore_plpython/hstore_plpython3u--1.0.sql | 19 | ||||
-rw-r--r-- | contrib/hstore_plpython/hstore_plpython3u.control | 6 | ||||
-rw-r--r-- | contrib/hstore_plpython/hstore_plpythonu--1.0.sql | 19 | ||||
-rw-r--r-- | contrib/hstore_plpython/hstore_plpythonu.control | 6 | ||||
-rw-r--r-- | contrib/hstore_plpython/sql/hstore_plpython.sql | 103 |
11 files changed, 463 insertions, 0 deletions
diff --git a/contrib/hstore_plpython/.gitignore b/contrib/hstore_plpython/.gitignore new file mode 100644 index 00000000000..ce6fab94a0f --- /dev/null +++ b/contrib/hstore_plpython/.gitignore @@ -0,0 +1,6 @@ +# Generated subdirectories +/expected/python3/ +/log/ +/results/ +/sql/python3/ +/tmp_check/ diff --git a/contrib/hstore_plpython/Makefile b/contrib/hstore_plpython/Makefile new file mode 100644 index 00000000000..f5a6a882b5f --- /dev/null +++ b/contrib/hstore_plpython/Makefile @@ -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 diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out b/contrib/hstore_plpython/expected/hstore_plpython.out new file mode 100644 index 00000000000..488f01d40be --- /dev/null +++ b/contrib/hstore_plpython/expected/hstore_plpython.out @@ -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) + diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c new file mode 100644 index 00000000000..92cd4f800f6 --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython.c @@ -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); +} diff --git a/contrib/hstore_plpython/hstore_plpython2u--1.0.sql b/contrib/hstore_plpython/hstore_plpython2u--1.0.sql new file mode 100644 index 00000000000..c998de51c96 --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython2u--1.0.sql @@ -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'; diff --git a/contrib/hstore_plpython/hstore_plpython2u.control b/contrib/hstore_plpython/hstore_plpython2u.control new file mode 100644 index 00000000000..ed905671123 --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython2u.control @@ -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' diff --git a/contrib/hstore_plpython/hstore_plpython3u--1.0.sql b/contrib/hstore_plpython/hstore_plpython3u--1.0.sql new file mode 100644 index 00000000000..61d0e47793c --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython3u--1.0.sql @@ -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'; diff --git a/contrib/hstore_plpython/hstore_plpython3u.control b/contrib/hstore_plpython/hstore_plpython3u.control new file mode 100644 index 00000000000..d86f38e9a7b --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython3u.control @@ -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' diff --git a/contrib/hstore_plpython/hstore_plpythonu--1.0.sql b/contrib/hstore_plpython/hstore_plpythonu--1.0.sql new file mode 100644 index 00000000000..6acb97aab9e --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpythonu--1.0.sql @@ -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'; diff --git a/contrib/hstore_plpython/hstore_plpythonu.control b/contrib/hstore_plpython/hstore_plpythonu.control new file mode 100644 index 00000000000..8e9b35e43bf --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpythonu.control @@ -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' diff --git a/contrib/hstore_plpython/sql/hstore_plpython.sql b/contrib/hstore_plpython/sql/hstore_plpython.sql new file mode 100644 index 00000000000..2d8aab1e7ab --- /dev/null +++ b/contrib/hstore_plpython/sql/hstore_plpython.sql @@ -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; |