summaryrefslogtreecommitdiffstats
path: root/contrib/jsonb_plperl/jsonb_plperl.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/jsonb_plperl/jsonb_plperl.c')
-rw-r--r--contrib/jsonb_plperl/jsonb_plperl.c300
1 files changed, 300 insertions, 0 deletions
diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c
new file mode 100644
index 0000000..ed361ef
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperl.c
@@ -0,0 +1,300 @@
+#include "postgres.h"
+
+#include <math.h>
+
+#include "fmgr.h"
+#include "plperl.h"
+#include "plperl_helpers.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 =
+ DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+ Int64GetDatum((int64) 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 NaN, so we have to prevent it here
+ * explicitly. We don't really have to check for isinf()
+ * here, as numeric doesn't allow it and it would be caught
+ * later, but it makes for a nicer error message.
+ */
+ 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);
+}