LCOV - code coverage report
Current view: top level - contrib/jsonb_plperl - jsonb_plperl.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 115 122 94.3 %
Date: 2025-04-01 14:15:22 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 JsonbValue *SV_to_JsonbValue(SV *obj, JsonbParseState **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         168 :                 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         108 :                 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 JsonbValue *
     131          44 : AV_to_JsonbValue(AV *in, JsonbParseState **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 :             (void) SV_to_JsonbValue(*value, jsonb_state, true);
     145             :     }
     146             : 
     147          44 :     return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
     148             : }
     149             : 
     150             : static JsonbValue *
     151          56 : HV_to_JsonbValue(HV *obj, JsonbParseState **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 :         (void) SV_to_JsonbValue(val, jsonb_state, false);
     171             :     }
     172             : 
     173          56 :     return pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
     174             : }
     175             : 
     176             : static JsonbValue *
     177         294 : SV_to_JsonbValue(SV *in, JsonbParseState **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 :             return AV_to_JsonbValue((AV *) in, jsonb_state);
     190             : 
     191          56 :         case SVt_PVHV:
     192          56 :             return HV_to_JsonbValue((HV *) in, jsonb_state);
     193             : 
     194         194 :         default:
     195         194 :             if (!SvOK(in))
     196             :             {
     197          24 :                 out.type = jbvNull;
     198             :             }
     199         170 :             else if (SvUOK(in))
     200             :             {
     201             :                 /*
     202             :                  * If UV is >=64 bits, we have no better way to make this
     203             :                  * happen than converting to text and back.  Given the low
     204             :                  * usage of UV in Perl code, it's not clear it's worth working
     205             :                  * hard to provide alternate code paths.
     206             :                  */
     207           4 :                 const char *strval = SvPV_nolen(in);
     208             : 
     209           4 :                 out.type = jbvNumeric;
     210           4 :                 out.val.numeric =
     211           4 :                     DatumGetNumeric(DirectFunctionCall3(numeric_in,
     212             :                                                         CStringGetDatum(strval),
     213             :                                                         ObjectIdGetDatum(InvalidOid),
     214             :                                                         Int32GetDatum(-1)));
     215             :             }
     216         166 :             else if (SvIOK(in))
     217             :             {
     218          20 :                 IV          ival = SvIV(in);
     219             : 
     220          20 :                 out.type = jbvNumeric;
     221          20 :                 out.val.numeric = int64_to_numeric(ival);
     222             :             }
     223         146 :             else if (SvNOK(in))
     224             :             {
     225         106 :                 double      nval = SvNV(in);
     226             : 
     227             :                 /*
     228             :                  * jsonb doesn't allow infinity or NaN (per JSON
     229             :                  * specification), but the numeric type that is used for the
     230             :                  * storage accepts those, so we have to reject them here
     231             :                  * explicitly.
     232             :                  */
     233         106 :                 if (isinf(nval))
     234           2 :                     ereport(ERROR,
     235             :                             (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     236             :                              errmsg("cannot convert infinity to jsonb")));
     237         104 :                 if (isnan(nval))
     238           0 :                     ereport(ERROR,
     239             :                             (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     240             :                              errmsg("cannot convert NaN to jsonb")));
     241             : 
     242         104 :                 out.type = jbvNumeric;
     243         104 :                 out.val.numeric =
     244         104 :                     DatumGetNumeric(DirectFunctionCall1(float8_numeric,
     245             :                                                         Float8GetDatum(nval)));
     246             :             }
     247          40 :             else if (SvPOK(in))
     248             :             {
     249          40 :                 out.type = jbvString;
     250          40 :                 out.val.string.val = sv2cstr(in);
     251          40 :                 out.val.string.len = strlen(out.val.string.val);
     252             :             }
     253             :             else
     254             :             {
     255             :                 /*
     256             :                  * XXX It might be nice if we could include the Perl type in
     257             :                  * the error message.
     258             :                  */
     259           0 :                 ereport(ERROR,
     260             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     261             :                          errmsg("cannot transform this Perl type to jsonb")));
     262             :                 return NULL;
     263             :             }
     264             :     }
     265             : 
     266             :     /* Push result into 'jsonb_state' unless it is a raw scalar. */
     267         192 :     return *jsonb_state
     268         148 :         ? pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, &out)
     269         340 :         : memcpy(palloc(sizeof(JsonbValue)), &out, sizeof(JsonbValue));
     270             : }
     271             : 
     272             : 
     273           8 : PG_FUNCTION_INFO_V1(jsonb_to_plperl);
     274             : 
     275             : Datum
     276         102 : jsonb_to_plperl(PG_FUNCTION_ARGS)
     277             : {
     278         102 :     dTHX;
     279         102 :     Jsonb      *in = PG_GETARG_JSONB_P(0);
     280         102 :     SV         *sv = Jsonb_to_SV(&in->root);
     281             : 
     282         102 :     return PointerGetDatum(sv);
     283             : }
     284             : 
     285             : 
     286           8 : PG_FUNCTION_INFO_V1(plperl_to_jsonb);
     287             : 
     288             : Datum
     289         126 : plperl_to_jsonb(PG_FUNCTION_ARGS)
     290             : {
     291         126 :     dTHX;
     292         126 :     JsonbParseState *jsonb_state = NULL;
     293         126 :     SV         *in = (SV *) PG_GETARG_POINTER(0);
     294         126 :     JsonbValue *out = SV_to_JsonbValue(in, &jsonb_state, true);
     295         124 :     Jsonb      *result = JsonbValueToJsonb(out);
     296             : 
     297         124 :     PG_RETURN_JSONB_P(result);
     298             : }

Generated by: LCOV version 1.14