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