summaryrefslogtreecommitdiff
path: root/contrib/jsonb_plperl/jsonb_plperl.c
diff options
context:
space:
mode:
authorPeter Eisentraut <peter_e@gmx.net>2018-04-03 09:47:18 -0400
committerPeter Eisentraut <peter_e@gmx.net>2018-04-03 09:47:18 -0400
commit341e1661805879db958dde0a9ed1dc44b1bb10c3 (patch)
treef78b33ca09f89599112fc81637c4fccf32cf12d4 /contrib/jsonb_plperl/jsonb_plperl.c
parenta08dc711952081d63577fc182fcf955958f70add (diff)
downloadpostgresql-341e1661805879db958dde0a9ed1dc44b1bb10c3.tar.gz
Transforms for jsonb to PL/Perl
Add a new contrib module jsonb_plperl that provides a transform between jsonb and PL/Perl. jsonb values are converted to appropriate Perl types such as arrays and hashes, and vice versa. Author: Anthony Bykov <a.bykov@postgrespro.ru> Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com> Reviewed-by: Aleksander Alekseev <a.alekseev@postgrespro.ru> Reviewed-by: Nikita Glukhov <n.gluhov@postgrespro.ru>
Diffstat (limited to 'contrib/jsonb_plperl/jsonb_plperl.c')
-rw-r--r--contrib/jsonb_plperl/jsonb_plperl.c262
1 files changed, 262 insertions, 0 deletions
diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c
new file mode 100644
index 0000000000..918debdddb
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperl.c
@@ -0,0 +1,262 @@
+#include "postgres.h"
+
+#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;
+
+ key.type = jbvString;
+
+ pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
+
+ (void) hv_iterinit(obj);
+
+ while ((val = hv_iternextsv(obj, &key.val.string.val, &key.val.string.len)))
+ {
+ key.val.string.val = pnstrdup(key.val.string.val, key.val.string.len);
+ 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_NV:
+ case SVt_IV:
+ {
+ char *str = sv2cstr(in);
+
+ /*
+ * Use case-insensitive comparison because infinity
+ * representation varies across Perl versions.
+ */
+ if (pg_strcasecmp(str, "inf") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ (errmsg("cannot convert infinite value to jsonb"))));
+
+ out.type = jbvNumeric;
+ out.val.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in,
+ CStringGetDatum(str), 0, -1));
+ }
+ break;
+
+ case SVt_NULL:
+ out.type = jbvNull;
+ break;
+
+ case SVt_PV: /* string */
+ out.type = jbvString;
+ out.val.string.val = sv2cstr(in);
+ out.val.string.len = strlen(out.val.string.val);
+ break;
+
+ default:
+
+ /*
+ * 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);
+}