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