LCOV - code coverage report
Current view: top level - contrib/jsonb_plperl - jsonb_plperl.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 94.4 % 126 119
Test Date: 2026-03-04 12:15:03 Functions: 100.0 % 10 10
Legend: Lines:     hit not hit

            Line data    Source code
       1              : #include "postgres.h"
       2              : 
       3              : #include <math.h>
       4              : 
       5              : #include "fmgr.h"
       6              : #include "plperl.h"
       7              : #include "utils/fmgrprotos.h"
       8              : #include "utils/jsonb.h"
       9              : 
      10            2 : PG_MODULE_MAGIC_EXT(
      11              :                     .name = "jsonb_plperl",
      12              :                     .version = PG_VERSION
      13              : );
      14              : 
      15              : static SV  *Jsonb_to_SV(JsonbContainer *jsonb);
      16              : static void SV_to_JsonbValue(SV *obj, JsonbInState *ps, bool is_elem);
      17              : 
      18              : 
      19              : static SV  *
      20           81 : JsonbValue_to_SV(JsonbValue *jbv)
      21              : {
      22           81 :     dTHX;
      23              : 
      24           81 :     switch (jbv->type)
      25              :     {
      26            6 :         case jbvBinary:
      27            6 :             return Jsonb_to_SV(jbv->val.binary.data);
      28              : 
      29           49 :         case jbvNumeric:
      30              :             {
      31           49 :                 char       *str = DatumGetCString(DirectFunctionCall1(numeric_out,
      32              :                                                                       NumericGetDatum(jbv->val.numeric)));
      33           49 :                 SV         *result = newSVnv(SvNV(cstr2sv(str)));
      34              : 
      35           49 :                 pfree(str);
      36           49 :                 return result;
      37              :             }
      38              : 
      39           14 :         case jbvString:
      40              :             {
      41           14 :                 char       *str = pnstrdup(jbv->val.string.val,
      42           14 :                                            jbv->val.string.len);
      43           14 :                 SV         *result = cstr2sv(str);
      44              : 
      45           14 :                 pfree(str);
      46           14 :                 return result;
      47              :             }
      48              : 
      49            4 :         case jbvBool:
      50            4 :             return newSVnv(SvNV(jbv->val.boolean ? &PL_sv_yes : &PL_sv_no));
      51              : 
      52            8 :         case jbvNull:
      53            8 :             return newSV(0);
      54              : 
      55            0 :         default:
      56            0 :             elog(ERROR, "unexpected jsonb value type: %d", jbv->type);
      57              :             return NULL;
      58              :     }
      59              : }
      60              : 
      61              : static SV  *
      62           57 : Jsonb_to_SV(JsonbContainer *jsonb)
      63              : {
      64           57 :     dTHX;
      65              :     JsonbValue  v;
      66              :     JsonbIterator *it;
      67              :     JsonbIteratorToken r;
      68              : 
      69           57 :     it = JsonbIteratorInit(jsonb);
      70           57 :     r = JsonbIteratorNext(&it, &v, true);
      71              : 
      72           57 :     switch (r)
      73              :     {
      74           39 :         case WJB_BEGIN_ARRAY:
      75           39 :             if (v.val.array.rawScalar)
      76              :             {
      77              :                 JsonbValue  tmp;
      78              : 
      79           38 :                 if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
      80           38 :                     (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
      81           19 :                     (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
      82            0 :                     elog(ERROR, "unexpected jsonb token: %d", r);
      83              : 
      84           19 :                 return JsonbValue_to_SV(&v);
      85              :             }
      86              :             else
      87              :             {
      88           20 :                 AV         *av = newAV();
      89              : 
      90          104 :                 while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
      91              :                 {
      92           64 :                     if (r == WJB_ELEM)
      93           44 :                         av_push(av, JsonbValue_to_SV(&v));
      94              :                 }
      95              : 
      96           20 :                 return newRV((SV *) av);
      97              :             }
      98              : 
      99           18 :         case WJB_BEGIN_OBJECT:
     100              :             {
     101           18 :                 HV         *hv = newHV();
     102              : 
     103           72 :                 while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
     104              :                 {
     105           36 :                     if (r == WJB_KEY)
     106              :                     {
     107              :                         /* json key in v, json value in val */
     108              :                         JsonbValue  val;
     109              : 
     110           18 :                         if (JsonbIteratorNext(&it, &val, true) == WJB_VALUE)
     111              :                         {
     112           18 :                             SV         *value = JsonbValue_to_SV(&val);
     113              : 
     114           18 :                             (void) hv_store(hv,
     115              :                                             v.val.string.val, v.val.string.len,
     116              :                                             value, 0);
     117              :                         }
     118              :                     }
     119              :                 }
     120              : 
     121           18 :                 return newRV((SV *) hv);
     122              :             }
     123              : 
     124            0 :         default:
     125            0 :             elog(ERROR, "unexpected jsonb token: %d", r);
     126              :             return NULL;
     127              :     }
     128              : }
     129              : 
     130              : static void
     131           22 : AV_to_JsonbValue(AV *in, JsonbInState *jsonb_state)
     132              : {
     133           22 :     dTHX;
     134           22 :     SSize_t     pcount = av_len(in) + 1;
     135              :     SSize_t     i;
     136              : 
     137           22 :     pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
     138              : 
     139           70 :     for (i = 0; i < pcount; i++)
     140              :     {
     141           48 :         SV        **value = av_fetch(in, i, FALSE);
     142              : 
     143           48 :         if (value)
     144           48 :             SV_to_JsonbValue(*value, jsonb_state, true);
     145              :     }
     146              : 
     147           22 :     pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
     148           22 : }
     149              : 
     150              : static void
     151           28 : HV_to_JsonbValue(HV *obj, JsonbInState *jsonb_state)
     152              : {
     153           28 :     dTHX;
     154              :     JsonbValue  key;
     155              :     SV         *val;
     156              :     char       *kstr;
     157              :     I32         klen;
     158              : 
     159           28 :     key.type = jbvString;
     160              : 
     161           28 :     pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
     162              : 
     163           28 :     (void) hv_iterinit(obj);
     164              : 
     165           64 :     while ((val = hv_iternextsv(obj, &kstr, &klen)))
     166              :     {
     167           36 :         key.val.string.val = pnstrdup(kstr, klen);
     168           36 :         key.val.string.len = klen;
     169           36 :         pushJsonbValue(jsonb_state, WJB_KEY, &key);
     170           36 :         SV_to_JsonbValue(val, jsonb_state, false);
     171              :     }
     172              : 
     173           28 :     pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
     174           28 : }
     175              : 
     176              : static void
     177          147 : SV_to_JsonbValue(SV *in, JsonbInState *jsonb_state, bool is_elem)
     178              : {
     179          147 :     dTHX;
     180              :     JsonbValue  out;            /* result */
     181              : 
     182              :     /* Dereference references recursively. */
     183          197 :     while (SvROK(in))
     184           50 :         in = SvRV(in);
     185              : 
     186          147 :     switch (SvTYPE(in))
     187              :     {
     188           22 :         case SVt_PVAV:
     189           22 :             AV_to_JsonbValue((AV *) in, jsonb_state);
     190           50 :             return;
     191              : 
     192           28 :         case SVt_PVHV:
     193           28 :             HV_to_JsonbValue((HV *) in, jsonb_state);
     194           28 :             return;
     195              : 
     196           97 :         default:
     197           97 :             if (!SvOK(in))
     198              :             {
     199           12 :                 out.type = jbvNull;
     200              :             }
     201           85 :             else if (SvUOK(in))
     202              :             {
     203              :                 /*
     204              :                  * If UV is >=64 bits, we have no better way to make this
     205              :                  * happen than converting to text and back.  Given the low
     206              :                  * usage of UV in Perl code, it's not clear it's worth working
     207              :                  * hard to provide alternate code paths.
     208              :                  */
     209            2 :                 const char *strval = SvPV_nolen(in);
     210              : 
     211            2 :                 out.type = jbvNumeric;
     212            2 :                 out.val.numeric =
     213            2 :                     DatumGetNumeric(DirectFunctionCall3(numeric_in,
     214              :                                                         CStringGetDatum(strval),
     215              :                                                         ObjectIdGetDatum(InvalidOid),
     216              :                                                         Int32GetDatum(-1)));
     217              :             }
     218           83 :             else if (SvIOK(in))
     219              :             {
     220           10 :                 IV          ival = SvIV(in);
     221              : 
     222           10 :                 out.type = jbvNumeric;
     223           10 :                 out.val.numeric = int64_to_numeric(ival);
     224              :             }
     225           73 :             else if (SvNOK(in))
     226              :             {
     227           53 :                 double      nval = SvNV(in);
     228              : 
     229              :                 /*
     230              :                  * jsonb doesn't allow infinity or NaN (per JSON
     231              :                  * specification), but the numeric type that is used for the
     232              :                  * storage accepts those, so we have to reject them here
     233              :                  * explicitly.
     234              :                  */
     235           53 :                 if (isinf(nval))
     236            1 :                     ereport(ERROR,
     237              :                             (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     238              :                              errmsg("cannot convert infinity to jsonb")));
     239           52 :                 if (isnan(nval))
     240            0 :                     ereport(ERROR,
     241              :                             (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     242              :                              errmsg("cannot convert NaN to jsonb")));
     243              : 
     244           52 :                 out.type = jbvNumeric;
     245           52 :                 out.val.numeric =
     246           52 :                     DatumGetNumeric(DirectFunctionCall1(float8_numeric,
     247              :                                                         Float8GetDatum(nval)));
     248              :             }
     249           20 :             else if (SvPOK(in))
     250              :             {
     251           20 :                 out.type = jbvString;
     252           20 :                 out.val.string.val = sv2cstr(in);
     253           20 :                 out.val.string.len = strlen(out.val.string.val);
     254              :             }
     255              :             else
     256              :             {
     257              :                 /*
     258              :                  * XXX It might be nice if we could include the Perl type in
     259              :                  * the error message.
     260              :                  */
     261            0 :                 ereport(ERROR,
     262              :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     263              :                          errmsg("cannot transform this Perl type to jsonb")));
     264              :             }
     265              :     }
     266              : 
     267           96 :     if (jsonb_state->parseState)
     268              :     {
     269              :         /* We're in an array or object, so push value as element or field. */
     270           74 :         pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, &out);
     271              :     }
     272              :     else
     273              :     {
     274              :         /*
     275              :          * We are at top level, so it's a raw scalar.  If we just shove the
     276              :          * scalar value into jsonb_state->result, JsonbValueToJsonb will take
     277              :          * care of wrapping it into a dummy array.
     278              :          */
     279           22 :         jsonb_state->result = palloc_object(JsonbValue);
     280           22 :         memcpy(jsonb_state->result, &out, sizeof(JsonbValue));
     281              :     }
     282              : }
     283              : 
     284              : 
     285            4 : PG_FUNCTION_INFO_V1(jsonb_to_plperl);
     286              : 
     287              : Datum
     288           51 : jsonb_to_plperl(PG_FUNCTION_ARGS)
     289              : {
     290           51 :     dTHX;
     291           51 :     Jsonb      *in = PG_GETARG_JSONB_P(0);
     292           51 :     SV         *sv = Jsonb_to_SV(&in->root);
     293              : 
     294           51 :     return PointerGetDatum(sv);
     295              : }
     296              : 
     297              : 
     298            4 : PG_FUNCTION_INFO_V1(plperl_to_jsonb);
     299              : 
     300              : Datum
     301           63 : plperl_to_jsonb(PG_FUNCTION_ARGS)
     302              : {
     303           63 :     dTHX;
     304           63 :     SV         *in = (SV *) PG_GETARG_POINTER(0);
     305           63 :     JsonbInState jsonb_state = {0};
     306              : 
     307           63 :     SV_to_JsonbValue(in, &jsonb_state, true);
     308           62 :     PG_RETURN_JSONB_P(JsonbValueToJsonb(jsonb_state.result));
     309              : }
        

Generated by: LCOV version 2.0-1