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 : :
164 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 JsonbValue *PLyObject_ToJsonbValue(PyObject *obj,
30 : : JsonbParseState **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
2719 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 *
1279 andres@anarazel.de 69 : 21 : PLyUnicode_FromJsonbValue(JsonbValue *jbv)
70 : : {
2719 peter_e@gmx.net 71 [ - + ]: 21 : Assert(jbv->type == jbvString);
72 : :
1279 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 : : {
2719 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:
1279 andres@anarazel.de 117 : 8 : return PLyUnicode_FromJsonbValue(jsonbValue);
118 : :
2719 peter_e@gmx.net 119 : 5 : case jbvBool:
120 [ + - ]: 5 : if (jsonbValue->val.boolean)
121 : 5 : Py_RETURN_TRUE;
122 : : else
2719 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 *
2719 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)
2719 peter_e@gmx.net 157 [ # # ]:UBC 0 : elog(ERROR, "unexpected jsonb token: %d", r);
158 : :
2719 peter_e@gmx.net 159 :CBC 9 : result = PLyObject_FromJsonbValue(&v);
160 : : }
161 : : else
162 : : {
2345 tgl@sss.pgh.pa.us 163 : 11 : PyObject *volatile elem = NULL;
164 : :
2719 peter_e@gmx.net 165 : 11 : result = PyList_New(0);
166 [ - + ]: 11 : if (!result)
2719 peter_e@gmx.net 167 :UBC 0 : return NULL;
168 : :
2345 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 : :
2719 peter_e@gmx.net 178 : 18 : PyList_Append(result, elem);
179 : 18 : Py_XDECREF(elem);
2345 tgl@sss.pgh.pa.us 180 : 18 : elem = NULL;
181 : : }
182 : : }
2345 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 : : }
2345 tgl@sss.pgh.pa.us 189 [ - + ]:CBC 11 : PG_END_TRY();
190 : : }
2719 peter_e@gmx.net 191 : 20 : break;
192 : :
193 : 10 : case WJB_BEGIN_OBJECT:
194 : : {
2345 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)
2345 tgl@sss.pgh.pa.us 200 :UBC 0 : return NULL;
201 : :
2345 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 : :
1279 andres@anarazel.de 209 : 13 : key = PLyUnicode_FromJsonbValue(&v);
2345 tgl@sss.pgh.pa.us 210 [ - + ]: 13 : if (!key)
211 : : {
2345 tgl@sss.pgh.pa.us 212 :UBC 0 : Py_XDECREF(result_v);
213 : 0 : result_v = NULL;
214 : 0 : break;
215 : : }
216 : :
2345 tgl@sss.pgh.pa.us 217 [ - + ]:CBC 13 : if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_VALUE)
2345 tgl@sss.pgh.pa.us 218 [ # # ]:UBC 0 : elog(ERROR, "unexpected jsonb token: %d", r);
219 : :
2345 tgl@sss.pgh.pa.us 220 :CBC 13 : val = PLyObject_FromJsonbValue(&v);
221 [ - + ]: 13 : if (!val)
222 : : {
2719 peter_e@gmx.net 223 :UBC 0 : Py_XDECREF(key);
2345 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 : :
2345 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 : : }
2345 tgl@sss.pgh.pa.us 238 :UBC 0 : PG_CATCH();
239 : : {
240 : 0 : Py_XDECREF(result_v);
2719 peter_e@gmx.net 241 : 0 : Py_XDECREF(key);
2345 tgl@sss.pgh.pa.us 242 : 0 : Py_XDECREF(val);
243 : 0 : PG_RE_THROW();
244 : : }
2345 tgl@sss.pgh.pa.us 245 [ - + ]:CBC 10 : PG_END_TRY();
246 : :
247 : 10 : result = result_v;
248 : : }
2719 peter_e@gmx.net 249 : 10 : break;
250 : :
2719 peter_e@gmx.net 251 :UBC 0 : default:
252 [ # # ]: 0 : elog(ERROR, "unexpected jsonb token: %d", r);
253 : : return NULL;
254 : : }
255 : :
2719 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 JsonbValue *
265 : 5 : PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
266 : : {
267 : : Py_ssize_t pcount;
268 : : PyObject *volatile items;
269 : : JsonbValue *volatile out;
270 : :
271 : 5 : pcount = PyMapping_Size(obj);
2368 peter@eisentraut.org 272 : 5 : items = PyMapping_Items(obj);
273 : :
2719 peter_e@gmx.net 274 [ + - ]: 5 : PG_TRY();
275 : : {
276 : : Py_ssize_t i;
277 : :
278 : 5 : pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
279 : :
280 [ + + ]: 12 : for (i = 0; i < pcount; i++)
281 : : {
282 : : JsonbValue jbvKey;
283 : 7 : PyObject *item = PyList_GetItem(items, i);
284 : 7 : PyObject *key = PyTuple_GetItem(item, 0);
285 : 7 : PyObject *value = PyTuple_GetItem(item, 1);
286 : :
287 : : /* Python dictionary can have None as key */
288 [ + + ]: 7 : if (key == Py_None)
289 : : {
290 : 1 : jbvKey.type = jbvString;
291 : 1 : jbvKey.val.string.len = 0;
292 : 1 : jbvKey.val.string.val = "";
293 : : }
294 : : else
295 : : {
296 : : /* All others types of keys we serialize to string */
1279 andres@anarazel.de 297 : 6 : PLyUnicode_ToJsonbValue(key, &jbvKey);
298 : : }
299 : :
2719 peter_e@gmx.net 300 : 7 : (void) pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey);
301 : 7 : (void) PLyObject_ToJsonbValue(value, jsonb_state, false);
302 : : }
303 : :
304 : 5 : out = pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
305 : : }
2136 peter@eisentraut.org 306 :UBC 0 : PG_FINALLY();
307 : : {
2368 peter@eisentraut.org 308 :CBC 5 : Py_DECREF(items);
309 : : }
2719 peter_e@gmx.net 310 [ - + ]: 5 : PG_END_TRY();
311 : :
312 : 5 : return out;
313 : : }
314 : :
315 : : /*
316 : : * PLySequence_ToJsonbValue
317 : : *
318 : : * Transform python list to JsonbValue. Expects transformed PyObject and
319 : : * a state required for jsonb construction.
320 : : */
321 : : static JsonbValue *
322 : 10 : PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
323 : : {
324 : : Py_ssize_t i;
325 : : Py_ssize_t pcount;
2345 tgl@sss.pgh.pa.us 326 : 10 : PyObject *volatile value = NULL;
327 : :
2719 peter_e@gmx.net 328 : 10 : pcount = PySequence_Size(obj);
329 : :
330 : 10 : pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
331 : :
2345 tgl@sss.pgh.pa.us 332 [ + - ]: 10 : PG_TRY();
333 : : {
334 [ + + ]: 28 : for (i = 0; i < pcount; i++)
335 : : {
336 : 18 : value = PySequence_GetItem(obj, i);
337 [ - + ]: 18 : Assert(value);
338 : :
339 : 18 : (void) PLyObject_ToJsonbValue(value, jsonb_state, true);
340 : 18 : Py_XDECREF(value);
341 : 18 : value = NULL;
342 : : }
343 : : }
2345 tgl@sss.pgh.pa.us 344 :UBC 0 : PG_CATCH();
345 : : {
2640 akorotkov@postgresql 346 : 0 : Py_XDECREF(value);
2345 tgl@sss.pgh.pa.us 347 : 0 : PG_RE_THROW();
348 : : }
2345 tgl@sss.pgh.pa.us 349 [ - + ]:CBC 10 : PG_END_TRY();
350 : :
2719 peter_e@gmx.net 351 : 10 : return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
352 : : }
353 : :
354 : : /*
355 : : * PLyNumber_ToJsonbValue(PyObject *obj)
356 : : *
357 : : * Transform python number to JsonbValue.
358 : : */
359 : : static JsonbValue *
360 : 16 : PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
361 : : {
362 : : Numeric num;
363 : 16 : char *str = PLyObject_AsString(obj);
364 : :
365 [ + + ]: 16 : PG_TRY();
366 : : {
367 : : Datum numd;
368 : :
2684 tgl@sss.pgh.pa.us 369 : 16 : numd = DirectFunctionCall3(numeric_in,
370 : : CStringGetDatum(str),
371 : : ObjectIdGetDatum(InvalidOid),
372 : : Int32GetDatum(-1));
373 : 15 : num = DatumGetNumeric(numd);
374 : : }
2719 peter_e@gmx.net 375 : 1 : PG_CATCH();
376 : : {
377 [ + - ]: 1 : ereport(ERROR,
378 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
379 : : errmsg("could not convert value \"%s\" to jsonb", str)));
380 : : }
381 [ - + ]: 15 : PG_END_TRY();
382 : :
383 : 15 : pfree(str);
384 : :
385 : : /*
386 : : * jsonb doesn't allow NaN or infinity (per JSON specification), so we
387 : : * have to reject those here explicitly.
388 : : */
2684 389 [ - + ]: 15 : if (numeric_is_nan(num))
2684 peter_e@gmx.net 390 [ # # ]:UBC 0 : ereport(ERROR,
391 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
392 : : errmsg("cannot convert NaN to jsonb")));
1872 tgl@sss.pgh.pa.us 393 [ - + ]:CBC 15 : if (numeric_is_inf(num))
1872 tgl@sss.pgh.pa.us 394 [ # # ]:UBC 0 : ereport(ERROR,
395 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
396 : : errmsg("cannot convert infinity to jsonb")));
397 : :
2719 peter_e@gmx.net 398 :CBC 15 : jbvNum->type = jbvNumeric;
399 : 15 : jbvNum->val.numeric = num;
400 : :
401 : 15 : return jbvNum;
402 : : }
403 : :
404 : : /*
405 : : * PLyObject_ToJsonbValue(PyObject *obj)
406 : : *
407 : : * Transform python object to JsonbValue.
408 : : */
409 : : static JsonbValue *
410 : 47 : PLyObject_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state, bool is_elem)
411 : : {
412 : : JsonbValue *out;
413 : :
1279 andres@anarazel.de 414 [ + + ]: 47 : if (!PyUnicode_Check(obj))
415 : : {
2719 peter_e@gmx.net 416 [ + + ]: 40 : if (PySequence_Check(obj))
417 : 10 : return PLySequence_ToJsonbValue(obj, jsonb_state);
418 [ + + ]: 30 : else if (PyMapping_Check(obj))
419 : 5 : return PLyMapping_ToJsonbValue(obj, jsonb_state);
420 : : }
421 : :
2046 tgl@sss.pgh.pa.us 422 : 32 : out = palloc(sizeof(JsonbValue));
423 : :
2719 peter_e@gmx.net 424 [ + + ]: 32 : if (obj == Py_None)
425 : 4 : out->type = jbvNull;
1279 andres@anarazel.de 426 [ + + ]: 28 : else if (PyUnicode_Check(obj))
427 : 7 : PLyUnicode_ToJsonbValue(obj, out);
428 : :
429 : : /*
430 : : * PyNumber_Check() returns true for booleans, so boolean check should
431 : : * come first.
432 : : */
2719 peter_e@gmx.net 433 [ + + ]: 21 : else if (PyBool_Check(obj))
434 : : {
435 : 5 : out->type = jbvBool;
436 : 5 : out->val.boolean = (obj == Py_True);
437 : : }
438 [ + - ]: 16 : else if (PyNumber_Check(obj))
439 : 16 : out = PLyNumber_ToJsonbValue(obj, out);
440 : : else
2719 peter_e@gmx.net 441 [ # # ]:UBC 0 : ereport(ERROR,
442 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
443 : : errmsg("Python type \"%s\" cannot be transformed to jsonb",
444 : : PLyObject_AsString((PyObject *) obj->ob_type))));
445 : :
446 : : /* Push result into 'jsonb_state' unless it is raw scalar value. */
2719 peter_e@gmx.net 447 :CBC 31 : return (*jsonb_state ?
448 [ + + + + ]: 31 : pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, out) :
449 : : out);
450 : : }
451 : :
452 : : /*
453 : : * plpython_to_jsonb
454 : : *
455 : : * Transform python object to Jsonb datum
456 : : */
457 : 2 : PG_FUNCTION_INFO_V1(plpython_to_jsonb);
458 : : Datum
459 : 22 : plpython_to_jsonb(PG_FUNCTION_ARGS)
460 : : {
461 : : PyObject *obj;
462 : : JsonbValue *out;
463 : 22 : JsonbParseState *jsonb_state = NULL;
464 : :
465 : 22 : obj = (PyObject *) PG_GETARG_POINTER(0);
466 : 22 : out = PLyObject_ToJsonbValue(obj, &jsonb_state, true);
467 : 21 : PG_RETURN_POINTER(JsonbValueToJsonb(out));
468 : : }
469 : :
470 : : /*
471 : : * jsonb_to_plpython
472 : : *
473 : : * Transform Jsonb datum to PyObject and return it as internal.
474 : : */
475 : 2 : PG_FUNCTION_INFO_V1(jsonb_to_plpython);
476 : : Datum
477 : 26 : jsonb_to_plpython(PG_FUNCTION_ARGS)
478 : : {
479 : : PyObject *result;
480 : 26 : Jsonb *in = PG_GETARG_JSONB_P(0);
481 : :
482 : : /*
483 : : * Initialize pointer to Decimal constructor. First we try "cdecimal", C
484 : : * version of decimal library. In case of failure we use slower "decimal"
485 : : * module.
486 : : */
487 [ + + ]: 26 : if (!decimal_constructor)
488 : : {
489 : 1 : PyObject *decimal_module = PyImport_ImportModule("cdecimal");
490 : :
491 [ + - ]: 1 : if (!decimal_module)
492 : : {
493 : 1 : PyErr_Clear();
494 : 1 : decimal_module = PyImport_ImportModule("decimal");
495 : : }
496 [ - + ]: 1 : Assert(decimal_module);
497 : 1 : decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal");
498 : : }
499 : :
500 : 26 : result = PLyObject_FromJsonbContainer(&in->root);
501 [ - + ]: 26 : if (!result)
2719 peter_e@gmx.net 502 :UBC 0 : PLy_elog(ERROR, "transformation from jsonb to Python failed");
503 : :
2719 peter_e@gmx.net 504 :CBC 26 : return PointerGetDatum(result);
505 : : }
|