#include "postgres.h" #include #include /* Defined by Perl */ #undef _ #include "fmgr.h" #include "plperl.h" #include "plperl_helpers.h" #include "utils/jsonb.h" #include "utils/fmgrprotos.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 newRV(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 newRV(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 (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 (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); case SVt_NULL: out.type = jbvNull; break; default: if (SvIOK(in)) { IV ival = SvIV(in); out.type = jbvNumeric; out.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric, Int64GetDatum((int64) ival))); } else if (SvNOK(in)) { double nval = SvNV(in); if (isinf(nval)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errmsg("cannot convert infinite value 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(newRV(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); }