mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-31 13:57:21 +02:00
Perl likes to redefine the _() macro: #ifdef CAN_PROTOTYPE #define _(args) args #else ... There was lots not to like about the way we dealt with this before: 1. Instead of taking care of the conflict centrally in plperl.h, we expected every one of its ever-growing number of includers to do so. This is duplicative and error-prone in itself, plus it means that plperl.h fails to meet the expectation of being compilable standalone, resulting in macro-redefinition warnings in cpluspluscheck. 2. We left _() with its Perl definition, meaning that if someone tried to use it in any Perl-related extension, it would silently fail to provide run-time translation. I don't see any live bugs of this ilk, but it's clearly a hard-to-notice bug waiting to happen. So fix that by centralizing the cleanup logic, making it match what we're already doing for other macro conflicts with Perl. Since we only expect plperl.h to be included by extensions not core code, we should redefine _() as dgettext() not gettext().
301 lines
6.5 KiB
C
301 lines
6.5 KiB
C
#include "postgres.h"
|
|
|
|
#include <math.h>
|
|
|
|
#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 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);
|
|
|
|
case SVt_NULL:
|
|
out.type = jbvNull;
|
|
break;
|
|
|
|
default:
|
|
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);
|
|
}
|