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 :
10 2 : 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
40 2 : _PG_init(void)
41 : {
42 : /* Asserts verify that typedefs above match original declarations */
43 : AssertVariableIsOfType(&PLyObject_AsString, PLyObject_AsString_t);
44 2 : PLyObject_AsString_p = (PLyObject_AsString_t)
45 2 : load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyObject_AsString",
46 : true, NULL);
47 : AssertVariableIsOfType(&PLyUnicode_FromStringAndSize, PLyUnicode_FromStringAndSize_t);
48 2 : PLyUnicode_FromStringAndSize_p = (PLyUnicode_FromStringAndSize_t)
49 2 : load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize",
50 : true, NULL);
51 : AssertVariableIsOfType(&PLy_elog_impl, PLy_elog_impl_t);
52 2 : PLy_elog_impl_p = (PLy_elog_impl_t)
53 2 : load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLy_elog_impl",
54 : true, NULL);
55 2 : }
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 *
69 42 : PLyUnicode_FromJsonbValue(JsonbValue *jbv)
70 : {
71 : Assert(jbv->type == jbvString);
72 :
73 42 : 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 26 : PLyUnicode_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem)
83 : {
84 26 : jbvElem->type = jbvString;
85 26 : jbvElem->val.string.val = PLyObject_AsString(obj);
86 26 : jbvElem->val.string.len = strlen(jbvElem->val.string.val);
87 26 : }
88 :
89 : /*
90 : * PLyObject_FromJsonbValue
91 : *
92 : * Transform JsonbValue to PyObject.
93 : */
94 : static PyObject *
95 80 : PLyObject_FromJsonbValue(JsonbValue *jsonbValue)
96 : {
97 80 : switch (jsonbValue->type)
98 : {
99 10 : case jbvNull:
100 10 : Py_RETURN_NONE;
101 :
102 8 : case jbvBinary:
103 8 : return PLyObject_FromJsonbContainer(jsonbValue->val.binary.data);
104 :
105 36 : case jbvNumeric:
106 : {
107 : Datum num;
108 : char *str;
109 :
110 36 : num = NumericGetDatum(jsonbValue->val.numeric);
111 36 : str = DatumGetCString(DirectFunctionCall1(numeric_out, num));
112 :
113 36 : return PyObject_CallFunction(decimal_constructor, "s", str);
114 : }
115 :
116 16 : case jbvString:
117 16 : return PLyUnicode_FromJsonbValue(jsonbValue);
118 :
119 10 : case jbvBool:
120 10 : if (jsonbValue->val.boolean)
121 10 : Py_RETURN_TRUE;
122 : else
123 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 *
137 60 : PLyObject_FromJsonbContainer(JsonbContainer *jsonb)
138 : {
139 : JsonbIteratorToken r;
140 : JsonbValue v;
141 : JsonbIterator *it;
142 : PyObject *result;
143 :
144 60 : it = JsonbIteratorInit(jsonb);
145 60 : r = JsonbIteratorNext(&it, &v, true);
146 :
147 60 : switch (r)
148 : {
149 40 : case WJB_BEGIN_ARRAY:
150 40 : if (v.val.array.rawScalar)
151 : {
152 : JsonbValue tmp;
153 :
154 18 : if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
155 18 : (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
156 18 : (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
157 0 : elog(ERROR, "unexpected jsonb token: %d", r);
158 :
159 18 : result = PLyObject_FromJsonbValue(&v);
160 : }
161 : else
162 : {
163 22 : PyObject *volatile elem = NULL;
164 :
165 22 : result = PyList_New(0);
166 22 : if (!result)
167 0 : return NULL;
168 :
169 22 : PG_TRY();
170 : {
171 80 : while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
172 : {
173 58 : if (r != WJB_ELEM)
174 22 : continue;
175 :
176 36 : elem = PLyObject_FromJsonbValue(&v);
177 :
178 36 : PyList_Append(result, elem);
179 36 : Py_XDECREF(elem);
180 36 : elem = NULL;
181 : }
182 : }
183 0 : PG_CATCH();
184 : {
185 0 : Py_XDECREF(elem);
186 0 : Py_XDECREF(result);
187 0 : PG_RE_THROW();
188 : }
189 22 : PG_END_TRY();
190 : }
191 40 : break;
192 :
193 20 : case WJB_BEGIN_OBJECT:
194 : {
195 20 : PyObject *volatile result_v = PyDict_New();
196 20 : PyObject *volatile key = NULL;
197 20 : PyObject *volatile val = NULL;
198 :
199 20 : if (!result_v)
200 0 : return NULL;
201 :
202 20 : PG_TRY();
203 : {
204 66 : while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
205 : {
206 46 : if (r != WJB_KEY)
207 20 : continue;
208 :
209 26 : key = PLyUnicode_FromJsonbValue(&v);
210 26 : if (!key)
211 : {
212 0 : Py_XDECREF(result_v);
213 0 : result_v = NULL;
214 0 : break;
215 : }
216 :
217 26 : if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_VALUE)
218 0 : elog(ERROR, "unexpected jsonb token: %d", r);
219 :
220 26 : val = PLyObject_FromJsonbValue(&v);
221 26 : if (!val)
222 : {
223 0 : Py_XDECREF(key);
224 0 : key = NULL;
225 0 : Py_XDECREF(result_v);
226 0 : result_v = NULL;
227 0 : break;
228 : }
229 :
230 26 : PyDict_SetItem(result_v, key, val);
231 :
232 26 : Py_XDECREF(key);
233 26 : key = NULL;
234 26 : Py_XDECREF(val);
235 26 : val = NULL;
236 : }
237 : }
238 0 : PG_CATCH();
239 : {
240 0 : Py_XDECREF(result_v);
241 0 : Py_XDECREF(key);
242 0 : Py_XDECREF(val);
243 0 : PG_RE_THROW();
244 : }
245 20 : PG_END_TRY();
246 :
247 20 : result = result_v;
248 : }
249 20 : break;
250 :
251 0 : default:
252 0 : elog(ERROR, "unexpected jsonb token: %d", r);
253 : return NULL;
254 : }
255 :
256 60 : return result;
257 : }
258 :
259 : /*
260 : * PLyMapping_ToJsonbValue
261 : *
262 : * Transform Python dict to JsonbValue.
263 : */
264 : static void
265 10 : PLyMapping_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
266 : {
267 : Py_ssize_t pcount;
268 : PyObject *volatile items;
269 :
270 10 : pcount = PyMapping_Size(obj);
271 10 : items = PyMapping_Items(obj);
272 :
273 10 : PG_TRY();
274 : {
275 : Py_ssize_t i;
276 :
277 10 : pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
278 :
279 24 : for (i = 0; i < pcount; i++)
280 : {
281 : JsonbValue jbvKey;
282 14 : PyObject *item = PyList_GetItem(items, i);
283 14 : PyObject *key = PyTuple_GetItem(item, 0);
284 14 : PyObject *value = PyTuple_GetItem(item, 1);
285 :
286 : /* Python dictionary can have None as key */
287 14 : if (key == Py_None)
288 : {
289 2 : jbvKey.type = jbvString;
290 2 : jbvKey.val.string.len = 0;
291 2 : jbvKey.val.string.val = "";
292 : }
293 : else
294 : {
295 : /* All others types of keys we serialize to string */
296 12 : PLyUnicode_ToJsonbValue(key, &jbvKey);
297 : }
298 :
299 14 : pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey);
300 14 : PLyObject_ToJsonbValue(value, jsonb_state, false);
301 : }
302 :
303 10 : pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
304 : }
305 0 : PG_FINALLY();
306 : {
307 10 : Py_DECREF(items);
308 : }
309 10 : PG_END_TRY();
310 10 : }
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
319 20 : PLySequence_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
320 : {
321 : Py_ssize_t i;
322 : Py_ssize_t pcount;
323 20 : PyObject *volatile value = NULL;
324 :
325 20 : pcount = PySequence_Size(obj);
326 :
327 20 : pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
328 :
329 20 : PG_TRY();
330 : {
331 56 : for (i = 0; i < pcount; i++)
332 : {
333 36 : value = PySequence_GetItem(obj, i);
334 : Assert(value);
335 :
336 36 : PLyObject_ToJsonbValue(value, jsonb_state, true);
337 36 : Py_XDECREF(value);
338 36 : value = NULL;
339 : }
340 : }
341 0 : PG_CATCH();
342 : {
343 0 : Py_XDECREF(value);
344 0 : PG_RE_THROW();
345 : }
346 20 : PG_END_TRY();
347 :
348 20 : pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
349 20 : }
350 :
351 : /*
352 : * PLyNumber_ToJsonbValue(PyObject *obj)
353 : *
354 : * Transform python number to JsonbValue.
355 : */
356 : static JsonbValue *
357 32 : PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
358 : {
359 : Numeric num;
360 32 : char *str = PLyObject_AsString(obj);
361 :
362 32 : PG_TRY();
363 : {
364 : Datum numd;
365 :
366 32 : numd = DirectFunctionCall3(numeric_in,
367 : CStringGetDatum(str),
368 : ObjectIdGetDatum(InvalidOid),
369 : Int32GetDatum(-1));
370 30 : num = DatumGetNumeric(numd);
371 : }
372 2 : PG_CATCH();
373 : {
374 2 : ereport(ERROR,
375 : (errcode(ERRCODE_DATATYPE_MISMATCH),
376 : errmsg("could not convert value \"%s\" to jsonb", str)));
377 : }
378 30 : PG_END_TRY();
379 :
380 30 : 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 30 : if (numeric_is_nan(num))
387 0 : ereport(ERROR,
388 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
389 : errmsg("cannot convert NaN to jsonb")));
390 30 : if (numeric_is_inf(num))
391 0 : ereport(ERROR,
392 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
393 : errmsg("cannot convert infinity to jsonb")));
394 :
395 30 : jbvNum->type = jbvNumeric;
396 30 : jbvNum->val.numeric = num;
397 :
398 30 : return jbvNum;
399 : }
400 :
401 : /*
402 : * PLyObject_ToJsonbValue(PyObject *obj)
403 : *
404 : * Transform python object to JsonbValue.
405 : */
406 : static void
407 94 : PLyObject_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state, bool is_elem)
408 : {
409 : JsonbValue *out;
410 :
411 94 : if (!PyUnicode_Check(obj))
412 : {
413 80 : if (PySequence_Check(obj))
414 : {
415 20 : PLySequence_ToJsonbValue(obj, jsonb_state);
416 20 : return;
417 : }
418 60 : else if (PyMapping_Check(obj))
419 : {
420 10 : PLyMapping_ToJsonbValue(obj, jsonb_state);
421 10 : return;
422 : }
423 : }
424 :
425 64 : out = palloc_object(JsonbValue);
426 :
427 64 : if (obj == Py_None)
428 8 : out->type = jbvNull;
429 56 : else if (PyUnicode_Check(obj))
430 14 : PLyUnicode_ToJsonbValue(obj, out);
431 :
432 : /*
433 : * PyNumber_Check() returns true for booleans, so boolean check should
434 : * come first.
435 : */
436 42 : else if (PyBool_Check(obj))
437 : {
438 10 : out->type = jbvBool;
439 10 : out->val.boolean = (obj == Py_True);
440 : }
441 32 : else if (PyNumber_Check(obj))
442 32 : out = PLyNumber_ToJsonbValue(obj, out);
443 : else
444 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 :
449 62 : if (jsonb_state->parseState)
450 : {
451 : /* We're in an array or object, so push value as element or field. */
452 50 : 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 12 : jsonb_state->result = out;
462 : }
463 : }
464 :
465 : /*
466 : * plpython_to_jsonb
467 : *
468 : * Transform python object to Jsonb datum
469 : */
470 4 : PG_FUNCTION_INFO_V1(plpython_to_jsonb);
471 : Datum
472 44 : plpython_to_jsonb(PG_FUNCTION_ARGS)
473 : {
474 44 : PyObject *obj = (PyObject *) PG_GETARG_POINTER(0);
475 44 : JsonbInState jsonb_state = {0};
476 :
477 44 : PLyObject_ToJsonbValue(obj, &jsonb_state, true);
478 42 : 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 : */
486 4 : PG_FUNCTION_INFO_V1(jsonb_to_plpython);
487 : Datum
488 52 : jsonb_to_plpython(PG_FUNCTION_ARGS)
489 : {
490 : PyObject *result;
491 52 : 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 52 : if (!decimal_constructor)
499 : {
500 2 : PyObject *decimal_module = PyImport_ImportModule("cdecimal");
501 :
502 2 : if (!decimal_module)
503 : {
504 2 : PyErr_Clear();
505 2 : decimal_module = PyImport_ImportModule("decimal");
506 : }
507 : Assert(decimal_module);
508 2 : decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal");
509 : }
510 :
511 52 : result = PLyObject_FromJsonbContainer(&in->root);
512 52 : if (!result)
513 0 : PLy_elog(ERROR, "transformation from jsonb to Python failed");
514 :
515 52 : return PointerGetDatum(result);
516 : }
|