LCOV - differential code coverage report
Current view: top level - contrib/jsonb_plpython - jsonb_plpython.c (source / functions) Coverage Total Hit LBC UIC UBC GBC GIC CBC EUB ECB
Current: Differential Code Coverage HEAD vs 15 Lines: 82.7 % 196 162 9 14 11 9 81 72 14 81
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 14 14 13 1 13
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

           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                 : 
      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 ECB             :  */
      36                 : void
      37 GIC           1 : _PG_init(void)
      38                 : {
      39 ECB             :     /* Asserts verify that typedefs above match original declarations */
      40                 :     AssertVariableIsOfType(&PLyObject_AsString, PLyObject_AsString_t);
      41 GIC           1 :     PLyObject_AsString_p = (PLyObject_AsString_t)
      42               1 :         load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyObject_AsString",
      43 ECB             :                                true, NULL);
      44                 :     AssertVariableIsOfType(&PLyUnicode_FromStringAndSize, PLyUnicode_FromStringAndSize_t);
      45 GIC           1 :     PLyUnicode_FromStringAndSize_p = (PLyUnicode_FromStringAndSize_t)
      46               1 :         load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize",
      47 ECB             :                                true, NULL);
      48                 :     AssertVariableIsOfType(&PLy_elog_impl, PLy_elog_impl_t);
      49 GIC           1 :     PLy_elog_impl_p = (PLy_elog_impl_t)
      50 CBC           1 :         load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLy_elog_impl",
      51                 :                                true, NULL);
      52 GIC           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 ECB             :  */
      65                 : static PyObject *
      66 CBC          21 : PLyUnicode_FromJsonbValue(JsonbValue *jbv)
      67                 : {
      68              21 :     Assert(jbv->type == jbvString);
      69                 : 
      70 GIC          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 ECB             :  */
      78                 : static void
      79 CBC          13 : PLyUnicode_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem)
      80 ECB             : {
      81 CBC          13 :     jbvElem->type = jbvString;
      82              13 :     jbvElem->val.string.val = PLyObject_AsString(obj);
      83 GIC          13 :     jbvElem->val.string.len = strlen(jbvElem->val.string.val);
      84              13 : }
      85                 : 
      86                 : /*
      87                 :  * PLyObject_FromJsonbValue
      88                 :  *
      89                 :  * Transform JsonbValue to PyObject.
      90 ECB             :  */
      91                 : static PyObject *
      92 CBC          40 : PLyObject_FromJsonbValue(JsonbValue *jsonbValue)
      93                 : {
      94              40 :     switch (jsonbValue->type)
      95 ECB             :     {
      96 GIC           5 :         case jbvNull:
      97 CBC           5 :             Py_RETURN_NONE;
      98 ECB             : 
      99 GIC           4 :         case jbvBinary:
     100 CBC           4 :             return PLyObject_FromJsonbContainer(jsonbValue->val.binary.data);
     101                 : 
     102 GIC          18 :         case jbvNumeric:
     103                 :             {
     104                 :                 Datum       num;
     105 ECB             :                 char       *str;
     106                 : 
     107 GIC          18 :                 num = NumericGetDatum(jsonbValue->val.numeric);
     108 CBC          18 :                 str = DatumGetCString(DirectFunctionCall1(numeric_out, num));
     109                 : 
     110 GIC          18 :                 return PyObject_CallFunction(decimal_constructor, "s", str);
     111 ECB             :             }
     112                 : 
     113 GIC           8 :         case jbvString:
     114 CBC           8 :             return PLyUnicode_FromJsonbValue(jsonbValue);
     115 ECB             : 
     116 CBC           5 :         case jbvBool:
     117 GIC           5 :             if (jsonbValue->val.boolean)
     118 GBC           5 :                 Py_RETURN_TRUE;
     119                 :             else
     120 UBC           0 :                 Py_RETURN_FALSE;
     121 EUB             : 
     122 UIC           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 ECB             :  */
     133                 : static PyObject *
     134 GIC          30 : PLyObject_FromJsonbContainer(JsonbContainer *jsonb)
     135                 : {
     136                 :     JsonbIteratorToken r;
     137                 :     JsonbValue  v;
     138                 :     JsonbIterator *it;
     139 ECB             :     PyObject   *result;
     140                 : 
     141 GIC          30 :     it = JsonbIteratorInit(jsonb);
     142 CBC          30 :     r = JsonbIteratorNext(&it, &v, true);
     143                 : 
     144              30 :     switch (r)
     145 ECB             :     {
     146 GIC          20 :         case WJB_BEGIN_ARRAY:
     147              20 :             if (v.val.array.rawScalar)
     148                 :             {
     149 ECB             :                 JsonbValue  tmp;
     150                 : 
     151 CBC           9 :                 if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
     152 GBC           9 :                     (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
     153 GIC           9 :                     (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
     154 LBC           0 :                     elog(ERROR, "unexpected jsonb token: %d", r);
     155                 : 
     156 GIC           9 :                 result = PLyObject_FromJsonbValue(&v);
     157                 :             }
     158 ECB             :             else
     159                 :             {
     160 CBC          11 :                 PyObject   *volatile elem = NULL;
     161 ECB             : 
     162 GBC          11 :                 result = PyList_New(0);
     163 GIC          11 :                 if (!result)
     164 LBC           0 :                     return NULL;
     165                 : 
     166 CBC          11 :                 PG_TRY();
     167                 :                 {
     168              40 :                     while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
     169 ECB             :                     {
     170 GIC          29 :                         if (r != WJB_ELEM)
     171 CBC          11 :                             continue;
     172                 : 
     173              18 :                         elem = PLyObject_FromJsonbValue(&v);
     174 ECB             : 
     175 CBC          18 :                         PyList_Append(result, elem);
     176 GIC          18 :                         Py_XDECREF(elem);
     177              18 :                         elem = NULL;
     178 EUB             :                     }
     179                 :                 }
     180 UBC           0 :                 PG_CATCH();
     181 EUB             :                 {
     182 UBC           0 :                     Py_XDECREF(elem);
     183 UIC           0 :                     Py_XDECREF(result);
     184 LBC           0 :                     PG_RE_THROW();
     185                 :                 }
     186 CBC          11 :                 PG_END_TRY();
     187                 :             }
     188              20 :             break;
     189                 : 
     190              10 :         case WJB_BEGIN_OBJECT:
     191 ECB             :             {
     192 CBC          10 :                 PyObject   *volatile result_v = PyDict_New();
     193 GIC          10 :                 PyObject   *volatile key = NULL;
     194 CBC          10 :                 PyObject   *volatile val = NULL;
     195 EUB             : 
     196 GIC          10 :                 if (!result_v)
     197 LBC           0 :                     return NULL;
     198                 : 
     199 CBC          10 :                 PG_TRY();
     200                 :                 {
     201              33 :                     while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
     202 ECB             :                     {
     203 GIC          23 :                         if (r != WJB_KEY)
     204 CBC          10 :                             continue;
     205 ECB             : 
     206 GIC          13 :                         key = PLyUnicode_FromJsonbValue(&v);
     207 GBC          13 :                         if (!key)
     208 EUB             :                         {
     209 UBC           0 :                             Py_XDECREF(result_v);
     210 UIC           0 :                             result_v = NULL;
     211               0 :                             break;
     212 ECB             :                         }
     213 EUB             : 
     214 GIC          13 :                         if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_VALUE)
     215 LBC           0 :                             elog(ERROR, "unexpected jsonb token: %d", r);
     216 ECB             : 
     217 GIC          13 :                         val = PLyObject_FromJsonbValue(&v);
     218 GBC          13 :                         if (!val)
     219 EUB             :                         {
     220 UBC           0 :                             Py_XDECREF(key);
     221               0 :                             key = NULL;
     222               0 :                             Py_XDECREF(result_v);
     223 UIC           0 :                             result_v = NULL;
     224               0 :                             break;
     225 ECB             :                         }
     226                 : 
     227 CBC          13 :                         PyDict_SetItem(result_v, key, val);
     228 ECB             : 
     229 CBC          13 :                         Py_XDECREF(key);
     230              13 :                         key = NULL;
     231 GIC          13 :                         Py_XDECREF(val);
     232              13 :                         val = NULL;
     233 EUB             :                     }
     234                 :                 }
     235 UBC           0 :                 PG_CATCH();
     236 EUB             :                 {
     237 UBC           0 :                     Py_XDECREF(result_v);
     238               0 :                     Py_XDECREF(key);
     239 UIC           0 :                     Py_XDECREF(val);
     240 LBC           0 :                     PG_RE_THROW();
     241                 :                 }
     242 CBC          10 :                 PG_END_TRY();
     243                 : 
     244              10 :                 result = result_v;
     245                 :             }
     246 GBC          10 :             break;
     247 EUB             : 
     248 UIC           0 :         default:
     249               0 :             elog(ERROR, "unexpected jsonb token: %d", r);
     250                 :             return NULL;
     251 ECB             :     }
     252                 : 
     253 GIC          30 :     return result;
     254                 : }
     255                 : 
     256                 : /*
     257                 :  * PLyMapping_ToJsonbValue
     258                 :  *
     259                 :  * Transform Python dict to JsonbValue.
     260 ECB             :  */
     261                 : static JsonbValue *
     262 GIC           5 : PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
     263                 : {
     264                 :     Py_ssize_t  pcount;
     265                 :     PyObject   *volatile items;
     266 ECB             :     JsonbValue *volatile out;
     267                 : 
     268 GIC           5 :     pcount = PyMapping_Size(obj);
     269 CBC           5 :     items = PyMapping_Items(obj);
     270                 : 
     271 GIC           5 :     PG_TRY();
     272                 :     {
     273 ECB             :         Py_ssize_t  i;
     274                 : 
     275 CBC           5 :         pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
     276                 : 
     277 GIC          12 :         for (i = 0; i < pcount; i++)
     278 ECB             :         {
     279                 :             JsonbValue  jbvKey;
     280 CBC           7 :             PyObject   *item = PyList_GetItem(items, i);
     281 GIC           7 :             PyObject   *key = PyTuple_GetItem(item, 0);
     282               7 :             PyObject   *value = PyTuple_GetItem(item, 1);
     283 ECB             : 
     284                 :             /* Python dictionary can have None as key */
     285 CBC           7 :             if (key == Py_None)
     286 ECB             :             {
     287 CBC           1 :                 jbvKey.type = jbvString;
     288 GIC           1 :                 jbvKey.val.string.len = 0;
     289               1 :                 jbvKey.val.string.val = "";
     290                 :             }
     291                 :             else
     292 ECB             :             {
     293                 :                 /* All others types of keys we serialize to string */
     294 GIC           6 :                 PLyUnicode_ToJsonbValue(key, &jbvKey);
     295 ECB             :             }
     296                 : 
     297 GIC           7 :             (void) pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey);
     298               7 :             (void) PLyObject_ToJsonbValue(value, jsonb_state, false);
     299 ECB             :         }
     300                 : 
     301 GBC           5 :         out = pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
     302                 :     }
     303 LBC           0 :     PG_FINALLY();
     304                 :     {
     305 CBC           5 :         Py_DECREF(items);
     306                 :     }
     307               5 :     PG_END_TRY();
     308                 : 
     309 GIC           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 ECB             :  */
     318                 : static JsonbValue *
     319 GIC          10 : PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
     320                 : {
     321 ECB             :     Py_ssize_t  i;
     322                 :     Py_ssize_t  pcount;
     323 CBC          10 :     PyObject   *volatile value = NULL;
     324                 : 
     325              10 :     pcount = PySequence_Size(obj);
     326                 : 
     327              10 :     pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
     328                 : 
     329              10 :     PG_TRY();
     330                 :     {
     331              28 :         for (i = 0; i < pcount; i++)
     332 ECB             :         {
     333 GIC          18 :             value = PySequence_GetItem(obj, i);
     334 CBC          18 :             Assert(value);
     335 ECB             : 
     336 CBC          18 :             (void) PLyObject_ToJsonbValue(value, jsonb_state, true);
     337 GIC          18 :             Py_XDECREF(value);
     338              18 :             value = NULL;
     339 EUB             :         }
     340                 :     }
     341 UBC           0 :     PG_CATCH();
     342 EUB             :     {
     343 UIC           0 :         Py_XDECREF(value);
     344 LBC           0 :         PG_RE_THROW();
     345                 :     }
     346 CBC          10 :     PG_END_TRY();
     347                 : 
     348 GIC          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 ECB             :  */
     356                 : static JsonbValue *
     357 GIC          16 : PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
     358 ECB             : {
     359                 :     Numeric     num;
     360 CBC          16 :     char       *str = PLyObject_AsString(obj);
     361                 : 
     362 GIC          16 :     PG_TRY();
     363                 :     {
     364 ECB             :         Datum       numd;
     365                 : 
     366 GIC          16 :         numd = DirectFunctionCall3(numeric_in,
     367                 :                                    CStringGetDatum(str),
     368 ECB             :                                    ObjectIdGetDatum(InvalidOid),
     369                 :                                    Int32GetDatum(-1));
     370 CBC          15 :         num = DatumGetNumeric(numd);
     371                 :     }
     372               1 :     PG_CATCH();
     373                 :     {
     374 GIC           1 :         ereport(ERROR,
     375                 :                 (errcode(ERRCODE_DATATYPE_MISMATCH),
     376 ECB             :                  errmsg("could not convert value \"%s\" to jsonb", str)));
     377                 :     }
     378 CBC          15 :     PG_END_TRY();
     379                 : 
     380 GIC          15 :     pfree(str);
     381                 : 
     382                 :     /*
     383                 :      * jsonb doesn't allow NaN or infinity (per JSON specification), so we
     384 ECB             :      * have to reject those here explicitly.
     385 EUB             :      */
     386 GIC          15 :     if (numeric_is_nan(num))
     387 UIC           0 :         ereport(ERROR,
     388 ECB             :                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     389 EUB             :                  errmsg("cannot convert NaN to jsonb")));
     390 GIC          15 :     if (numeric_is_inf(num))
     391 UIC           0 :         ereport(ERROR,
     392                 :                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     393 ECB             :                  errmsg("cannot convert infinity to jsonb")));
     394                 : 
     395 GIC          15 :     jbvNum->type = jbvNumeric;
     396 CBC          15 :     jbvNum->val.numeric = num;
     397                 : 
     398 GIC          15 :     return jbvNum;
     399                 : }
     400                 : 
     401                 : /*
     402                 :  * PLyObject_ToJsonbValue(PyObject *obj)
     403                 :  *
     404                 :  * Transform python object to JsonbValue.
     405 ECB             :  */
     406                 : static JsonbValue *
     407 GIC          47 : PLyObject_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state, bool is_elem)
     408                 : {
     409 ECB             :     JsonbValue *out;
     410                 : 
     411 CBC          47 :     if (!PyUnicode_Check(obj))
     412 ECB             :     {
     413 CBC          40 :         if (PySequence_Check(obj))
     414              10 :             return PLySequence_ToJsonbValue(obj, jsonb_state);
     415 GIC          30 :         else if (PyMapping_Check(obj))
     416               5 :             return PLyMapping_ToJsonbValue(obj, jsonb_state);
     417 ECB             :     }
     418                 : 
     419 CBC          32 :     out = palloc(sizeof(JsonbValue));
     420 ECB             : 
     421 CBC          32 :     if (obj == Py_None)
     422               4 :         out->type = jbvNull;
     423 GIC          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 ECB             :      * come first.
     429                 :      */
     430 CBC          21 :     else if (PyBool_Check(obj))
     431 ECB             :     {
     432 GIC           5 :         out->type = jbvBool;
     433 CBC           5 :         out->val.boolean = (obj == Py_True);
     434 ECB             :     }
     435 GIC          16 :     else if (PyNumber_Check(obj))
     436 GBC          16 :         out = PLyNumber_ToJsonbValue(obj, out);
     437                 :     else
     438 UIC           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 ECB             : 
     443                 :     /* Push result into 'jsonb_state' unless it is raw scalar value. */
     444 GIC          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 ECB             :  * Transform python object to Jsonb datum
     453                 :  */
     454 CBC           2 : PG_FUNCTION_INFO_V1(plpython_to_jsonb);
     455                 : Datum
     456 GIC          22 : plpython_to_jsonb(PG_FUNCTION_ARGS)
     457                 : {
     458 ECB             :     PyObject   *obj;
     459                 :     JsonbValue *out;
     460 CBC          22 :     JsonbParseState *jsonb_state = NULL;
     461 ECB             : 
     462 CBC          22 :     obj = (PyObject *) PG_GETARG_POINTER(0);
     463 GIC          22 :     out = PLyObject_ToJsonbValue(obj, &jsonb_state, true);
     464              21 :     PG_RETURN_POINTER(JsonbValueToJsonb(out));
     465                 : }
     466                 : 
     467                 : /*
     468                 :  * jsonb_to_plpython
     469                 :  *
     470 ECB             :  * Transform Jsonb datum to PyObject and return it as internal.
     471                 :  */
     472 CBC           2 : PG_FUNCTION_INFO_V1(jsonb_to_plpython);
     473                 : Datum
     474 GIC          26 : jsonb_to_plpython(PG_FUNCTION_ARGS)
     475 ECB             : {
     476                 :     PyObject   *result;
     477 GIC          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 ECB             :      * module.
     483                 :      */
     484 CBC          26 :     if (!decimal_constructor)
     485                 :     {
     486               1 :         PyObject   *decimal_module = PyImport_ImportModule("cdecimal");
     487                 : 
     488               1 :         if (!decimal_module)
     489 ECB             :         {
     490 GIC           1 :             PyErr_Clear();
     491 CBC           1 :             decimal_module = PyImport_ImportModule("decimal");
     492 ECB             :         }
     493 GIC           1 :         Assert(decimal_module);
     494               1 :         decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal");
     495 ECB             :     }
     496                 : 
     497 GBC          26 :     result = PLyObject_FromJsonbContainer(&in->root);
     498 GIC          26 :     if (!result)
     499 LBC           0 :         PLy_elog(ERROR, "transformation from jsonb to Python failed");
     500                 : 
     501 GIC          26 :     return PointerGetDatum(result);
     502                 : }
        

Generated by: LCOV version v1.16-55-g56c0a2a