diff options
author | Peter Eisentraut <peter_e@gmx.net> | 2018-04-03 09:47:18 -0400 |
---|---|---|
committer | Peter Eisentraut <peter_e@gmx.net> | 2018-04-03 09:47:18 -0400 |
commit | 341e1661805879db958dde0a9ed1dc44b1bb10c3 (patch) | |
tree | f78b33ca09f89599112fc81637c4fccf32cf12d4 /contrib/jsonb_plperl/jsonb_plperl.c | |
parent | a08dc711952081d63577fc182fcf955958f70add (diff) | |
download | postgresql-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.c | 262 |
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); +} |