LCOV - code coverage report
Current view: top level - contrib/jsonb_plpython - jsonb_plpython.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 82.7 % 197 163
Test Date: 2026-02-28 14:14:49 Functions: 100.0 % 14 14
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.0-1