LCOV - code coverage report
Current view: top level - contrib/jsonb_plperl - jsonb_plperl.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 119 126 94.4 %
Date: 2025-12-27 21:18:42 Functions: 10 10 100.0 %
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           4 : 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         162 : JsonbValue_to_SV(JsonbValue *jbv)
      21             : {
      22         162 :     dTHX;
      23             : 
      24         162 :     switch (jbv->type)
      25             :     {
      26          12 :         case jbvBinary:
      27          12 :             return Jsonb_to_SV(jbv->val.binary.data);
      28             : 
      29          98 :         case jbvNumeric:
      30             :             {
      31          98 :                 char       *str = DatumGetCString(DirectFunctionCall1(numeric_out,
      32             :                                                                       NumericGetDatum(jbv->val.numeric)));
      33          98 :                 SV         *result = newSVnv(SvNV(cstr2sv(str)));
      34             : 
      35          98 :                 pfree(str);
      36          98 :                 return result;
      37             :             }
      38             : 
      39          28 :         case jbvString:
      40             :             {
      41          28 :                 char       *str = pnstrdup(jbv->val.string.val,
      42          28 :                                            jbv->val.string.len);
      43          28 :                 SV         *result = cstr2sv(str);
      44             : 
      45          28 :                 pfree(str);
      46          28 :                 return result;
      47             :             }
      48             : 
      49           8 :         case jbvBool:
      50           8 :             return newSVnv(SvNV(jbv->val.boolean ? &PL_sv_yes : &PL_sv_no));
      51             : 
      52          16 :         case jbvNull:
      53          16 :             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         114 : Jsonb_to_SV(JsonbContainer *jsonb)
      63             : {
      64         114 :     dTHX;
      65             :     JsonbValue  v;
      66             :     JsonbIterator *it;
      67             :     JsonbIteratorToken r;
      68             : 
      69         114 :     it = JsonbIteratorInit(jsonb);
      70         114 :     r = JsonbIteratorNext(&it, &v, true);
      71             : 
      72         114 :     switch (r)
      73             :     {
      74          78 :         case WJB_BEGIN_ARRAY:
      75          78 :             if (v.val.array.rawScalar)
      76             :             {
      77             :                 JsonbValue  tmp;
      78             : 
      79          76 :                 if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
      80          76 :                     (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
      81          38 :                     (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
      82           0 :                     elog(ERROR, "unexpected jsonb token: %d", r);
      83             : 
      84          38 :                 return JsonbValue_to_SV(&v);
      85             :             }
      86             :             else
      87             :             {
      88          40 :                 AV         *av = newAV();
      89             : 
      90         208 :                 while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
      91             :                 {
      92         128 :                     if (r == WJB_ELEM)
      93          88 :                         av_push(av, JsonbValue_to_SV(&v));
      94             :                 }
      95             : 
      96          40 :                 return newRV((SV *) av);
      97             :             }
      98             : 
      99          36 :         case WJB_BEGIN_OBJECT:
     100             :             {
     101          36 :                 HV         *hv = newHV();
     102             : 
     103         144 :                 while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
     104             :                 {
     105          72 :                     if (r == WJB_KEY)
     106             :                     {
     107             :                         /* json key in v, json value in val */
     108             :                         JsonbValue  val;
     109             : 
     110          36 :                         if (JsonbIteratorNext(&it, &val, true) == WJB_VALUE)
     111             :                         {
     112          36 :                             SV         *value = JsonbValue_to_SV(&val);
     113             : 
     114          36 :                             (void) hv_store(hv,
     115             :                                             v.val.string.val, v.val.string.len,
     116             :                                             value, 0);
     117             :                         }
     118             :                     }
     119             :                 }
     120             : 
     121          36 :                 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          44 : AV_to_JsonbValue(AV *in, JsonbInState *jsonb_state)
     132             : {
     133          44 :     dTHX;
     134          44 :     SSize_t     pcount = av_len(in) + 1;
     135             :     SSize_t     i;
     136             : 
     137          44 :     pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
     138             : 
     139         140 :     for (i = 0; i < pcount; i++)
     140             :     {
     141          96 :         SV        **value = av_fetch(in, i, FALSE);
     142             : 
     143          96 :         if (value)
     144          96 :             SV_to_JsonbValue(*value, jsonb_state, true);
     145             :     }
     146             : 
     147          44 :     pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
     148          44 : }
     149             : 
     150             : static void
     151          56 : HV_to_JsonbValue(HV *obj, JsonbInState *jsonb_state)
     152             : {
     153          56 :     dTHX;
     154             :     JsonbValue  key;
     155             :     SV         *val;
     156             :     char       *kstr;
     157             :     I32         klen;
     158             : 
     159          56 :     key.type = jbvString;
     160             : 
     161          56 :     pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
     162             : 
     163          56 :     (void) hv_iterinit(obj);
     164             : 
     165         128 :     while ((val = hv_iternextsv(obj, &kstr, &klen)))
     166             :     {
     167          72 :         key.val.string.val = pnstrdup(kstr, klen);
     168          72 :         key.val.string.len = klen;
     169          72 :         pushJsonbValue(jsonb_state, WJB_KEY, &key);
     170          72 :         SV_to_JsonbValue(val, jsonb_state, false);
     171             :     }
     172             : 
     173          56 :     pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
     174          56 : }
     175             : 
     176             : static void
     177         294 : SV_to_JsonbValue(SV *in, JsonbInState *jsonb_state, bool is_elem)
     178             : {
     179         294 :     dTHX;
     180             :     JsonbValue  out;            /* result */
     181             : 
     182             :     /* Dereference references recursively. */
     183         394 :     while (SvROK(in))
     184         100 :         in = SvRV(in);
     185             : 
     186         294 :     switch (SvTYPE(in))
     187             :     {
     188          44 :         case SVt_PVAV:
     189          44 :             AV_to_JsonbValue((AV *) in, jsonb_state);
     190         100 :             return;
     191             : 
     192          56 :         case SVt_PVHV:
     193          56 :             HV_to_JsonbValue((HV *) in, jsonb_state);
     194          56 :             return;
     195             : 
     196         194 :         default:
     197         194 :             if (!SvOK(in))
     198             :             {
     199          24 :                 out.type = jbvNull;
     200             :             }
     201         170 :             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           4 :                 const char *strval = SvPV_nolen(in);
     210             : 
     211           4 :                 out.type = jbvNumeric;
     212           4 :                 out.val.numeric =
     213           4 :                     DatumGetNumeric(DirectFunctionCall3(numeric_in,
     214             :                                                         CStringGetDatum(strval),
     215             :                                                         ObjectIdGetDatum(InvalidOid),
     216             :                                                         Int32GetDatum(-1)));
     217             :             }
     218         166 :             else if (SvIOK(in))
     219             :             {
     220          20 :                 IV          ival = SvIV(in);
     221             : 
     222          20 :                 out.type = jbvNumeric;
     223          20 :                 out.val.numeric = int64_to_numeric(ival);
     224             :             }
     225         146 :             else if (SvNOK(in))
     226             :             {
     227         106 :                 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         106 :                 if (isinf(nval))
     236           2 :                     ereport(ERROR,
     237             :                             (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     238             :                              errmsg("cannot convert infinity to jsonb")));
     239         104 :                 if (isnan(nval))
     240           0 :                     ereport(ERROR,
     241             :                             (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     242             :                              errmsg("cannot convert NaN to jsonb")));
     243             : 
     244         104 :                 out.type = jbvNumeric;
     245         104 :                 out.val.numeric =
     246         104 :                     DatumGetNumeric(DirectFunctionCall1(float8_numeric,
     247             :                                                         Float8GetDatum(nval)));
     248             :             }
     249          40 :             else if (SvPOK(in))
     250             :             {
     251          40 :                 out.type = jbvString;
     252          40 :                 out.val.string.val = sv2cstr(in);
     253          40 :                 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         192 :     if (jsonb_state->parseState)
     268             :     {
     269             :         /* We're in an array or object, so push value as element or field. */
     270         148 :         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          44 :         jsonb_state->result = palloc_object(JsonbValue);
     280          44 :         memcpy(jsonb_state->result, &out, sizeof(JsonbValue));
     281             :     }
     282             : }
     283             : 
     284             : 
     285           8 : PG_FUNCTION_INFO_V1(jsonb_to_plperl);
     286             : 
     287             : Datum
     288         102 : jsonb_to_plperl(PG_FUNCTION_ARGS)
     289             : {
     290         102 :     dTHX;
     291         102 :     Jsonb      *in = PG_GETARG_JSONB_P(0);
     292         102 :     SV         *sv = Jsonb_to_SV(&in->root);
     293             : 
     294         102 :     return PointerGetDatum(sv);
     295             : }
     296             : 
     297             : 
     298           8 : PG_FUNCTION_INFO_V1(plperl_to_jsonb);
     299             : 
     300             : Datum
     301         126 : plperl_to_jsonb(PG_FUNCTION_ARGS)
     302             : {
     303         126 :     dTHX;
     304         126 :     SV         *in = (SV *) PG_GETARG_POINTER(0);
     305         126 :     JsonbInState jsonb_state = {0};
     306             : 
     307         126 :     SV_to_JsonbValue(in, &jsonb_state, true);
     308         124 :     PG_RETURN_JSONB_P(JsonbValueToJsonb(jsonb_state.result));
     309             : }

Generated by: LCOV version 1.16