mirror of
https://github.com/postgres/postgres.git
synced 2025-07-27 12:41:57 +03:00
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>
This commit is contained in:
262
contrib/jsonb_plperl/jsonb_plperl.c
Normal file
262
contrib/jsonb_plperl/jsonb_plperl.c
Normal file
@ -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);
|
||||
}
|
Reference in New Issue
Block a user