| #include "postgres.h" |
| |
| #include <math.h> |
| |
| #include "fmgr.h" |
| #include "plperl.h" |
| #include "utils/fmgrprotos.h" |
| #include "utils/jsonb.h" |
| |
| PG_MODULE_MAGIC; |
| |
| static SV *Jsonb_to_SV(JsonbContainer *jsonb); |
| static JsonbValue *SV_to_JsonbValue(SV *obj, JsonbParseState **ps, bool is_elem); |
| |
| |
| static SV * |
| JsonbValue_to_SV(JsonbValue *jbv) |
| { |
| dTHX; |
| |
| switch (jbv->type) |
| { |
| case jbvBinary: |
| return Jsonb_to_SV(jbv->val.binary.data); |
| |
| case jbvNumeric: |
| { |
| char *str = DatumGetCString(DirectFunctionCall1(numeric_out, |
| NumericGetDatum(jbv->val.numeric))); |
| SV *result = newSVnv(SvNV(cstr2sv(str))); |
| |
| pfree(str); |
| return result; |
| } |
| |
| case jbvString: |
| { |
| char *str = pnstrdup(jbv->val.string.val, |
| jbv->val.string.len); |
| SV *result = cstr2sv(str); |
| |
| pfree(str); |
| return result; |
| } |
| |
| case jbvBool: |
| return newSVnv(SvNV(jbv->val.boolean ? &PL_sv_yes : &PL_sv_no)); |
| |
| case jbvNull: |
| return newSV(0); |
| |
| default: |
| elog(ERROR, "unexpected jsonb value type: %d", jbv->type); |
| return NULL; |
| } |
| } |
| |
| static SV * |
| Jsonb_to_SV(JsonbContainer *jsonb) |
| { |
| dTHX; |
| JsonbValue v; |
| JsonbIterator *it; |
| JsonbIteratorToken r; |
| |
| it = JsonbIteratorInit(jsonb); |
| r = JsonbIteratorNext(&it, &v, true); |
| |
| switch (r) |
| { |
| case WJB_BEGIN_ARRAY: |
| if (v.val.array.rawScalar) |
| { |
| JsonbValue tmp; |
| |
| if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM || |
| (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY || |
| (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE) |
| elog(ERROR, "unexpected jsonb token: %d", r); |
| |
| return JsonbValue_to_SV(&v); |
| } |
| else |
| { |
| AV *av = newAV(); |
| |
| while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) |
| { |
| if (r == WJB_ELEM) |
| av_push(av, JsonbValue_to_SV(&v)); |
| } |
| |
| return newRV((SV *) av); |
| } |
| |
| case WJB_BEGIN_OBJECT: |
| { |
| HV *hv = newHV(); |
| |
| while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) |
| { |
| if (r == WJB_KEY) |
| { |
| /* json key in v, json value in val */ |
| JsonbValue val; |
| |
| if (JsonbIteratorNext(&it, &val, true) == WJB_VALUE) |
| { |
| SV *value = JsonbValue_to_SV(&val); |
| |
| (void) hv_store(hv, |
| v.val.string.val, v.val.string.len, |
| value, 0); |
| } |
| } |
| } |
| |
| return newRV((SV *) hv); |
| } |
| |
| default: |
| elog(ERROR, "unexpected jsonb token: %d", r); |
| return NULL; |
| } |
| } |
| |
| static JsonbValue * |
| AV_to_JsonbValue(AV *in, JsonbParseState **jsonb_state) |
| { |
| dTHX; |
| SSize_t pcount = av_len(in) + 1; |
| SSize_t i; |
| |
| pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL); |
| |
| for (i = 0; i < pcount; i++) |
| { |
| SV **value = av_fetch(in, i, FALSE); |
| |
| if (value) |
| (void) SV_to_JsonbValue(*value, jsonb_state, true); |
| } |
| |
| return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL); |
| } |
| |
| static JsonbValue * |
| HV_to_JsonbValue(HV *obj, JsonbParseState **jsonb_state) |
| { |
| dTHX; |
| JsonbValue key; |
| SV *val; |
| char *kstr; |
| I32 klen; |
| |
| key.type = jbvString; |
| |
| pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL); |
| |
| (void) hv_iterinit(obj); |
| |
| while ((val = hv_iternextsv(obj, &kstr, &klen))) |
| { |
| key.val.string.val = pnstrdup(kstr, klen); |
| key.val.string.len = klen; |
| pushJsonbValue(jsonb_state, WJB_KEY, &key); |
| (void) SV_to_JsonbValue(val, jsonb_state, false); |
| } |
| |
| return pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL); |
| } |
| |
| static JsonbValue * |
| SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem) |
| { |
| dTHX; |
| JsonbValue out; /* result */ |
| |
| /* Dereference references recursively. */ |
| while (SvROK(in)) |
| in = SvRV(in); |
| |
| switch (SvTYPE(in)) |
| { |
| case SVt_PVAV: |
| return AV_to_JsonbValue((AV *) in, jsonb_state); |
| |
| case SVt_PVHV: |
| return HV_to_JsonbValue((HV *) in, jsonb_state); |
| |
| default: |
| if (!SvOK(in)) |
| { |
| out.type = jbvNull; |
| } |
| else if (SvUOK(in)) |
| { |
| /* |
| * If UV is >=64 bits, we have no better way to make this |
| * happen than converting to text and back. Given the low |
| * usage of UV in Perl code, it's not clear it's worth working |
| * hard to provide alternate code paths. |
| */ |
| const char *strval = SvPV_nolen(in); |
| |
| out.type = jbvNumeric; |
| out.val.numeric = |
| DatumGetNumeric(DirectFunctionCall3(numeric_in, |
| CStringGetDatum(strval), |
| ObjectIdGetDatum(InvalidOid), |
| Int32GetDatum(-1))); |
| } |
| else if (SvIOK(in)) |
| { |
| IV ival = SvIV(in); |
| |
| out.type = jbvNumeric; |
| out.val.numeric = int64_to_numeric(ival); |
| } |
| else if (SvNOK(in)) |
| { |
| double nval = SvNV(in); |
| |
| /* |
| * jsonb doesn't allow infinity or NaN (per JSON |
| * specification), but the numeric type that is used for the |
| * storage accepts those, so we have to reject them here |
| * explicitly. |
| */ |
| if (isinf(nval)) |
| ereport(ERROR, |
| (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), |
| errmsg("cannot convert infinity to jsonb"))); |
| if (isnan(nval)) |
| ereport(ERROR, |
| (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), |
| errmsg("cannot convert NaN to jsonb"))); |
| |
| out.type = jbvNumeric; |
| out.val.numeric = |
| DatumGetNumeric(DirectFunctionCall1(float8_numeric, |
| Float8GetDatum(nval))); |
| } |
| else if (SvPOK(in)) |
| { |
| out.type = jbvString; |
| out.val.string.val = sv2cstr(in); |
| out.val.string.len = strlen(out.val.string.val); |
| } |
| else |
| { |
| /* |
| * XXX It might be nice if we could include the Perl type in |
| * the error message. |
| */ |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("cannot transform this Perl type to jsonb"))); |
| return NULL; |
| } |
| } |
| |
| /* Push result into 'jsonb_state' unless it is a raw scalar. */ |
| return *jsonb_state |
| ? pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, &out) |
| : memcpy(palloc(sizeof(JsonbValue)), &out, sizeof(JsonbValue)); |
| } |
| |
| |
| PG_FUNCTION_INFO_V1(jsonb_to_plperl); |
| |
| Datum |
| jsonb_to_plperl(PG_FUNCTION_ARGS) |
| { |
| dTHX; |
| Jsonb *in = PG_GETARG_JSONB_P(0); |
| SV *sv = Jsonb_to_SV(&in->root); |
| |
| return PointerGetDatum(sv); |
| } |
| |
| |
| PG_FUNCTION_INFO_V1(plperl_to_jsonb); |
| |
| Datum |
| plperl_to_jsonb(PG_FUNCTION_ARGS) |
| { |
| dTHX; |
| JsonbParseState *jsonb_state = NULL; |
| SV *in = (SV *) PG_GETARG_POINTER(0); |
| JsonbValue *out = SV_to_JsonbValue(in, &jsonb_state, true); |
| Jsonb *result = JsonbValueToJsonb(out); |
| |
| PG_RETURN_JSONB_P(result); |
| } |