Age Owner Branch data TLA Line data Source code
1 : : #include "postgres.h"
2 : :
3 : : #include "plpy_elog.h"
4 : : #include "plpy_typeio.h"
5 : : #include "plpy_util.h"
6 : : #include "utils/fmgrprotos.h"
7 : : #include "utils/jsonb.h"
8 : : #include "utils/numeric.h"
9 : :
265 tgl@sss.pgh.pa.us 10 :CBC 1 : PG_MODULE_MAGIC_EXT(
11 : : .name = "jsonb_plpython",
12 : : .version = PG_VERSION
13 : : );
14 : :
15 : : /* for PLyObject_AsString in plpy_typeio.c */
16 : : typedef char *(*PLyObject_AsString_t) (PyObject *plrv);
17 : : static PLyObject_AsString_t PLyObject_AsString_p;
18 : :
19 : : typedef void (*PLy_elog_impl_t) (int elevel, const char *fmt,...);
20 : : static PLy_elog_impl_t PLy_elog_impl_p;
21 : :
22 : : /*
23 : : * decimal_constructor is a function from python library and used
24 : : * for transforming strings into python decimal type
25 : : */
26 : : static PyObject *decimal_constructor;
27 : :
28 : : static PyObject *PLyObject_FromJsonbContainer(JsonbContainer *jsonb);
29 : : static void PLyObject_ToJsonbValue(PyObject *obj,
30 : : JsonbInState *jsonb_state, bool is_elem);
31 : :
32 : : typedef PyObject *(*PLyUnicode_FromStringAndSize_t)
33 : : (const char *s, Py_ssize_t size);
34 : : static PLyUnicode_FromStringAndSize_t PLyUnicode_FromStringAndSize_p;
35 : :
36 : : /*
37 : : * Module initialize function: fetch function pointers for cross-module calls.
38 : : */
39 : : void
2820 peter_e@gmx.net 40 : 1 : _PG_init(void)
41 : : {
42 : : /* Asserts verify that typedefs above match original declarations */
43 : : AssertVariableIsOfType(&PLyObject_AsString, PLyObject_AsString_t);
44 : 1 : PLyObject_AsString_p = (PLyObject_AsString_t)
45 : 1 : load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyObject_AsString",
46 : : true, NULL);
47 : : AssertVariableIsOfType(&PLyUnicode_FromStringAndSize, PLyUnicode_FromStringAndSize_t);
48 : 1 : PLyUnicode_FromStringAndSize_p = (PLyUnicode_FromStringAndSize_t)
49 : 1 : load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize",
50 : : true, NULL);
51 : : AssertVariableIsOfType(&PLy_elog_impl, PLy_elog_impl_t);
52 : 1 : PLy_elog_impl_p = (PLy_elog_impl_t)
53 : 1 : load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLy_elog_impl",
54 : : true, NULL);
55 : 1 : }
56 : :
57 : : /* These defines must be after the _PG_init */
58 : : #define PLyObject_AsString (PLyObject_AsString_p)
59 : : #define PLyUnicode_FromStringAndSize (PLyUnicode_FromStringAndSize_p)
60 : : #undef PLy_elog
61 : : #define PLy_elog (PLy_elog_impl_p)
62 : :
63 : : /*
64 : : * PLyUnicode_FromJsonbValue
65 : : *
66 : : * Transform string JsonbValue to Python string.
67 : : */
68 : : static PyObject *
1380 andres@anarazel.de 69 : 21 : PLyUnicode_FromJsonbValue(JsonbValue *jbv)
70 : : {
2820 peter_e@gmx.net 71 [ - + ]: 21 : Assert(jbv->type == jbvString);
72 : :
1380 andres@anarazel.de 73 : 21 : return PLyUnicode_FromStringAndSize(jbv->val.string.val, jbv->val.string.len);
74 : : }
75 : :
76 : : /*
77 : : * PLyUnicode_ToJsonbValue
78 : : *
79 : : * Transform Python string to JsonbValue.
80 : : */
81 : : static void
82 : 13 : PLyUnicode_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem)
83 : : {
2820 peter_e@gmx.net 84 : 13 : jbvElem->type = jbvString;
85 : 13 : jbvElem->val.string.val = PLyObject_AsString(obj);
86 : 13 : jbvElem->val.string.len = strlen(jbvElem->val.string.val);
87 : 13 : }
88 : :
89 : : /*
90 : : * PLyObject_FromJsonbValue
91 : : *
92 : : * Transform JsonbValue to PyObject.
93 : : */
94 : : static PyObject *
95 : 40 : PLyObject_FromJsonbValue(JsonbValue *jsonbValue)
96 : : {
97 [ + + + + : 40 : switch (jsonbValue->type)
+ - ]
98 : : {
99 : 5 : case jbvNull:
100 : 5 : Py_RETURN_NONE;
101 : :
102 : 4 : case jbvBinary:
103 : 4 : return PLyObject_FromJsonbContainer(jsonbValue->val.binary.data);
104 : :
105 : 18 : case jbvNumeric:
106 : : {
107 : : Datum num;
108 : : char *str;
109 : :
110 : 18 : num = NumericGetDatum(jsonbValue->val.numeric);
111 : 18 : str = DatumGetCString(DirectFunctionCall1(numeric_out, num));
112 : :
113 : 18 : return PyObject_CallFunction(decimal_constructor, "s", str);
114 : : }
115 : :
116 : 8 : case jbvString:
1380 andres@anarazel.de 117 : 8 : return PLyUnicode_FromJsonbValue(jsonbValue);
118 : :
2820 peter_e@gmx.net 119 : 5 : case jbvBool:
120 [ + - ]: 5 : if (jsonbValue->val.boolean)
121 : 5 : Py_RETURN_TRUE;
122 : : else
2820 peter_e@gmx.net 123 :UBC 0 : Py_RETURN_FALSE;
124 : :
125 : 0 : default:
126 [ # # ]: 0 : elog(ERROR, "unexpected jsonb value type: %d", jsonbValue->type);
127 : : return NULL;
128 : : }
129 : : }
130 : :
131 : : /*
132 : : * PLyObject_FromJsonbContainer
133 : : *
134 : : * Transform JsonbContainer to PyObject.
135 : : */
136 : : static PyObject *
2820 peter_e@gmx.net 137 :CBC 30 : PLyObject_FromJsonbContainer(JsonbContainer *jsonb)
138 : : {
139 : : JsonbIteratorToken r;
140 : : JsonbValue v;
141 : : JsonbIterator *it;
142 : : PyObject *result;
143 : :
144 : 30 : it = JsonbIteratorInit(jsonb);
145 : 30 : r = JsonbIteratorNext(&it, &v, true);
146 : :
147 [ + + - ]: 30 : switch (r)
148 : : {
149 : 20 : case WJB_BEGIN_ARRAY:
150 [ + + ]: 20 : if (v.val.array.rawScalar)
151 : : {
152 : : JsonbValue tmp;
153 : :
154 [ + - ]: 9 : if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
155 [ + - ]: 9 : (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
156 [ - + ]: 9 : (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
2820 peter_e@gmx.net 157 [ # # ]:UBC 0 : elog(ERROR, "unexpected jsonb token: %d", r);
158 : :
2820 peter_e@gmx.net 159 :CBC 9 : result = PLyObject_FromJsonbValue(&v);
160 : : }
161 : : else
162 : : {
2446 tgl@sss.pgh.pa.us 163 : 11 : PyObject *volatile elem = NULL;
164 : :
2820 peter_e@gmx.net 165 : 11 : result = PyList_New(0);
166 [ - + ]: 11 : if (!result)
2820 peter_e@gmx.net 167 :UBC 0 : return NULL;
168 : :
2446 tgl@sss.pgh.pa.us 169 [ + - ]:CBC 11 : PG_TRY();
170 : : {
171 [ + + ]: 40 : while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
172 : : {
173 [ + + ]: 29 : if (r != WJB_ELEM)
174 : 11 : continue;
175 : :
176 : 18 : elem = PLyObject_FromJsonbValue(&v);
177 : :
2820 peter_e@gmx.net 178 : 18 : PyList_Append(result, elem);
179 : 18 : Py_XDECREF(elem);
2446 tgl@sss.pgh.pa.us 180 : 18 : elem = NULL;
181 : : }
182 : : }
2446 tgl@sss.pgh.pa.us 183 :UBC 0 : PG_CATCH();
184 : : {
185 : 0 : Py_XDECREF(elem);
186 : 0 : Py_XDECREF(result);
187 : 0 : PG_RE_THROW();
188 : : }
2446 tgl@sss.pgh.pa.us 189 [ - + ]:CBC 11 : PG_END_TRY();
190 : : }
2820 peter_e@gmx.net 191 : 20 : break;
192 : :
193 : 10 : case WJB_BEGIN_OBJECT:
194 : : {
2446 tgl@sss.pgh.pa.us 195 : 10 : PyObject *volatile result_v = PyDict_New();
196 : 10 : PyObject *volatile key = NULL;
197 : 10 : PyObject *volatile val = NULL;
198 : :
199 [ - + ]: 10 : if (!result_v)
2446 tgl@sss.pgh.pa.us 200 :UBC 0 : return NULL;
201 : :
2446 tgl@sss.pgh.pa.us 202 [ + - ]:CBC 10 : PG_TRY();
203 : : {
204 [ + + ]: 33 : while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
205 : : {
206 [ + + ]: 23 : if (r != WJB_KEY)
207 : 10 : continue;
208 : :
1380 andres@anarazel.de 209 : 13 : key = PLyUnicode_FromJsonbValue(&v);
2446 tgl@sss.pgh.pa.us 210 [ - + ]: 13 : if (!key)
211 : : {
2446 tgl@sss.pgh.pa.us 212 :UBC 0 : Py_XDECREF(result_v);
213 : 0 : result_v = NULL;
214 : 0 : break;
215 : : }
216 : :
2446 tgl@sss.pgh.pa.us 217 [ - + ]:CBC 13 : if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_VALUE)
2446 tgl@sss.pgh.pa.us 218 [ # # ]:UBC 0 : elog(ERROR, "unexpected jsonb token: %d", r);
219 : :
2446 tgl@sss.pgh.pa.us 220 :CBC 13 : val = PLyObject_FromJsonbValue(&v);
221 [ - + ]: 13 : if (!val)
222 : : {
2820 peter_e@gmx.net 223 :UBC 0 : Py_XDECREF(key);
2446 tgl@sss.pgh.pa.us 224 : 0 : key = NULL;
225 : 0 : Py_XDECREF(result_v);
226 : 0 : result_v = NULL;
227 : 0 : break;
228 : : }
229 : :
2446 tgl@sss.pgh.pa.us 230 :CBC 13 : PyDict_SetItem(result_v, key, val);
231 : :
232 : 13 : Py_XDECREF(key);
233 : 13 : key = NULL;
234 : 13 : Py_XDECREF(val);
235 : 13 : val = NULL;
236 : : }
237 : : }
2446 tgl@sss.pgh.pa.us 238 :UBC 0 : PG_CATCH();
239 : : {
240 : 0 : Py_XDECREF(result_v);
2820 peter_e@gmx.net 241 : 0 : Py_XDECREF(key);
2446 tgl@sss.pgh.pa.us 242 : 0 : Py_XDECREF(val);
243 : 0 : PG_RE_THROW();
244 : : }
2446 tgl@sss.pgh.pa.us 245 [ - + ]:CBC 10 : PG_END_TRY();
246 : :
247 : 10 : result = result_v;
248 : : }
2820 peter_e@gmx.net 249 : 10 : break;
250 : :
2820 peter_e@gmx.net 251 :UBC 0 : default:
252 [ # # ]: 0 : elog(ERROR, "unexpected jsonb token: %d", r);
253 : : return NULL;
254 : : }
255 : :
2820 peter_e@gmx.net 256 :CBC 30 : return result;
257 : : }
258 : :
259 : : /*
260 : : * PLyMapping_ToJsonbValue
261 : : *
262 : : * Transform Python dict to JsonbValue.
263 : : */
264 : : static void
9 tgl@sss.pgh.pa.us 265 :GNC 5 : PLyMapping_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
266 : : {
267 : : Py_ssize_t pcount;
268 : : PyObject *volatile items;
269 : :
2820 peter_e@gmx.net 270 :CBC 5 : pcount = PyMapping_Size(obj);
2469 peter@eisentraut.org 271 : 5 : items = PyMapping_Items(obj);
272 : :
2820 peter_e@gmx.net 273 [ + - ]: 5 : PG_TRY();
274 : : {
275 : : Py_ssize_t i;
276 : :
277 : 5 : pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
278 : :
279 [ + + ]: 12 : for (i = 0; i < pcount; i++)
280 : : {
281 : : JsonbValue jbvKey;
282 : 7 : PyObject *item = PyList_GetItem(items, i);
283 : 7 : PyObject *key = PyTuple_GetItem(item, 0);
284 : 7 : PyObject *value = PyTuple_GetItem(item, 1);
285 : :
286 : : /* Python dictionary can have None as key */
287 [ + + ]: 7 : if (key == Py_None)
288 : : {
289 : 1 : jbvKey.type = jbvString;
290 : 1 : jbvKey.val.string.len = 0;
291 : 1 : jbvKey.val.string.val = "";
292 : : }
293 : : else
294 : : {
295 : : /* All others types of keys we serialize to string */
1380 andres@anarazel.de 296 : 6 : PLyUnicode_ToJsonbValue(key, &jbvKey);
297 : : }
298 : :
9 tgl@sss.pgh.pa.us 299 :GNC 7 : pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey);
300 : 7 : PLyObject_ToJsonbValue(value, jsonb_state, false);
301 : : }
302 : :
303 : 5 : pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
304 : : }
2237 peter@eisentraut.org 305 :UBC 0 : PG_FINALLY();
306 : : {
2469 peter@eisentraut.org 307 :CBC 5 : Py_DECREF(items);
308 : : }
2820 peter_e@gmx.net 309 [ - + ]: 5 : PG_END_TRY();
2820 peter_e@gmx.net 310 :GIC 5 : }
311 : :
312 : : /*
313 : : * PLySequence_ToJsonbValue
314 : : *
315 : : * Transform python list to JsonbValue. Expects transformed PyObject and
316 : : * a state required for jsonb construction.
317 : : */
318 : : static void
9 tgl@sss.pgh.pa.us 319 :GNC 10 : PLySequence_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
320 : : {
321 : : Py_ssize_t i;
322 : : Py_ssize_t pcount;
2446 tgl@sss.pgh.pa.us 323 :CBC 10 : PyObject *volatile value = NULL;
324 : :
2820 peter_e@gmx.net 325 : 10 : pcount = PySequence_Size(obj);
326 : :
327 : 10 : pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
328 : :
2446 tgl@sss.pgh.pa.us 329 [ + - ]: 10 : PG_TRY();
330 : : {
331 [ + + ]: 28 : for (i = 0; i < pcount; i++)
332 : : {
333 : 18 : value = PySequence_GetItem(obj, i);
334 [ - + ]: 18 : Assert(value);
335 : :
9 tgl@sss.pgh.pa.us 336 :GNC 18 : PLyObject_ToJsonbValue(value, jsonb_state, true);
2446 tgl@sss.pgh.pa.us 337 :CBC 18 : Py_XDECREF(value);
338 : 18 : value = NULL;
339 : : }
340 : : }
2446 tgl@sss.pgh.pa.us 341 :UBC 0 : PG_CATCH();
342 : : {
2741 akorotkov@postgresql 343 : 0 : Py_XDECREF(value);
2446 tgl@sss.pgh.pa.us 344 : 0 : PG_RE_THROW();
345 : : }
2446 tgl@sss.pgh.pa.us 346 [ - + ]:CBC 10 : PG_END_TRY();
347 : :
9 tgl@sss.pgh.pa.us 348 :GNC 10 : pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
2820 peter_e@gmx.net 349 :GIC 10 : }
350 : :
351 : : /*
352 : : * PLyNumber_ToJsonbValue(PyObject *obj)
353 : : *
354 : : * Transform python number to JsonbValue.
355 : : */
356 : : static JsonbValue *
2820 peter_e@gmx.net 357 :CBC 16 : PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
358 : : {
359 : : Numeric num;
360 : 16 : char *str = PLyObject_AsString(obj);
361 : :
362 [ + + ]: 16 : PG_TRY();
363 : : {
364 : : Datum numd;
365 : :
2785 tgl@sss.pgh.pa.us 366 : 16 : numd = DirectFunctionCall3(numeric_in,
367 : : CStringGetDatum(str),
368 : : ObjectIdGetDatum(InvalidOid),
369 : : Int32GetDatum(-1));
370 : 15 : num = DatumGetNumeric(numd);
371 : : }
2820 peter_e@gmx.net 372 : 1 : PG_CATCH();
373 : : {
374 [ + - ]: 1 : ereport(ERROR,
375 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
376 : : errmsg("could not convert value \"%s\" to jsonb", str)));
377 : : }
378 [ - + ]: 15 : PG_END_TRY();
379 : :
380 : 15 : pfree(str);
381 : :
382 : : /*
383 : : * jsonb doesn't allow NaN or infinity (per JSON specification), so we
384 : : * have to reject those here explicitly.
385 : : */
2785 386 [ - + ]: 15 : if (numeric_is_nan(num))
2785 peter_e@gmx.net 387 [ # # ]:UBC 0 : ereport(ERROR,
388 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
389 : : errmsg("cannot convert NaN to jsonb")));
1973 tgl@sss.pgh.pa.us 390 [ - + ]:CBC 15 : if (numeric_is_inf(num))
1973 tgl@sss.pgh.pa.us 391 [ # # ]:UBC 0 : ereport(ERROR,
392 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
393 : : errmsg("cannot convert infinity to jsonb")));
394 : :
2820 peter_e@gmx.net 395 :CBC 15 : jbvNum->type = jbvNumeric;
396 : 15 : jbvNum->val.numeric = num;
397 : :
398 : 15 : return jbvNum;
399 : : }
400 : :
401 : : /*
402 : : * PLyObject_ToJsonbValue(PyObject *obj)
403 : : *
404 : : * Transform python object to JsonbValue.
405 : : */
406 : : static void
9 tgl@sss.pgh.pa.us 407 :GNC 47 : PLyObject_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state, bool is_elem)
408 : : {
409 : : JsonbValue *out;
410 : :
1380 andres@anarazel.de 411 [ + + ]:CBC 47 : if (!PyUnicode_Check(obj))
412 : : {
2820 peter_e@gmx.net 413 [ + + ]: 40 : if (PySequence_Check(obj))
414 : : {
9 tgl@sss.pgh.pa.us 415 :GNC 10 : PLySequence_ToJsonbValue(obj, jsonb_state);
416 : 10 : return;
417 : : }
2820 peter_e@gmx.net 418 [ + + ]:CBC 30 : else if (PyMapping_Check(obj))
419 : : {
9 tgl@sss.pgh.pa.us 420 :GNC 5 : PLyMapping_ToJsonbValue(obj, jsonb_state);
421 : 5 : return;
422 : : }
423 : : }
424 : :
11 michael@paquier.xyz 425 : 32 : out = palloc_object(JsonbValue);
426 : :
2820 peter_e@gmx.net 427 [ + + ]:CBC 32 : if (obj == Py_None)
428 : 4 : out->type = jbvNull;
1380 andres@anarazel.de 429 [ + + ]: 28 : else if (PyUnicode_Check(obj))
430 : 7 : PLyUnicode_ToJsonbValue(obj, out);
431 : :
432 : : /*
433 : : * PyNumber_Check() returns true for booleans, so boolean check should
434 : : * come first.
435 : : */
2820 peter_e@gmx.net 436 [ + + ]: 21 : else if (PyBool_Check(obj))
437 : : {
438 : 5 : out->type = jbvBool;
439 : 5 : out->val.boolean = (obj == Py_True);
440 : : }
441 [ + - ]: 16 : else if (PyNumber_Check(obj))
442 : 16 : out = PLyNumber_ToJsonbValue(obj, out);
443 : : else
2820 peter_e@gmx.net 444 [ # # ]:UBC 0 : ereport(ERROR,
445 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
446 : : errmsg("Python type \"%s\" cannot be transformed to jsonb",
447 : : PLyObject_AsString((PyObject *) obj->ob_type))));
448 : :
9 tgl@sss.pgh.pa.us 449 [ + + ]:GNC 31 : if (jsonb_state->parseState)
450 : : {
451 : : /* We're in an array or object, so push value as element or field. */
452 [ + + ]: 25 : pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, out);
453 : : }
454 : : else
455 : : {
456 : : /*
457 : : * We are at top level, so it's a raw scalar. If we just shove the
458 : : * scalar value into jsonb_state->result, JsonbValueToJsonb will take
459 : : * care of wrapping it into a dummy array.
460 : : */
461 : 6 : jsonb_state->result = out;
462 : : }
463 : : }
464 : :
465 : : /*
466 : : * plpython_to_jsonb
467 : : *
468 : : * Transform python object to Jsonb datum
469 : : */
2820 peter_e@gmx.net 470 :CBC 2 : PG_FUNCTION_INFO_V1(plpython_to_jsonb);
471 : : Datum
472 : 22 : plpython_to_jsonb(PG_FUNCTION_ARGS)
473 : : {
9 tgl@sss.pgh.pa.us 474 :GNC 22 : PyObject *obj = (PyObject *) PG_GETARG_POINTER(0);
475 : 22 : JsonbInState jsonb_state = {0};
476 : :
477 : 22 : PLyObject_ToJsonbValue(obj, &jsonb_state, true);
478 : 21 : PG_RETURN_POINTER(JsonbValueToJsonb(jsonb_state.result));
479 : : }
480 : :
481 : : /*
482 : : * jsonb_to_plpython
483 : : *
484 : : * Transform Jsonb datum to PyObject and return it as internal.
485 : : */
2820 peter_e@gmx.net 486 :CBC 2 : PG_FUNCTION_INFO_V1(jsonb_to_plpython);
487 : : Datum
488 : 26 : jsonb_to_plpython(PG_FUNCTION_ARGS)
489 : : {
490 : : PyObject *result;
491 : 26 : Jsonb *in = PG_GETARG_JSONB_P(0);
492 : :
493 : : /*
494 : : * Initialize pointer to Decimal constructor. First we try "cdecimal", C
495 : : * version of decimal library. In case of failure we use slower "decimal"
496 : : * module.
497 : : */
498 [ + + ]: 26 : if (!decimal_constructor)
499 : : {
500 : 1 : PyObject *decimal_module = PyImport_ImportModule("cdecimal");
501 : :
502 [ + - ]: 1 : if (!decimal_module)
503 : : {
504 : 1 : PyErr_Clear();
505 : 1 : decimal_module = PyImport_ImportModule("decimal");
506 : : }
507 [ - + ]: 1 : Assert(decimal_module);
508 : 1 : decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal");
509 : : }
510 : :
511 : 26 : result = PLyObject_FromJsonbContainer(&in->root);
512 [ - + ]: 26 : if (!result)
2820 peter_e@gmx.net 513 :UBC 0 : PLy_elog(ERROR, "transformation from jsonb to Python failed");
514 : :
2820 peter_e@gmx.net 515 :CBC 26 : return PointerGetDatum(result);
516 : : }
|