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