PostgreSQL Source Code git master
jsonb_plpython.c
Go to the documentation of this file.
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
11 .name = "jsonb_plpython",
12 .version = PG_VERSION
13);
14
15/* for PLyObject_AsString in plpy_typeio.c */
16typedef char *(*PLyObject_AsString_t) (PyObject *plrv);
18
19typedef void (*PLy_elog_impl_t) (int elevel, const char *fmt,...);
21
22/*
23 * decimal_constructor is a function from python library and used
24 * for transforming strings into python decimal type
25 */
26static PyObject *decimal_constructor;
27
28static PyObject *PLyObject_FromJsonbContainer(JsonbContainer *jsonb);
29static void PLyObject_ToJsonbValue(PyObject *obj,
30 JsonbInState *jsonb_state, bool is_elem);
31
32typedef PyObject *(*PLyUnicode_FromStringAndSize_t)
33 (const char *s, Py_ssize_t size);
35
36/*
37 * Module initialize function: fetch function pointers for cross-module calls.
38 */
39void
41{
42 /* Asserts verify that typedefs above match original declarations */
45 load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyObject_AsString",
46 true, NULL);
49 load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize",
50 true, NULL);
53 load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLy_elog_impl",
54 true, NULL);
55}
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 */
68static PyObject *
70{
71 Assert(jbv->type == jbvString);
72
73 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 */
81static void
82PLyUnicode_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem)
83{
84 jbvElem->type = jbvString;
85 jbvElem->val.string.val = PLyObject_AsString(obj);
86 jbvElem->val.string.len = strlen(jbvElem->val.string.val);
87}
88
89/*
90 * PLyObject_FromJsonbValue
91 *
92 * Transform JsonbValue to PyObject.
93 */
94static PyObject *
96{
97 switch (jsonbValue->type)
98 {
99 case jbvNull:
100 Py_RETURN_NONE;
101
102 case jbvBinary:
103 return PLyObject_FromJsonbContainer(jsonbValue->val.binary.data);
104
105 case jbvNumeric:
106 {
107 Datum num;
108 char *str;
109
110 num = NumericGetDatum(jsonbValue->val.numeric);
112
113 return PyObject_CallFunction(decimal_constructor, "s", str);
114 }
115
116 case jbvString:
117 return PLyUnicode_FromJsonbValue(jsonbValue);
118
119 case jbvBool:
120 if (jsonbValue->val.boolean)
121 Py_RETURN_TRUE;
122 else
123 Py_RETURN_FALSE;
124
125 default:
126 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 */
136static PyObject *
138{
140 JsonbValue v;
141 JsonbIterator *it;
142 PyObject *result;
143
144 it = JsonbIteratorInit(jsonb);
145 r = JsonbIteratorNext(&it, &v, true);
146
147 switch (r)
148 {
149 case WJB_BEGIN_ARRAY:
150 if (v.val.array.rawScalar)
151 {
152 JsonbValue tmp;
153
154 if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
155 (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
156 (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
157 elog(ERROR, "unexpected jsonb token: %d", r);
158
159 result = PLyObject_FromJsonbValue(&v);
160 }
161 else
162 {
163 PyObject *volatile elem = NULL;
164
165 result = PyList_New(0);
166 if (!result)
167 return NULL;
168
169 PG_TRY();
170 {
171 while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
172 {
173 if (r != WJB_ELEM)
174 continue;
175
176 elem = PLyObject_FromJsonbValue(&v);
177
178 PyList_Append(result, elem);
179 Py_XDECREF(elem);
180 elem = NULL;
181 }
182 }
183 PG_CATCH();
184 {
185 Py_XDECREF(elem);
186 Py_XDECREF(result);
187 PG_RE_THROW();
188 }
189 PG_END_TRY();
190 }
191 break;
192
193 case WJB_BEGIN_OBJECT:
194 {
195 PyObject *volatile result_v = PyDict_New();
196 PyObject *volatile key = NULL;
197 PyObject *volatile val = NULL;
198
199 if (!result_v)
200 return NULL;
201
202 PG_TRY();
203 {
204 while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
205 {
206 if (r != WJB_KEY)
207 continue;
208
210 if (!key)
211 {
212 Py_XDECREF(result_v);
213 result_v = NULL;
214 break;
215 }
216
217 if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_VALUE)
218 elog(ERROR, "unexpected jsonb token: %d", r);
219
221 if (!val)
222 {
223 Py_XDECREF(key);
224 key = NULL;
225 Py_XDECREF(result_v);
226 result_v = NULL;
227 break;
228 }
229
230 PyDict_SetItem(result_v, key, val);
231
232 Py_XDECREF(key);
233 key = NULL;
234 Py_XDECREF(val);
235 val = NULL;
236 }
237 }
238 PG_CATCH();
239 {
240 Py_XDECREF(result_v);
241 Py_XDECREF(key);
242 Py_XDECREF(val);
243 PG_RE_THROW();
244 }
245 PG_END_TRY();
246
247 result = result_v;
248 }
249 break;
250
251 default:
252 elog(ERROR, "unexpected jsonb token: %d", r);
253 return NULL;
254 }
255
256 return result;
257}
258
259/*
260 * PLyMapping_ToJsonbValue
261 *
262 * Transform Python dict to JsonbValue.
263 */
264static void
265PLyMapping_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
266{
267 Py_ssize_t pcount;
268 PyObject *volatile items;
269
270 pcount = PyMapping_Size(obj);
271 items = PyMapping_Items(obj);
272
273 PG_TRY();
274 {
275 Py_ssize_t i;
276
277 pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
278
279 for (i = 0; i < pcount; i++)
280 {
281 JsonbValue jbvKey;
282 PyObject *item = PyList_GetItem(items, i);
283 PyObject *key = PyTuple_GetItem(item, 0);
284 PyObject *value = PyTuple_GetItem(item, 1);
285
286 /* Python dictionary can have None as key */
287 if (key == Py_None)
288 {
289 jbvKey.type = jbvString;
290 jbvKey.val.string.len = 0;
291 jbvKey.val.string.val = "";
292 }
293 else
294 {
295 /* All others types of keys we serialize to string */
297 }
298
299 pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey);
300 PLyObject_ToJsonbValue(value, jsonb_state, false);
301 }
302
303 pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
304 }
305 PG_FINALLY();
306 {
307 Py_DECREF(items);
308 }
309 PG_END_TRY();
310}
311
312/*
313 * PLySequence_ToJsonbValue
314 *
315 * Transform python list to JsonbValue. Expects transformed PyObject and
316 * a state required for jsonb construction.
317 */
318static void
319PLySequence_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
320{
321 Py_ssize_t i;
322 Py_ssize_t pcount;
323 PyObject *volatile value = NULL;
324
325 pcount = PySequence_Size(obj);
326
327 pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
328
329 PG_TRY();
330 {
331 for (i = 0; i < pcount; i++)
332 {
333 value = PySequence_GetItem(obj, i);
334 Assert(value);
335
336 PLyObject_ToJsonbValue(value, jsonb_state, true);
337 Py_XDECREF(value);
338 value = NULL;
339 }
340 }
341 PG_CATCH();
342 {
343 Py_XDECREF(value);
344 PG_RE_THROW();
345 }
346 PG_END_TRY();
347
348 pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
349}
350
351/*
352 * PLyNumber_ToJsonbValue(PyObject *obj)
353 *
354 * Transform python number to JsonbValue.
355 */
356static JsonbValue *
357PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
358{
359 Numeric num;
360 char *str = PLyObject_AsString(obj);
361
362 PG_TRY();
363 {
364 Datum numd;
365
369 Int32GetDatum(-1));
370 num = DatumGetNumeric(numd);
371 }
372 PG_CATCH();
373 {
375 (errcode(ERRCODE_DATATYPE_MISMATCH),
376 errmsg("could not convert value \"%s\" to jsonb", str)));
377 }
378 PG_END_TRY();
379
380 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 */
386 if (numeric_is_nan(num))
388 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
389 errmsg("cannot convert NaN to jsonb")));
390 if (numeric_is_inf(num))
392 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
393 errmsg("cannot convert infinity to jsonb")));
394
395 jbvNum->type = jbvNumeric;
396 jbvNum->val.numeric = num;
397
398 return jbvNum;
399}
400
401/*
402 * PLyObject_ToJsonbValue(PyObject *obj)
403 *
404 * Transform python object to JsonbValue.
405 */
406static void
407PLyObject_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state, bool is_elem)
408{
409 JsonbValue *out;
410
411 if (!PyUnicode_Check(obj))
412 {
413 if (PySequence_Check(obj))
414 {
415 PLySequence_ToJsonbValue(obj, jsonb_state);
416 return;
417 }
418 else if (PyMapping_Check(obj))
419 {
420 PLyMapping_ToJsonbValue(obj, jsonb_state);
421 return;
422 }
423 }
424
426
427 if (obj == Py_None)
428 out->type = jbvNull;
429 else if (PyUnicode_Check(obj))
430 PLyUnicode_ToJsonbValue(obj, out);
431
432 /*
433 * PyNumber_Check() returns true for booleans, so boolean check should
434 * come first.
435 */
436 else if (PyBool_Check(obj))
437 {
438 out->type = jbvBool;
439 out->val.boolean = (obj == Py_True);
440 }
441 else if (PyNumber_Check(obj))
442 out = PLyNumber_ToJsonbValue(obj, out);
443 else
445 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
446 errmsg("Python type \"%s\" cannot be transformed to jsonb",
447 PLyObject_AsString((PyObject *) obj->ob_type))));
448
449 if (jsonb_state->parseState)
450 {
451 /* We're in an array or object, so push value as element or field. */
452 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 jsonb_state->result = out;
462 }
463}
464
465/*
466 * plpython_to_jsonb
467 *
468 * Transform python object to Jsonb datum
469 */
471Datum
473{
474 PyObject *obj = (PyObject *) PG_GETARG_POINTER(0);
475 JsonbInState jsonb_state = {0};
476
477 PLyObject_ToJsonbValue(obj, &jsonb_state, true);
479}
480
481/*
482 * jsonb_to_plpython
483 *
484 * Transform Jsonb datum to PyObject and return it as internal.
485 */
487Datum
489{
490 PyObject *result;
491 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 */
499 {
500 PyObject *decimal_module = PyImport_ImportModule("cdecimal");
501
502 if (!decimal_module)
503 {
504 PyErr_Clear();
505 decimal_module = PyImport_ImportModule("decimal");
506 }
507 Assert(decimal_module);
508 decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal");
509 }
510
511 result = PLyObject_FromJsonbContainer(&in->root);
512 if (!result)
513 PLy_elog(ERROR, "transformation from jsonb to Python failed");
514
515 return PointerGetDatum(result);
516}
Datum numeric_out(PG_FUNCTION_ARGS)
Definition: numeric.c:799
Datum numeric_in(PG_FUNCTION_ARGS)
Definition: numeric.c:626
bool numeric_is_nan(Numeric num)
Definition: numeric.c:834
bool numeric_is_inf(Numeric num)
Definition: numeric.c:845
#define AssertVariableIsOfType(varname, typename)
Definition: c.h:993
void * load_external_function(const char *filename, const char *funcname, bool signalNotFound, void **filehandle)
Definition: dfmgr.c:95
int errcode(int sqlerrcode)
Definition: elog.c:863
int errmsg(const char *fmt,...)
Definition: elog.c:1080
#define PG_RE_THROW()
Definition: elog.h:405
#define PG_TRY(...)
Definition: elog.h:372
#define PG_END_TRY(...)
Definition: elog.h:397
#define ERROR
Definition: elog.h:39
#define PG_CATCH(...)
Definition: elog.h:382
#define elog(elevel,...)
Definition: elog.h:226
#define PG_FINALLY(...)
Definition: elog.h:389
#define ereport(elevel,...)
Definition: elog.h:150
#define palloc_object(type)
Definition: fe_memutils.h:74
#define PG_GETARG_POINTER(n)
Definition: fmgr.h:276
#define DirectFunctionCall1(func, arg1)
Definition: fmgr.h:682
#define DirectFunctionCall3(func, arg1, arg2, arg3)
Definition: fmgr.h:686
#define PG_RETURN_POINTER(x)
Definition: fmgr.h:361
#define PG_FUNCTION_ARGS
Definition: fmgr.h:193
Assert(PointerIsAligned(start, uint64))
const char * str
long val
Definition: informix.c:689
static struct @171 value
int i
Definition: isn.c:77
@ jbvNumeric
Definition: jsonb.h:232
@ jbvBool
Definition: jsonb.h:233
@ jbvBinary
Definition: jsonb.h:238
@ jbvNull
Definition: jsonb.h:230
@ jbvString
Definition: jsonb.h:231
#define PG_GETARG_JSONB_P(x)
Definition: jsonb.h:418
JsonbIteratorToken
Definition: jsonb.h:21
@ WJB_KEY
Definition: jsonb.h:23
@ WJB_DONE
Definition: jsonb.h:22
@ WJB_END_ARRAY
Definition: jsonb.h:27
@ WJB_VALUE
Definition: jsonb.h:24
@ WJB_END_OBJECT
Definition: jsonb.h:29
@ WJB_ELEM
Definition: jsonb.h:25
@ WJB_BEGIN_OBJECT
Definition: jsonb.h:28
@ WJB_BEGIN_ARRAY
Definition: jsonb.h:26
static void PLyMapping_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
#define PLy_elog
#define PLyUnicode_FromStringAndSize
static void PLyObject_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state, bool is_elem)
void _PG_init(void)
PG_MODULE_MAGIC_EXT(.name="jsonb_plpython",.version=PG_VERSION)
static PLy_elog_impl_t PLy_elog_impl_p
static JsonbValue * PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
PyObject *(* PLyUnicode_FromStringAndSize_t)(const char *s, Py_ssize_t size)
Datum plpython_to_jsonb(PG_FUNCTION_ARGS)
void(* PLy_elog_impl_t)(int elevel, const char *fmt,...)
static PyObject * PLyObject_FromJsonbValue(JsonbValue *jsonbValue)
PG_FUNCTION_INFO_V1(plpython_to_jsonb)
static PLyObject_AsString_t PLyObject_AsString_p
static void PLySequence_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
char *(* PLyObject_AsString_t)(PyObject *plrv)
static PyObject * decimal_constructor
Datum jsonb_to_plpython(PG_FUNCTION_ARGS)
static PLyUnicode_FromStringAndSize_t PLyUnicode_FromStringAndSize_p
static void PLyUnicode_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem)
static PyObject * PLyObject_FromJsonbContainer(JsonbContainer *jsonb)
#define PLyObject_AsString
static PyObject * PLyUnicode_FromJsonbValue(JsonbValue *jbv)
void pushJsonbValue(JsonbInState *pstate, JsonbIteratorToken seq, JsonbValue *jbval)
Definition: jsonb_util.c:583
JsonbIterator * JsonbIteratorInit(JsonbContainer *container)
Definition: jsonb_util.c:935
JsonbIteratorToken JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested)
Definition: jsonb_util.c:973
Jsonb * JsonbValueToJsonb(JsonbValue *val)
Definition: jsonb_util.c:96
void pfree(void *pointer)
Definition: mcxt.c:1594
static Numeric DatumGetNumeric(Datum X)
Definition: numeric.h:64
static Datum NumericGetDatum(Numeric X)
Definition: numeric.h:76
void PLy_elog_impl(int elevel, const char *fmt,...)
Definition: plpy_elog.c:44
static Datum PointerGetDatum(const void *X)
Definition: postgres.h:332
static Datum ObjectIdGetDatum(Oid X)
Definition: postgres.h:262
static char * DatumGetCString(Datum X)
Definition: postgres.h:345
uint64_t Datum
Definition: postgres.h:70
static Datum CStringGetDatum(const char *X)
Definition: postgres.h:360
static Datum Int32GetDatum(int32 X)
Definition: postgres.h:222
#define InvalidOid
Definition: postgres_ext.h:37
JsonbValue * result
Definition: jsonb.h:333
JsonbParseState * parseState
Definition: jsonb.h:337
enum jbvType type
Definition: jsonb.h:257
char * val
Definition: jsonb.h:266
Definition: jsonb.h:215
JsonbContainer root
Definition: jsonb.h:217
static ItemArray items
Definition: test_tidstore.c:48
const char * name