mirror of https://github.com/postgres/postgres
Add a new contrib module jsonb_plpython that provide a transform between jsonb and PL/Python. jsonb values are converted to appropriate Python types such as dicts and lists, and vice versa. Author: Anthony Bykov <a.bykov@postgrespro.ru> Reviewed-by: Aleksander Alekseev <a.alekseev@postgrespro.ru> Reviewed-by: Nikita Glukhov <n.gluhov@postgrespro.ru>pull/31/merge
parent
a437551a22
commit
3f44e3db72
@ -0,0 +1,6 @@ |
||||
# Generated subdirectories |
||||
/expected/python3/ |
||||
/log/ |
||||
/results/ |
||||
/sql/python3/ |
||||
/tmp_check/ |
@ -0,0 +1,39 @@ |
||||
# contrib/jsonb_plpython/Makefile
|
||||
|
||||
MODULE_big = jsonb_plpython$(python_majorversion)
|
||||
OBJS = jsonb_plpython.o $(WIN32RES)
|
||||
PGFILEDESC = "jsonb_plpython - transform between jsonb and plpythonu"
|
||||
|
||||
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -DPLPYTHON_LIBNAME='"plpython$(python_majorversion)"'
|
||||
|
||||
EXTENSION = jsonb_plpythonu jsonb_plpython2u jsonb_plpython3u
|
||||
DATA = jsonb_plpythonu--1.0.sql jsonb_plpython2u--1.0.sql jsonb_plpython3u--1.0.sql
|
||||
|
||||
REGRESS = jsonb_plpython
|
||||
REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
|
||||
|
||||
ifdef USE_PGXS |
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS) |
||||
else |
||||
subdir = contrib/jsonb_plpython
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
include $(top_srcdir)/contrib/contrib-global.mk |
||||
endif |
||||
|
||||
# We must link libpython explicitly
|
||||
ifeq ($(PORTNAME), win32) |
||||
# ... see silliness in plpython Makefile ...
|
||||
SHLIB_LINK += $(sort $(wildcard ../../src/pl/plpython/libpython*.a))
|
||||
else |
||||
rpathdir = $(python_libdir)
|
||||
SHLIB_LINK += $(python_libspec) $(python_additional_libs)
|
||||
endif |
||||
|
||||
ifeq ($(python_majorversion),2) |
||||
REGRESS_OPTS += --load-extension=plpythonu --load-extension=jsonb_plpythonu
|
||||
endif |
||||
|
||||
include $(top_srcdir)/src/pl/plpython/regress-python3-mangle.mk |
@ -0,0 +1,347 @@ |
||||
CREATE EXTENSION jsonb_plpython2u CASCADE; |
||||
NOTICE: installing required extension "plpython2u" |
||||
-- test jsonb -> python dict |
||||
CREATE FUNCTION test1(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, dict) |
||||
plpy.info(sorted(val.items())) |
||||
return len(val) |
||||
$$; |
||||
SELECT test1('{"a": 1, "c": "NULL"}'::jsonb); |
||||
INFO: [('a', Decimal('1')), ('c', 'NULL')] |
||||
test1 |
||||
------- |
||||
2 |
||||
(1 row) |
||||
|
||||
-- test jsonb -> python dict |
||||
-- complex dict with dicts as value |
||||
CREATE FUNCTION test1complex(val jsonb) RETURNS int |
||||
LANGUAGE plpython2u |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, dict) |
||||
assert(val == {"d": {"d": 1}}) |
||||
return len(val) |
||||
$$; |
||||
SELECT test1complex('{"d": {"d": 1}}'::jsonb); |
||||
test1complex |
||||
-------------- |
||||
1 |
||||
(1 row) |
||||
|
||||
-- test jsonb[] -> python dict |
||||
-- dict with array as value |
||||
CREATE FUNCTION test1arr(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, dict) |
||||
assert(val == {"d": [12, 1]}) |
||||
return len(val) |
||||
$$; |
||||
SELECT test1arr('{"d":[12, 1]}'::jsonb); |
||||
test1arr |
||||
---------- |
||||
1 |
||||
(1 row) |
||||
|
||||
-- test jsonb[] -> python list |
||||
-- simple list |
||||
CREATE FUNCTION test2arr(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, list) |
||||
assert(val == [12, 1]) |
||||
return len(val) |
||||
$$; |
||||
SELECT test2arr('[12, 1]'::jsonb); |
||||
test2arr |
||||
---------- |
||||
2 |
||||
(1 row) |
||||
|
||||
-- test jsonb[] -> python list |
||||
-- array of dicts |
||||
CREATE FUNCTION test3arr(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, list) |
||||
assert(val == [{"a": 1,"b": 2}, {"c": 3,"d": 4}]) |
||||
return len(val) |
||||
$$; |
||||
SELECT test3arr('[{"a": 1, "b": 2}, {"c": 3,"d": 4}]'::jsonb); |
||||
test3arr |
||||
---------- |
||||
2 |
||||
(1 row) |
||||
|
||||
-- test jsonb int -> python int |
||||
CREATE FUNCTION test1int(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert(val == 1) |
||||
return val |
||||
$$; |
||||
SELECT test1int('1'::jsonb); |
||||
test1int |
||||
---------- |
||||
1 |
||||
(1 row) |
||||
|
||||
-- test jsonb string -> python string |
||||
CREATE FUNCTION test1string(val jsonb) RETURNS text |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert(val == "a") |
||||
return val |
||||
$$; |
||||
SELECT test1string('"a"'::jsonb); |
||||
test1string |
||||
------------- |
||||
a |
||||
(1 row) |
||||
|
||||
-- test jsonb null -> python None |
||||
CREATE FUNCTION test1null(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert(val == None) |
||||
return 1 |
||||
$$; |
||||
SELECT test1null('null'::jsonb); |
||||
test1null |
||||
----------- |
||||
1 |
||||
(1 row) |
||||
|
||||
-- test python -> jsonb |
||||
CREATE FUNCTION roundtrip(val jsonb) RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
as $$ |
||||
return val |
||||
$$; |
||||
SELECT roundtrip('null'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
|
||||
(1 row) |
||||
|
||||
SELECT roundtrip('1'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
1 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('1234567890.0987654321'::jsonb); |
||||
roundtrip |
||||
----------------------- |
||||
1234567890.0987654321 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('-1234567890.0987654321'::jsonb); |
||||
roundtrip |
||||
------------------------ |
||||
-1234567890.0987654321 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('true'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
true |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('"string"'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
"string" |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": null}'::jsonb); |
||||
roundtrip |
||||
------------- |
||||
{"1": null} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": 1}'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
{"1": 1} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": true}'::jsonb); |
||||
roundtrip |
||||
------------- |
||||
{"1": true} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": "string"}'::jsonb); |
||||
roundtrip |
||||
----------------- |
||||
{"1": "string"} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[null]'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
[null] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[1]'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
[1] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[true]'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
[true] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('["string"]'::jsonb); |
||||
roundtrip |
||||
------------ |
||||
["string"] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[null, 1]'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
[null, 1] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[1, true]'::jsonb); |
||||
roundtrip |
||||
----------- |
||||
[1, true] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[true, "string"]'::jsonb); |
||||
roundtrip |
||||
------------------ |
||||
[true, "string"] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('["string", "string2"]'::jsonb); |
||||
roundtrip |
||||
----------------------- |
||||
["string", "string2"] |
||||
(1 row) |
||||
|
||||
-- test python infinity -> jsonb |
||||
CREATE FUNCTION test1inf() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = float('inf') |
||||
print(x) |
||||
return x |
||||
$$; |
||||
SELECT test1inf(); |
||||
ERROR: could not convert value "inf" to jsonb |
||||
CONTEXT: while creating return value |
||||
PL/Python function "test1inf" |
||||
-- test python -infinity -> jsonb |
||||
CREATE FUNCTION test2inf() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = float('-inf') |
||||
print(x) |
||||
return x |
||||
$$; |
||||
SELECT test2inf(); |
||||
ERROR: could not convert value "-inf" to jsonb |
||||
CONTEXT: while creating return value |
||||
PL/Python function "test2inf" |
||||
-- test python NaN -> jsonb |
||||
CREATE FUNCTION test1nan() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = float('nan') |
||||
return x |
||||
$$; |
||||
SELECT test1nan(); |
||||
test1nan |
||||
---------- |
||||
NaN |
||||
(1 row) |
||||
|
||||
-- complex numbers -> jsonb |
||||
CREATE FUNCTION testComplexNumbers() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = 1 + 2j |
||||
return x |
||||
$$; |
||||
SELECT testComplexNumbers(); |
||||
ERROR: could not convert value "(1+2j)" to jsonb |
||||
CONTEXT: while creating return value |
||||
PL/Python function "testcomplexnumbers" |
||||
-- range -> jsonb |
||||
CREATE FUNCTION testRange() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = range(3) |
||||
return x |
||||
$$; |
||||
SELECT testRange(); |
||||
testrange |
||||
----------- |
||||
[0, 1, 2] |
||||
(1 row) |
||||
|
||||
-- 0xff -> jsonb |
||||
CREATE FUNCTION testDecimal() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = 0xff |
||||
return x |
||||
$$; |
||||
SELECT testDecimal(); |
||||
testdecimal |
||||
------------- |
||||
255 |
||||
(1 row) |
||||
|
||||
-- tuple -> jsonb |
||||
CREATE FUNCTION testTuple() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = (1, 'String', None) |
||||
return x |
||||
$$; |
||||
SELECT testTuple(); |
||||
testtuple |
||||
--------------------- |
||||
[1, "String", null] |
||||
(1 row) |
||||
|
||||
-- interesting dict -> jsonb |
||||
CREATE FUNCTION test_dict1() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = {"a": 1, None: 2, 33: 3} |
||||
return x |
||||
$$; |
||||
SELECT test_dict1(); |
||||
test_dict1 |
||||
-------------------------- |
||||
{"": 2, "a": 1, "33": 3} |
||||
(1 row) |
||||
|
@ -0,0 +1,453 @@ |
||||
#include "postgres.h" |
||||
|
||||
#include "plpython.h" |
||||
#include "plpy_elog.h" |
||||
#include "plpy_typeio.h" |
||||
#include "utils/jsonb.h" |
||||
#include "utils/fmgrprotos.h" |
||||
|
||||
PG_MODULE_MAGIC; |
||||
|
||||
void _PG_init(void); |
||||
|
||||
/* for PLyObject_AsString in plpy_typeio.c */ |
||||
typedef char *(*PLyObject_AsString_t) (PyObject *plrv); |
||||
static PLyObject_AsString_t PLyObject_AsString_p; |
||||
|
||||
typedef void (*PLy_elog_impl_t) (int elevel, const char *fmt,...); |
||||
static PLy_elog_impl_t PLy_elog_impl_p; |
||||
|
||||
/*
|
||||
* decimal_constructor is a function from python library and used |
||||
* for transforming strings into python decimal type |
||||
*/ |
||||
static PyObject *decimal_constructor; |
||||
|
||||
static PyObject *PLyObject_FromJsonbContainer(JsonbContainer *jsonb); |
||||
static JsonbValue *PLyObject_ToJsonbValue(PyObject *obj, |
||||
JsonbParseState **jsonb_state, bool is_elem); |
||||
|
||||
#if PY_MAJOR_VERSION >= 3 |
||||
typedef PyObject *(*PLyUnicode_FromStringAndSize_t) |
||||
(const char *s, Py_ssize_t size); |
||||
static PLyUnicode_FromStringAndSize_t PLyUnicode_FromStringAndSize_p; |
||||
#endif |
||||
|
||||
/*
|
||||
* Module initialize function: fetch function pointers for cross-module calls. |
||||
*/ |
||||
void |
||||
_PG_init(void) |
||||
{ |
||||
/* Asserts verify that typedefs above match original declarations */ |
||||
AssertVariableIsOfType(&PLyObject_AsString, PLyObject_AsString_t); |
||||
PLyObject_AsString_p = (PLyObject_AsString_t) |
||||
load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyObject_AsString", |
||||
true, NULL); |
||||
#if PY_MAJOR_VERSION >= 3 |
||||
AssertVariableIsOfType(&PLyUnicode_FromStringAndSize, PLyUnicode_FromStringAndSize_t); |
||||
PLyUnicode_FromStringAndSize_p = (PLyUnicode_FromStringAndSize_t) |
||||
load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize", |
||||
true, NULL); |
||||
#endif |
||||
|
||||
AssertVariableIsOfType(&PLy_elog_impl, PLy_elog_impl_t); |
||||
PLy_elog_impl_p = (PLy_elog_impl_t) |
||||
load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLy_elog_impl", |
||||
true, NULL); |
||||
} |
||||
|
||||
/* These defines must be after the _PG_init */ |
||||
#define PLyObject_AsString (PLyObject_AsString_p) |
||||
#define PLyUnicode_FromStringAndSize (PLyUnicode_FromStringAndSize_p) |
||||
#undef PLy_elog |
||||
#define PLy_elog (PLy_elog_impl_p) |
||||
|
||||
/*
|
||||
* PLyString_FromJsonbValue |
||||
* |
||||
* Transform string JsonbValue to Python string. |
||||
*/ |
||||
static PyObject * |
||||
PLyString_FromJsonbValue(JsonbValue *jbv) |
||||
{ |
||||
Assert(jbv->type == jbvString); |
||||
|
||||
return PyString_FromStringAndSize(jbv->val.string.val, jbv->val.string.len); |
||||
} |
||||
|
||||
/*
|
||||
* PLyString_ToJsonbValue |
||||
* |
||||
* Transform Python string to JsonbValue. |
||||
*/ |
||||
static void |
||||
PLyString_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem) |
||||
{ |
||||
jbvElem->type = jbvString; |
||||
jbvElem->val.string.val = PLyObject_AsString(obj); |
||||
jbvElem->val.string.len = strlen(jbvElem->val.string.val); |
||||
} |
||||
|
||||
/*
|
||||
* PLyObject_FromJsonbValue |
||||
* |
||||
* Transform JsonbValue to PyObject. |
||||
*/ |
||||
static PyObject * |
||||
PLyObject_FromJsonbValue(JsonbValue *jsonbValue) |
||||
{ |
||||
switch (jsonbValue->type) |
||||
{ |
||||
case jbvNull: |
||||
Py_RETURN_NONE; |
||||
|
||||
case jbvBinary: |
||||
return PLyObject_FromJsonbContainer(jsonbValue->val.binary.data); |
||||
|
||||
case jbvNumeric: |
||||
{ |
||||
Datum num; |
||||
char *str; |
||||
|
||||
num = NumericGetDatum(jsonbValue->val.numeric); |
||||
str = DatumGetCString(DirectFunctionCall1(numeric_out, num)); |
||||
|
||||
return PyObject_CallFunction(decimal_constructor, "s", str); |
||||
} |
||||
|
||||
case jbvString: |
||||
return PLyString_FromJsonbValue(jsonbValue); |
||||
|
||||
case jbvBool: |
||||
if (jsonbValue->val.boolean) |
||||
Py_RETURN_TRUE; |
||||
else |
||||
Py_RETURN_FALSE; |
||||
|
||||
default: |
||||
elog(ERROR, "unexpected jsonb value type: %d", jsonbValue->type); |
||||
return NULL; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* PLyObject_FromJsonb |
||||
* |
||||
* Transform JsonbContainer to PyObject. |
||||
*/ |
||||
static PyObject * |
||||
PLyObject_FromJsonbContainer(JsonbContainer *jsonb) |
||||
{ |
||||
JsonbIteratorToken r; |
||||
JsonbValue v; |
||||
JsonbIterator *it; |
||||
PyObject *result; |
||||
|
||||
it = JsonbIteratorInit(jsonb); |
||||
r = JsonbIteratorNext(&it, &v, true); |
||||
|
||||
switch (r) |
||||
{ |
||||
case WJB_BEGIN_ARRAY: |
||||
if (v.val.array.rawScalar) |
||||
{ |
||||
JsonbValue tmp; |
||||
|
||||
if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM || |
||||
(r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY || |
||||
(r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE) |
||||
elog(ERROR, "unexpected jsonb token: %d", r); |
||||
|
||||
result = PLyObject_FromJsonbValue(&v); |
||||
} |
||||
else |
||||
{ |
||||
/* array in v */ |
||||
result = PyList_New(0); |
||||
if (!result) |
||||
return NULL; |
||||
|
||||
while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) |
||||
{ |
||||
if (r == WJB_ELEM) |
||||
{ |
||||
PyObject *elem = PLyObject_FromJsonbValue(&v); |
||||
|
||||
PyList_Append(result, elem); |
||||
Py_XDECREF(elem); |
||||
} |
||||
} |
||||
} |
||||
break; |
||||
|
||||
case WJB_BEGIN_OBJECT: |
||||
result = PyDict_New(); |
||||
if (!result) |
||||
return NULL; |
||||
|
||||
while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) |
||||
{ |
||||
if (r == WJB_KEY) |
||||
{ |
||||
PyObject *key = PLyString_FromJsonbValue(&v); |
||||
|
||||
if (!key) |
||||
return NULL; |
||||
|
||||
r = JsonbIteratorNext(&it, &v, true); |
||||
|
||||
if (r == WJB_VALUE) |
||||
{ |
||||
PyObject *value = PLyObject_FromJsonbValue(&v); |
||||
|
||||
if (!value) |
||||
{ |
||||
Py_XDECREF(key); |
||||
return NULL; |
||||
} |
||||
|
||||
PyDict_SetItem(result, key, value); |
||||
Py_XDECREF(value); |
||||
} |
||||
|
||||
Py_XDECREF(key); |
||||
} |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
elog(ERROR, "unexpected jsonb token: %d", r); |
||||
return NULL; |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/*
|
||||
* PLyMapping_ToJsonbValue |
||||
* |
||||
* Transform Python dict to JsonbValue. |
||||
*/ |
||||
static JsonbValue * |
||||
PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) |
||||
{ |
||||
Py_ssize_t pcount; |
||||
JsonbValue *out = NULL; |
||||
|
||||
/* We need it volatile, since we use it after longjmp */ |
||||
volatile PyObject *items_v = NULL; |
||||
|
||||
pcount = PyMapping_Size(obj); |
||||
items_v = PyMapping_Items(obj); |
||||
|
||||
PG_TRY(); |
||||
{ |
||||
Py_ssize_t i; |
||||
PyObject *items; |
||||
|
||||
items = (PyObject *) items_v; |
||||
|
||||
pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL); |
||||
|
||||
for (i = 0; i < pcount; i++) |
||||
{ |
||||
JsonbValue jbvKey; |
||||
PyObject *item = PyList_GetItem(items, i); |
||||
PyObject *key = PyTuple_GetItem(item, 0); |
||||
PyObject *value = PyTuple_GetItem(item, 1); |
||||
|
||||
/* Python dictionary can have None as key */ |
||||
if (key == Py_None) |
||||
{ |
||||
jbvKey.type = jbvString; |
||||
jbvKey.val.string.len = 0; |
||||
jbvKey.val.string.val = ""; |
||||
} |
||||
else |
||||
{ |
||||
/* All others types of keys we serialize to string */ |
||||
PLyString_ToJsonbValue(key, &jbvKey); |
||||
} |
||||
|
||||
(void) pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey); |
||||
(void) PLyObject_ToJsonbValue(value, jsonb_state, false); |
||||
} |
||||
|
||||
out = pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL); |
||||
} |
||||
PG_CATCH(); |
||||
{ |
||||
Py_DECREF(items_v); |
||||
PG_RE_THROW(); |
||||
} |
||||
PG_END_TRY(); |
||||
|
||||
return out; |
||||
} |
||||
|
||||
/*
|
||||
* PLySequence_ToJsonbValue |
||||
* |
||||
* Transform python list to JsonbValue. Expects transformed PyObject and |
||||
* a state required for jsonb construction. |
||||
*/ |
||||
static JsonbValue * |
||||
PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) |
||||
{ |
||||
Py_ssize_t i; |
||||
Py_ssize_t pcount; |
||||
|
||||
pcount = PySequence_Size(obj); |
||||
|
||||
pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL); |
||||
|
||||
for (i = 0; i < pcount; i++) |
||||
{ |
||||
PyObject *value = PySequence_GetItem(obj, i); |
||||
|
||||
(void) PLyObject_ToJsonbValue(value, jsonb_state, true); |
||||
} |
||||
|
||||
return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL); |
||||
} |
||||
|
||||
/*
|
||||
* PLyNumber_ToJsonbValue(PyObject *obj) |
||||
* |
||||
* Transform python number to JsonbValue. |
||||
*/ |
||||
static JsonbValue * |
||||
PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum) |
||||
{ |
||||
Numeric num; |
||||
char *str = PLyObject_AsString(obj); |
||||
|
||||
PG_TRY(); |
||||
{ |
||||
num = DatumGetNumeric(DirectFunctionCall3(numeric_in, |
||||
CStringGetDatum(str), 0, -1)); |
||||
} |
||||
PG_CATCH(); |
||||
{ |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_DATATYPE_MISMATCH), |
||||
(errmsg("could not convert value \"%s\" to jsonb", str)))); |
||||
} |
||||
PG_END_TRY(); |
||||
|
||||
pfree(str); |
||||
|
||||
jbvNum->type = jbvNumeric; |
||||
jbvNum->val.numeric = num; |
||||
|
||||
return jbvNum; |
||||
} |
||||
|
||||
/*
|
||||
* PLyObject_ToJsonbValue(PyObject *obj) |
||||
* |
||||
* Transform python object to JsonbValue. |
||||
*/ |
||||
static JsonbValue * |
||||
PLyObject_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state, bool is_elem) |
||||
{ |
||||
JsonbValue buf; |
||||
JsonbValue *out; |
||||
|
||||
if (!(PyString_Check(obj) || PyUnicode_Check(obj))) |
||||
{ |
||||
if (PySequence_Check(obj)) |
||||
return PLySequence_ToJsonbValue(obj, jsonb_state); |
||||
else if (PyMapping_Check(obj)) |
||||
return PLyMapping_ToJsonbValue(obj, jsonb_state); |
||||
} |
||||
|
||||
/* Allocate JsonbValue in heap only if it is raw scalar value. */ |
||||
if (*jsonb_state) |
||||
out = &buf; |
||||
else |
||||
out = palloc(sizeof(JsonbValue)); |
||||
|
||||
if (obj == Py_None) |
||||
out->type = jbvNull; |
||||
else if (PyString_Check(obj) || PyUnicode_Check(obj)) |
||||
PLyString_ToJsonbValue(obj, out); |
||||
/*
|
||||
* PyNumber_Check() returns true for booleans, so boolean check should come |
||||
* first. |
||||
*/ |
||||
else if (PyBool_Check(obj)) |
||||
{ |
||||
out = palloc(sizeof(JsonbValue)); |
||||
out->type = jbvBool; |
||||
out->val.boolean = (obj == Py_True); |
||||
} |
||||
else if (PyNumber_Check(obj)) |
||||
out = PLyNumber_ToJsonbValue(obj, out); |
||||
else |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
(errmsg("Python type \"%s\" cannot be transformed to jsonb", |
||||
PLyObject_AsString((PyObject *) obj->ob_type))))); |
||||
|
||||
/* Push result into 'jsonb_state' unless it is raw scalar value. */ |
||||
return (*jsonb_state ? |
||||
pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, out) : |
||||
out); |
||||
} |
||||
|
||||
/*
|
||||
* plpython_to_jsonb |
||||
* |
||||
* Transform python object to Jsonb datum |
||||
*/ |
||||
PG_FUNCTION_INFO_V1(plpython_to_jsonb); |
||||
Datum |
||||
plpython_to_jsonb(PG_FUNCTION_ARGS) |
||||
{ |
||||
PyObject *obj; |
||||
JsonbValue *out; |
||||
JsonbParseState *jsonb_state = NULL; |
||||
|
||||
obj = (PyObject *) PG_GETARG_POINTER(0); |
||||
out = PLyObject_ToJsonbValue(obj, &jsonb_state, true); |
||||
PG_RETURN_POINTER(JsonbValueToJsonb(out)); |
||||
} |
||||
|
||||
/*
|
||||
* jsonb_to_plpython |
||||
* |
||||
* Transform Jsonb datum to PyObject and return it as internal. |
||||
*/ |
||||
PG_FUNCTION_INFO_V1(jsonb_to_plpython); |
||||
Datum |
||||
jsonb_to_plpython(PG_FUNCTION_ARGS) |
||||
{ |
||||
PyObject *result; |
||||
Jsonb *in = PG_GETARG_JSONB_P(0); |
||||
|
||||
/*
|
||||
* Initialize pointer to Decimal constructor. First we try "cdecimal", C |
||||
* version of decimal library. In case of failure we use slower "decimal" |
||||
* module. |
||||
*/ |
||||
if (!decimal_constructor) |
||||
{ |
||||
PyObject *decimal_module = PyImport_ImportModule("cdecimal"); |
||||
|
||||
if (!decimal_module) |
||||
{ |
||||
PyErr_Clear(); |
||||
decimal_module = PyImport_ImportModule("decimal"); |
||||
} |
||||
Assert(decimal_module); |
||||
decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal"); |
||||
} |
||||
|
||||
result = PLyObject_FromJsonbContainer(&in->root); |
||||
if (!result) |
||||
PLy_elog(ERROR, "transformation from jsonb to Python failed"); |
||||
|
||||
return PointerGetDatum(result); |
||||
} |
@ -0,0 +1,19 @@ |
||||
/* contrib/jsonb_plpython/jsonb_plpython2u--1.0.sql */ |
||||
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION |
||||
\echo Use "CREATE EXTENSION jsonb_plpython2u" to load this file. \quit |
||||
|
||||
CREATE FUNCTION jsonb_to_plpython2(val internal) RETURNS internal |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME', 'jsonb_to_plpython'; |
||||
|
||||
CREATE FUNCTION plpython2_to_jsonb(val internal) RETURNS jsonb |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME', 'plpython_to_jsonb'; |
||||
|
||||
CREATE TRANSFORM FOR jsonb LANGUAGE plpython2u ( |
||||
FROM SQL WITH FUNCTION jsonb_to_plpython2(internal), |
||||
TO SQL WITH FUNCTION plpython2_to_jsonb(internal) |
||||
); |
||||
|
||||
COMMENT ON TRANSFORM FOR jsonb LANGUAGE plpython2u IS 'transform between jsonb and Python'; |
@ -0,0 +1,6 @@ |
||||
# jsonb_plpython2u extension |
||||
comment = 'transform between jsonb and plpython2u' |
||||
default_version = '1.0' |
||||
module_pathname = '$libdir/jsonb_plpython2' |
||||
relocatable = true |
||||
requires = 'plpython2u' |
@ -0,0 +1,19 @@ |
||||
/* contrib/jsonb_plpython/jsonb_plpython3u--1.0.sql */ |
||||
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION |
||||
\echo Use "CREATE EXTENSION jsonb_plpython3u" to load this file. \quit |
||||
|
||||
CREATE FUNCTION jsonb_to_plpython3(val internal) RETURNS internal |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME', 'jsonb_to_plpython'; |
||||
|
||||
CREATE FUNCTION plpython3_to_jsonb(val internal) RETURNS jsonb |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME', 'plpython_to_jsonb'; |
||||
|
||||
CREATE TRANSFORM FOR jsonb LANGUAGE plpython3u ( |
||||
FROM SQL WITH FUNCTION jsonb_to_plpython3(internal), |
||||
TO SQL WITH FUNCTION plpython3_to_jsonb(internal) |
||||
); |
||||
|
||||
COMMENT ON TRANSFORM FOR jsonb LANGUAGE plpython3u IS 'transform between jsonb and Python'; |
@ -0,0 +1,6 @@ |
||||
# jsonb_plpython3u extension |
||||
comment = 'transform between jsonb and plpython3u' |
||||
default_version = '1.0' |
||||
module_pathname = '$libdir/jsonb_plpython3' |
||||
relocatable = true |
||||
requires = 'plpython3u' |
@ -0,0 +1,19 @@ |
||||
/* contrib/jsonb_plpython/jsonb_plpythonu--1.0.sql */ |
||||
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION |
||||
\echo Use "CREATE EXTENSION jsonb_plpythonu" to load this file. \quit |
||||
|
||||
CREATE FUNCTION jsonb_to_plpython(val internal) RETURNS internal |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME'; |
||||
|
||||
CREATE FUNCTION plpython_to_jsonb(val internal) RETURNS jsonb |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME'; |
||||
|
||||
CREATE TRANSFORM FOR jsonb LANGUAGE plpythonu ( |
||||
FROM SQL WITH FUNCTION jsonb_to_plpython(internal), |
||||
TO SQL WITH FUNCTION plpython_to_jsonb(internal) |
||||
); |
||||
|
||||
COMMENT ON TRANSFORM FOR jsonb LANGUAGE plpythonu IS 'transform between jsonb and Python'; |
@ -0,0 +1,6 @@ |
||||
# jsonb_plpythonu extension |
||||
comment = 'transform between jsonb and plpythonu' |
||||
default_version = '1.0' |
||||
module_pathname = '$libdir/jsonb_plpython2' |
||||
relocatable = true |
||||
requires = 'plpythonu' |
@ -0,0 +1,218 @@ |
||||
CREATE EXTENSION jsonb_plpython2u CASCADE; |
||||
|
||||
-- test jsonb -> python dict |
||||
CREATE FUNCTION test1(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, dict) |
||||
plpy.info(sorted(val.items())) |
||||
return len(val) |
||||
$$; |
||||
|
||||
SELECT test1('{"a": 1, "c": "NULL"}'::jsonb); |
||||
|
||||
-- test jsonb -> python dict |
||||
-- complex dict with dicts as value |
||||
CREATE FUNCTION test1complex(val jsonb) RETURNS int |
||||
LANGUAGE plpython2u |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, dict) |
||||
assert(val == {"d": {"d": 1}}) |
||||
return len(val) |
||||
$$; |
||||
|
||||
SELECT test1complex('{"d": {"d": 1}}'::jsonb); |
||||
|
||||
|
||||
-- test jsonb[] -> python dict |
||||
-- dict with array as value |
||||
CREATE FUNCTION test1arr(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, dict) |
||||
assert(val == {"d": [12, 1]}) |
||||
return len(val) |
||||
$$; |
||||
|
||||
SELECT test1arr('{"d":[12, 1]}'::jsonb); |
||||
|
||||
-- test jsonb[] -> python list |
||||
-- simple list |
||||
CREATE FUNCTION test2arr(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, list) |
||||
assert(val == [12, 1]) |
||||
return len(val) |
||||
$$; |
||||
|
||||
SELECT test2arr('[12, 1]'::jsonb); |
||||
|
||||
-- test jsonb[] -> python list |
||||
-- array of dicts |
||||
CREATE FUNCTION test3arr(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert isinstance(val, list) |
||||
assert(val == [{"a": 1,"b": 2}, {"c": 3,"d": 4}]) |
||||
return len(val) |
||||
$$; |
||||
|
||||
SELECT test3arr('[{"a": 1, "b": 2}, {"c": 3,"d": 4}]'::jsonb); |
||||
|
||||
-- test jsonb int -> python int |
||||
CREATE FUNCTION test1int(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert(val == 1) |
||||
return val |
||||
$$; |
||||
|
||||
SELECT test1int('1'::jsonb); |
||||
|
||||
-- test jsonb string -> python string |
||||
CREATE FUNCTION test1string(val jsonb) RETURNS text |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert(val == "a") |
||||
return val |
||||
$$; |
||||
|
||||
SELECT test1string('"a"'::jsonb); |
||||
|
||||
-- test jsonb null -> python None |
||||
CREATE FUNCTION test1null(val jsonb) RETURNS int |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
assert(val == None) |
||||
return 1 |
||||
$$; |
||||
|
||||
SELECT test1null('null'::jsonb); |
||||
|
||||
-- test python -> jsonb |
||||
CREATE FUNCTION roundtrip(val jsonb) RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
as $$ |
||||
return val |
||||
$$; |
||||
|
||||
SELECT roundtrip('null'::jsonb); |
||||
SELECT roundtrip('1'::jsonb); |
||||
SELECT roundtrip('1234567890.0987654321'::jsonb); |
||||
SELECT roundtrip('-1234567890.0987654321'::jsonb); |
||||
SELECT roundtrip('true'::jsonb); |
||||
SELECT roundtrip('"string"'::jsonb); |
||||
|
||||
SELECT roundtrip('{"1": null}'::jsonb); |
||||
SELECT roundtrip('{"1": 1}'::jsonb); |
||||
SELECT roundtrip('{"1": true}'::jsonb); |
||||
SELECT roundtrip('{"1": "string"}'::jsonb); |
||||
|
||||
SELECT roundtrip('[null]'::jsonb); |
||||
SELECT roundtrip('[1]'::jsonb); |
||||
SELECT roundtrip('[true]'::jsonb); |
||||
SELECT roundtrip('["string"]'::jsonb); |
||||
SELECT roundtrip('[null, 1]'::jsonb); |
||||
SELECT roundtrip('[1, true]'::jsonb); |
||||
SELECT roundtrip('[true, "string"]'::jsonb); |
||||
SELECT roundtrip('["string", "string2"]'::jsonb); |
||||
|
||||
-- test python infinity -> jsonb |
||||
CREATE FUNCTION test1inf() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = float('inf') |
||||
print(x) |
||||
return x |
||||
$$; |
||||
|
||||
SELECT test1inf(); |
||||
|
||||
-- test python -infinity -> jsonb |
||||
CREATE FUNCTION test2inf() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = float('-inf') |
||||
print(x) |
||||
return x |
||||
$$; |
||||
|
||||
SELECT test2inf(); |
||||
|
||||
-- test python NaN -> jsonb |
||||
CREATE FUNCTION test1nan() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = float('nan') |
||||
return x |
||||
$$; |
||||
|
||||
SELECT test1nan(); |
||||
|
||||
-- complex numbers -> jsonb |
||||
CREATE FUNCTION testComplexNumbers() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = 1 + 2j |
||||
return x |
||||
$$; |
||||
|
||||
SELECT testComplexNumbers(); |
||||
|
||||
-- range -> jsonb |
||||
CREATE FUNCTION testRange() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = range(3) |
||||
return x |
||||
$$; |
||||
|
||||
SELECT testRange(); |
||||
|
||||
-- 0xff -> jsonb |
||||
CREATE FUNCTION testDecimal() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = 0xff |
||||
return x |
||||
$$; |
||||
|
||||
SELECT testDecimal(); |
||||
|
||||
-- tuple -> jsonb |
||||
CREATE FUNCTION testTuple() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = (1, 'String', None) |
||||
return x |
||||
$$; |
||||
|
||||
SELECT testTuple(); |
||||
|
||||
-- interesting dict -> jsonb |
||||
CREATE FUNCTION test_dict1() RETURNS jsonb |
||||
LANGUAGE plpythonu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
x = {"a": 1, None: 2, 33: 3} |
||||
return x |
||||
$$; |
||||
|
||||
SELECT test_dict1(); |
Loading…
Reference in new issue