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 #if PY_MAJOR_VERSION >= 3
32 typedef PyObject *(*PLyUnicode_FromStringAndSize_t)
33  (const char *s, Py_ssize_t size);
34 static PLyUnicode_FromStringAndSize_t PLyUnicode_FromStringAndSize_p;
35 #endif
36 
37 /*
38  * Module initialize function: fetch function pointers for cross-module calls.
39  */
40 void
41 _PG_init(void)
42 {
43  /* Asserts verify that typedefs above match original declarations */
46  load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyObject_AsString",
47  true, NULL);
48 #if PY_MAJOR_VERSION >= 3
49  AssertVariableIsOfType(&PLyUnicode_FromStringAndSize, PLyUnicode_FromStringAndSize_t);
50  PLyUnicode_FromStringAndSize_p = (PLyUnicode_FromStringAndSize_t)
51  load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize",
52  true, NULL);
53 #endif
54 
57  load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLy_elog_impl",
58  true, NULL);
59 }
60 
61 /* These defines must be after the _PG_init */
62 #define PLyObject_AsString (PLyObject_AsString_p)
63 #define PLyUnicode_FromStringAndSize (PLyUnicode_FromStringAndSize_p)
64 #undef PLy_elog
65 #define PLy_elog (PLy_elog_impl_p)
66 
67 /*
68  * PLyString_FromJsonbValue
69  *
70  * Transform string JsonbValue to Python string.
71  */
72 static PyObject *
74 {
75  Assert(jbv->type == jbvString);
76 
77  return PyString_FromStringAndSize(jbv->val.string.val, jbv->val.string.len);
78 }
79 
80 /*
81  * PLyString_ToJsonbValue
82  *
83  * Transform Python string to JsonbValue.
84  */
85 static void
86 PLyString_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem)
87 {
88  jbvElem->type = jbvString;
89  jbvElem->val.string.val = PLyObject_AsString(obj);
90  jbvElem->val.string.len = strlen(jbvElem->val.string.val);
91 }
92 
93 /*
94  * PLyObject_FromJsonbValue
95  *
96  * Transform JsonbValue to PyObject.
97  */
98 static PyObject *
100 {
101  switch (jsonbValue->type)
102  {
103  case jbvNull:
104  Py_RETURN_NONE;
105 
106  case jbvBinary:
107  return PLyObject_FromJsonbContainer(jsonbValue->val.binary.data);
108 
109  case jbvNumeric:
110  {
111  Datum num;
112  char *str;
113 
114  num = NumericGetDatum(jsonbValue->val.numeric);
116 
117  return PyObject_CallFunction(decimal_constructor, "s", str);
118  }
119 
120  case jbvString:
121  return PLyString_FromJsonbValue(jsonbValue);
122 
123  case jbvBool:
124  if (jsonbValue->val.boolean)
125  Py_RETURN_TRUE;
126  else
127  Py_RETURN_FALSE;
128 
129  default:
130  elog(ERROR, "unexpected jsonb value type: %d", jsonbValue->type);
131  return NULL;
132  }
133 }
134 
135 /*
136  * PLyObject_FromJsonbContainer
137  *
138  * Transform JsonbContainer to PyObject.
139  */
140 static PyObject *
142 {
144  JsonbValue v;
145  JsonbIterator *it;
146  PyObject *result;
147 
148  it = JsonbIteratorInit(jsonb);
149  r = JsonbIteratorNext(&it, &v, true);
150 
151  switch (r)
152  {
153  case WJB_BEGIN_ARRAY:
154  if (v.val.array.rawScalar)
155  {
156  JsonbValue tmp;
157 
158  if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
159  (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
160  (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
161  elog(ERROR, "unexpected jsonb token: %d", r);
162 
163  result = PLyObject_FromJsonbValue(&v);
164  }
165  else
166  {
167  PyObject *volatile elem = NULL;
168 
169  result = PyList_New(0);
170  if (!result)
171  return NULL;
172 
173  PG_TRY();
174  {
175  while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
176  {
177  if (r != WJB_ELEM)
178  continue;
179 
180  elem = PLyObject_FromJsonbValue(&v);
181 
182  PyList_Append(result, elem);
183  Py_XDECREF(elem);
184  elem = NULL;
185  }
186  }
187  PG_CATCH();
188  {
189  Py_XDECREF(elem);
190  Py_XDECREF(result);
191  PG_RE_THROW();
192  }
193  PG_END_TRY();
194  }
195  break;
196 
197  case WJB_BEGIN_OBJECT:
198  {
199  PyObject *volatile result_v = PyDict_New();
200  PyObject *volatile key = NULL;
201  PyObject *volatile val = NULL;
202 
203  if (!result_v)
204  return NULL;
205 
206  PG_TRY();
207  {
208  while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
209  {
210  if (r != WJB_KEY)
211  continue;
212 
213  key = PLyString_FromJsonbValue(&v);
214  if (!key)
215  {
216  Py_XDECREF(result_v);
217  result_v = NULL;
218  break;
219  }
220 
221  if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_VALUE)
222  elog(ERROR, "unexpected jsonb token: %d", r);
223 
224  val = PLyObject_FromJsonbValue(&v);
225  if (!val)
226  {
227  Py_XDECREF(key);
228  key = NULL;
229  Py_XDECREF(result_v);
230  result_v = NULL;
231  break;
232  }
233 
234  PyDict_SetItem(result_v, key, val);
235 
236  Py_XDECREF(key);
237  key = NULL;
238  Py_XDECREF(val);
239  val = NULL;
240  }
241  }
242  PG_CATCH();
243  {
244  Py_XDECREF(result_v);
245  Py_XDECREF(key);
246  Py_XDECREF(val);
247  PG_RE_THROW();
248  }
249  PG_END_TRY();
250 
251  result = result_v;
252  }
253  break;
254 
255  default:
256  elog(ERROR, "unexpected jsonb token: %d", r);
257  return NULL;
258  }
259 
260  return result;
261 }
262 
263 /*
264  * PLyMapping_ToJsonbValue
265  *
266  * Transform Python dict to JsonbValue.
267  */
268 static JsonbValue *
269 PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
270 {
271  Py_ssize_t pcount;
272  PyObject *volatile items;
273  JsonbValue *volatile out;
274 
275  pcount = PyMapping_Size(obj);
276  items = PyMapping_Items(obj);
277 
278  PG_TRY();
279  {
280  Py_ssize_t i;
281 
282  pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
283 
284  for (i = 0; i < pcount; i++)
285  {
286  JsonbValue jbvKey;
287  PyObject *item = PyList_GetItem(items, i);
288  PyObject *key = PyTuple_GetItem(item, 0);
289  PyObject *value = PyTuple_GetItem(item, 1);
290 
291  /* Python dictionary can have None as key */
292  if (key == Py_None)
293  {
294  jbvKey.type = jbvString;
295  jbvKey.val.string.len = 0;
296  jbvKey.val.string.val = "";
297  }
298  else
299  {
300  /* All others types of keys we serialize to string */
301  PLyString_ToJsonbValue(key, &jbvKey);
302  }
303 
304  (void) pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey);
305  (void) PLyObject_ToJsonbValue(value, jsonb_state, false);
306  }
307 
308  out = pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
309  }
310  PG_FINALLY();
311  {
312  Py_DECREF(items);
313  }
314  PG_END_TRY();
315 
316  return out;
317 }
318 
319 /*
320  * PLySequence_ToJsonbValue
321  *
322  * Transform python list to JsonbValue. Expects transformed PyObject and
323  * a state required for jsonb construction.
324  */
325 static JsonbValue *
326 PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
327 {
328  Py_ssize_t i;
329  Py_ssize_t pcount;
330  PyObject *volatile value = NULL;
331 
332  pcount = PySequence_Size(obj);
333 
334  pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
335 
336  PG_TRY();
337  {
338  for (i = 0; i < pcount; i++)
339  {
340  value = PySequence_GetItem(obj, i);
341  Assert(value);
342 
343  (void) PLyObject_ToJsonbValue(value, jsonb_state, true);
344  Py_XDECREF(value);
345  value = NULL;
346  }
347  }
348  PG_CATCH();
349  {
350  Py_XDECREF(value);
351  PG_RE_THROW();
352  }
353  PG_END_TRY();
354 
355  return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
356 }
357 
358 /*
359  * PLyNumber_ToJsonbValue(PyObject *obj)
360  *
361  * Transform python number to JsonbValue.
362  */
363 static JsonbValue *
364 PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
365 {
366  Numeric num;
367  char *str = PLyObject_AsString(obj);
368 
369  PG_TRY();
370  {
371  Datum numd;
372 
374  CStringGetDatum(str),
376  Int32GetDatum(-1));
377  num = DatumGetNumeric(numd);
378  }
379  PG_CATCH();
380  {
381  ereport(ERROR,
382  (errcode(ERRCODE_DATATYPE_MISMATCH),
383  (errmsg("could not convert value \"%s\" to jsonb", str))));
384  }
385  PG_END_TRY();
386 
387  pfree(str);
388 
389  /*
390  * jsonb doesn't allow NaN (per JSON specification), so we have to prevent
391  * it here explicitly. (Infinity is also not allowed in jsonb, but
392  * numeric_in above already catches that.)
393  */
394  if (numeric_is_nan(num))
395  ereport(ERROR,
396  (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
397  (errmsg("cannot convert NaN to jsonb"))));
398 
399  jbvNum->type = jbvNumeric;
400  jbvNum->val.numeric = num;
401 
402  return jbvNum;
403 }
404 
405 /*
406  * PLyObject_ToJsonbValue(PyObject *obj)
407  *
408  * Transform python object to JsonbValue.
409  */
410 static JsonbValue *
411 PLyObject_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state, bool is_elem)
412 {
413  JsonbValue buf;
414  JsonbValue *out;
415 
416  if (!(PyString_Check(obj) || PyUnicode_Check(obj)))
417  {
418  if (PySequence_Check(obj))
419  return PLySequence_ToJsonbValue(obj, jsonb_state);
420  else if (PyMapping_Check(obj))
421  return PLyMapping_ToJsonbValue(obj, jsonb_state);
422  }
423 
424  /* Allocate JsonbValue in heap only if it is raw scalar value. */
425  if (*jsonb_state)
426  out = &buf;
427  else
428  out = palloc(sizeof(JsonbValue));
429 
430  if (obj == Py_None)
431  out->type = jbvNull;
432  else if (PyString_Check(obj) || PyUnicode_Check(obj))
433  PLyString_ToJsonbValue(obj, out);
434 
435  /*
436  * PyNumber_Check() returns true for booleans, so boolean check should
437  * come first.
438  */
439  else if (PyBool_Check(obj))
440  {
441  out->type = jbvBool;
442  out->val.boolean = (obj == Py_True);
443  }
444  else if (PyNumber_Check(obj))
445  out = PLyNumber_ToJsonbValue(obj, out);
446  else
447  ereport(ERROR,
448  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
449  (errmsg("Python type \"%s\" cannot be transformed to jsonb",
450  PLyObject_AsString((PyObject *) obj->ob_type)))));
451 
452  /* Push result into 'jsonb_state' unless it is raw scalar value. */
453  return (*jsonb_state ?
454  pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, out) :
455  out);
456 }
457 
458 /*
459  * plpython_to_jsonb
460  *
461  * Transform python object to Jsonb datum
462  */
464 Datum
466 {
467  PyObject *obj;
468  JsonbValue *out;
469  JsonbParseState *jsonb_state = NULL;
470 
471  obj = (PyObject *) PG_GETARG_POINTER(0);
472  out = PLyObject_ToJsonbValue(obj, &jsonb_state, true);
474 }
475 
476 /*
477  * jsonb_to_plpython
478  *
479  * Transform Jsonb datum to PyObject and return it as internal.
480  */
482 Datum
484 {
485  PyObject *result;
486  Jsonb *in = PG_GETARG_JSONB_P(0);
487 
488  /*
489  * Initialize pointer to Decimal constructor. First we try "cdecimal", C
490  * version of decimal library. In case of failure we use slower "decimal"
491  * module.
492  */
493  if (!decimal_constructor)
494  {
495  PyObject *decimal_module = PyImport_ImportModule("cdecimal");
496 
497  if (!decimal_module)
498  {
499  PyErr_Clear();
500  decimal_module = PyImport_ImportModule("decimal");
501  }
502  Assert(decimal_module);
503  decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal");
504  }
505 
506  result = PLyObject_FromJsonbContainer(&in->root);
507  if (!result)
508  PLy_elog(ERROR, "transformation from jsonb to Python failed");
509 
510  return PointerGetDatum(result);
511 }
#define PG_RETURN_POINTER(x)
Definition: fmgr.h:351
static JsonbValue * PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
Jsonb * JsonbValueToJsonb(JsonbValue *val)
Definition: jsonb_util.c:84
Definition: jsonb.h:220
#define PointerGetDatum(X)
Definition: postgres.h:556
char * val
Definition: jsonb.h:272
#define NumericGetDatum(X)
Definition: numeric.h:51
Definition: jsonb.h:239
Datum numeric_out(PG_FUNCTION_ARGS)
Definition: numeric.c:655
Definition: jsonb.h:22
static struct @145 value
void PLy_elog_impl(int elevel, const char *fmt,...)
Definition: plpy_elog.c:47
int errcode(int sqlerrcode)
Definition: elog.c:608
#define PG_GETARG_POINTER(n)
Definition: fmgr.h:271
Definition: jsonb.h:236
#define DirectFunctionCall1(func, arg1)
Definition: fmgr.h:615
PG_FUNCTION_INFO_V1(plpython_to_jsonb)
int Py_ssize_t
Definition: plpython.h:66
JsonbValue * pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq, JsonbValue *jbval)
Definition: jsonb_util.c:558
void pfree(void *pointer)
Definition: mcxt.c:1056
void _PG_init(void)
static void PLyString_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem)
#define ObjectIdGetDatum(X)
Definition: postgres.h:507
#define ERROR
Definition: elog.h:43
static PyObject * decimal_constructor
bool numeric_is_nan(Numeric num)
Definition: numeric.c:683
#define DatumGetCString(X)
Definition: postgres.h:566
#define PLy_elog
static char * buf
Definition: pg_test_fsync.c:67
#define PLyObject_AsString
#define CStringGetDatum(X)
Definition: postgres.h:578
void(* PLy_elog_impl_t)(int elevel, const char *fmt,...)
Datum numeric_in(PG_FUNCTION_ARGS)
Definition: numeric.c:573
Definition: jsonb.h:23
PGFunction load_external_function(const char *filename, const char *funcname, bool signalNotFound, void **filehandle)
Definition: dfmgr.c:107
#define ereport(elevel, rest)
Definition: elog.h:141
JsonbIteratorToken
Definition: jsonb.h:20
#define DirectFunctionCall3(func, arg1, arg2, arg3)
Definition: fmgr.h:619
JsonbContainer root
Definition: jsonb.h:223
static int elevel
Definition: vacuumlazy.c:143
#define PG_FINALLY()
Definition: elog.h:339
uintptr_t Datum
Definition: postgres.h:367
static PyObject * PLyObject_FromJsonbContainer(JsonbContainer *jsonb)
static JsonbValue * PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
#define InvalidOid
Definition: postgres_ext.h:36
PG_MODULE_MAGIC
static PyObject * PLyString_FromJsonbValue(JsonbValue *jbv)
char *(* PLyObject_AsString_t)(PyObject *plrv)
#define PG_CATCH()
Definition: elog.h:332
#define DatumGetNumeric(X)
Definition: numeric.h:49
Datum plpython_to_jsonb(PG_FUNCTION_ARGS)
#define Assert(condition)
Definition: c.h:733
JsonbIterator * JsonbIteratorInit(JsonbContainer *container)
Definition: jsonb_util.c:759
#define PLyUnicode_FromStringAndSize
static PLy_elog_impl_t PLy_elog_impl_p
static JsonbValue * PLyObject_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state, bool is_elem)
#define PG_RE_THROW()
Definition: elog.h:363
enum jbvType type
Definition: jsonb.h:263
Datum jsonb_to_plpython(PG_FUNCTION_ARGS)
static PLyObject_AsString_t PLyObject_AsString_p
#define Int32GetDatum(X)
Definition: postgres.h:479
void * palloc(Size size)
Definition: mcxt.c:949
int errmsg(const char *fmt,...)
Definition: elog.c:822
#define elog(elevel,...)
Definition: elog.h:228
int i
#define PG_FUNCTION_ARGS
Definition: fmgr.h:188
#define PG_TRY()
Definition: elog.h:322
static JsonbValue * PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
long val
Definition: informix.c:684
#define PG_END_TRY()
Definition: elog.h:347
JsonbIteratorToken JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested)
Definition: jsonb_util.c:795
Definition: jsonb.h:25
#define PG_GETARG_JSONB_P(x)
Definition: jsonb.h:74
static PyObject * PLyObject_FromJsonbValue(JsonbValue *jsonbValue)
#define AssertVariableIsOfType(varname, typename)
Definition: c.h:882