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

Generated by: LCOV version 1.14