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