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

Generated by: LCOV version 1.14