mirror of https://github.com/postgres/postgres
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>pull/27/merge
parent
a08dc71195
commit
341e166180
@ -0,0 +1,4 @@ |
||||
# Generated subdirectories |
||||
/log/ |
||||
/results/ |
||||
/tmp_check/ |
||||
@ -0,0 +1,40 @@ |
||||
# contrib/jsonb_plperl/Makefile
|
||||
|
||||
MODULE_big = jsonb_plperl
|
||||
OBJS = jsonb_plperl.o $(WIN32RES)
|
||||
PGFILEDESC = "jsonb_plperl - jsonb transform for plperl"
|
||||
|
||||
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl
|
||||
|
||||
EXTENSION = jsonb_plperlu jsonb_plperl
|
||||
DATA = jsonb_plperlu--1.0.sql jsonb_plperl--1.0.sql
|
||||
|
||||
REGRESS = jsonb_plperl jsonb_plperlu
|
||||
|
||||
ifdef USE_PGXS |
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS) |
||||
else |
||||
subdir = contrib/jsonb_plperl
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
include $(top_srcdir)/contrib/contrib-global.mk |
||||
endif |
||||
|
||||
# We must link libperl explicitly
|
||||
ifeq ($(PORTNAME), win32) |
||||
# these settings are the same as for plperl
|
||||
override CPPFLAGS += -DPLPERL_HAVE_UID_GID -Wno-comment
|
||||
# ... see silliness in plperl Makefile ...
|
||||
SHLIB_LINK += $(sort $(wildcard ../../src/pl/plperl/libperl*.a))
|
||||
else |
||||
rpathdir = $(perl_archlibexp)/CORE
|
||||
SHLIB_LINK += $(perl_embed_ldflags)
|
||||
endif |
||||
|
||||
# As with plperl we need to make sure that the CORE directory is included
|
||||
# last, probably because it sometimes contains some header files with names
|
||||
# that clash with some of ours, or with some that we include, notably on
|
||||
# Windows.
|
||||
override CPPFLAGS := $(CPPFLAGS) $(perl_embed_ccflags) -I$(perl_archlibexp)/CORE |
||||
@ -0,0 +1,211 @@ |
||||
CREATE EXTENSION jsonb_plperl CASCADE; |
||||
NOTICE: installing required extension "plperl" |
||||
CREATE FUNCTION testHVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = {a => 1, b => 'boo', c => undef}; |
||||
return $val; |
||||
$$; |
||||
SELECT testHVToJsonb(); |
||||
testhvtojsonb |
||||
--------------------------------- |
||||
{"a": 1, "b": "boo", "c": null} |
||||
(1 row) |
||||
|
||||
CREATE FUNCTION testAVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; |
||||
return $val; |
||||
$$; |
||||
SELECT testAVToJsonb(); |
||||
testavtojsonb |
||||
--------------------------------------------- |
||||
[{"a": 1, "b": "boo", "c": null}, {"d": 2}] |
||||
(1 row) |
||||
|
||||
CREATE FUNCTION testSVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = 1; |
||||
return $val; |
||||
$$; |
||||
SELECT testSVToJsonb(); |
||||
testsvtojsonb |
||||
--------------- |
||||
1 |
||||
(1 row) |
||||
|
||||
CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
return ('1' =~ m(0\t2)); |
||||
$$; |
||||
SELECT testRegexpToJsonb(); |
||||
ERROR: cannot transform this Perl type to jsonb |
||||
CONTEXT: PL/Perl function "testregexptojsonb" |
||||
CREATE FUNCTION roundtrip(val jsonb) RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
return $_[0]; |
||||
$$; |
||||
SELECT roundtrip('null'); |
||||
roundtrip |
||||
----------- |
||||
null |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('1'); |
||||
roundtrip |
||||
----------- |
||||
1 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('1E+131071'); |
||||
ERROR: cannot convert infinite value to jsonb |
||||
CONTEXT: PL/Perl function "roundtrip" |
||||
SELECT roundtrip('-1'); |
||||
roundtrip |
||||
----------- |
||||
-1 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('1.2'); |
||||
roundtrip |
||||
----------- |
||||
1.2 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('-1.2'); |
||||
roundtrip |
||||
----------- |
||||
-1.2 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('"string"'); |
||||
roundtrip |
||||
----------- |
||||
"string" |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('"NaN"'); |
||||
roundtrip |
||||
----------- |
||||
"NaN" |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('true'); |
||||
roundtrip |
||||
----------- |
||||
1 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('false'); |
||||
roundtrip |
||||
----------- |
||||
0 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[]'); |
||||
roundtrip |
||||
----------- |
||||
[] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[null, null]'); |
||||
roundtrip |
||||
-------------- |
||||
[null, null] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[1, 2, 3]'); |
||||
roundtrip |
||||
----------- |
||||
[1, 2, 3] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[-1, 2, -3]'); |
||||
roundtrip |
||||
------------- |
||||
[-1, 2, -3] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[1.2, 2.3, 3.4]'); |
||||
roundtrip |
||||
----------------- |
||||
[1.2, 2.3, 3.4] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[-1.2, 2.3, -3.4]'); |
||||
roundtrip |
||||
------------------- |
||||
[-1.2, 2.3, -3.4] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('["string1", "string2"]'); |
||||
roundtrip |
||||
------------------------ |
||||
["string1", "string2"] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{}'); |
||||
roundtrip |
||||
----------- |
||||
{} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": null}'); |
||||
roundtrip |
||||
------------- |
||||
{"1": null} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": 1}'); |
||||
roundtrip |
||||
----------- |
||||
{"1": 1} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": -1}'); |
||||
roundtrip |
||||
----------- |
||||
{"1": -1} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": 1.1}'); |
||||
roundtrip |
||||
------------ |
||||
{"1": 1.1} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": -1.1}'); |
||||
roundtrip |
||||
------------- |
||||
{"1": -1.1} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": "string1"}'); |
||||
roundtrip |
||||
------------------ |
||||
{"1": "string1"} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": {"2": [3, 4, 5]}, "2": 3}'); |
||||
roundtrip |
||||
--------------------------------- |
||||
{"1": {"2": [3, 4, 5]}, "2": 3} |
||||
(1 row) |
||||
|
||||
DROP EXTENSION plperl CASCADE; |
||||
NOTICE: drop cascades to 6 other objects |
||||
DETAIL: drop cascades to extension jsonb_plperl |
||||
drop cascades to function testhvtojsonb() |
||||
drop cascades to function testavtojsonb() |
||||
drop cascades to function testsvtojsonb() |
||||
drop cascades to function testregexptojsonb() |
||||
drop cascades to function roundtrip(jsonb) |
||||
@ -0,0 +1,211 @@ |
||||
CREATE EXTENSION jsonb_plperlu CASCADE; |
||||
NOTICE: installing required extension "plperlu" |
||||
CREATE FUNCTION testHVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = {a => 1, b => 'boo', c => undef}; |
||||
return $val; |
||||
$$; |
||||
SELECT testHVToJsonb(); |
||||
testhvtojsonb |
||||
--------------------------------- |
||||
{"a": 1, "b": "boo", "c": null} |
||||
(1 row) |
||||
|
||||
CREATE FUNCTION testAVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; |
||||
return $val; |
||||
$$; |
||||
SELECT testAVToJsonb(); |
||||
testavtojsonb |
||||
--------------------------------------------- |
||||
[{"a": 1, "b": "boo", "c": null}, {"d": 2}] |
||||
(1 row) |
||||
|
||||
CREATE FUNCTION testSVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = 1; |
||||
return $val; |
||||
$$; |
||||
SELECT testSVToJsonb(); |
||||
testsvtojsonb |
||||
--------------- |
||||
1 |
||||
(1 row) |
||||
|
||||
CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
return ('1' =~ m(0\t2)); |
||||
$$; |
||||
SELECT testRegexpToJsonb(); |
||||
ERROR: cannot transform this Perl type to jsonb |
||||
CONTEXT: PL/Perl function "testregexptojsonb" |
||||
CREATE FUNCTION roundtrip(val jsonb) RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
return $_[0]; |
||||
$$; |
||||
SELECT roundtrip('null'); |
||||
roundtrip |
||||
----------- |
||||
null |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('1'); |
||||
roundtrip |
||||
----------- |
||||
1 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('1E+131071'); |
||||
ERROR: cannot convert infinite value to jsonb |
||||
CONTEXT: PL/Perl function "roundtrip" |
||||
SELECT roundtrip('-1'); |
||||
roundtrip |
||||
----------- |
||||
-1 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('1.2'); |
||||
roundtrip |
||||
----------- |
||||
1.2 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('-1.2'); |
||||
roundtrip |
||||
----------- |
||||
-1.2 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('"string"'); |
||||
roundtrip |
||||
----------- |
||||
"string" |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('"NaN"'); |
||||
roundtrip |
||||
----------- |
||||
"NaN" |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('true'); |
||||
roundtrip |
||||
----------- |
||||
1 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('false'); |
||||
roundtrip |
||||
----------- |
||||
0 |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[]'); |
||||
roundtrip |
||||
----------- |
||||
[] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[null, null]'); |
||||
roundtrip |
||||
-------------- |
||||
[null, null] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[1, 2, 3]'); |
||||
roundtrip |
||||
----------- |
||||
[1, 2, 3] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[-1, 2, -3]'); |
||||
roundtrip |
||||
------------- |
||||
[-1, 2, -3] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[1.2, 2.3, 3.4]'); |
||||
roundtrip |
||||
----------------- |
||||
[1.2, 2.3, 3.4] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('[-1.2, 2.3, -3.4]'); |
||||
roundtrip |
||||
------------------- |
||||
[-1.2, 2.3, -3.4] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('["string1", "string2"]'); |
||||
roundtrip |
||||
------------------------ |
||||
["string1", "string2"] |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{}'); |
||||
roundtrip |
||||
----------- |
||||
{} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": null}'); |
||||
roundtrip |
||||
------------- |
||||
{"1": null} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": 1}'); |
||||
roundtrip |
||||
----------- |
||||
{"1": 1} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": -1}'); |
||||
roundtrip |
||||
----------- |
||||
{"1": -1} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": 1.1}'); |
||||
roundtrip |
||||
------------ |
||||
{"1": 1.1} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": -1.1}'); |
||||
roundtrip |
||||
------------- |
||||
{"1": -1.1} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": "string1"}'); |
||||
roundtrip |
||||
------------------ |
||||
{"1": "string1"} |
||||
(1 row) |
||||
|
||||
SELECT roundtrip('{"1": {"2": [3, 4, 5]}, "2": 3}'); |
||||
roundtrip |
||||
--------------------------------- |
||||
{"1": {"2": [3, 4, 5]}, "2": 3} |
||||
(1 row) |
||||
|
||||
DROP EXTENSION plperlu CASCADE; |
||||
NOTICE: drop cascades to 6 other objects |
||||
DETAIL: drop cascades to extension jsonb_plperlu |
||||
drop cascades to function testhvtojsonb() |
||||
drop cascades to function testavtojsonb() |
||||
drop cascades to function testsvtojsonb() |
||||
drop cascades to function testregexptojsonb() |
||||
drop cascades to function roundtrip(jsonb) |
||||
@ -0,0 +1,19 @@ |
||||
/* contrib/jsonb_plperl/jsonb_plperl--1.0.sql */ |
||||
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION |
||||
\echo Use "CREATE EXTENSION jsonb_plperl" to load this file. \quit |
||||
|
||||
CREATE FUNCTION jsonb_to_plperl(val internal) RETURNS internal |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME'; |
||||
|
||||
CREATE FUNCTION plperl_to_jsonb(val internal) RETURNS jsonb |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME'; |
||||
|
||||
CREATE TRANSFORM FOR jsonb LANGUAGE plperl ( |
||||
FROM SQL WITH FUNCTION jsonb_to_plperl(internal), |
||||
TO SQL WITH FUNCTION plperl_to_jsonb(internal) |
||||
); |
||||
|
||||
COMMENT ON TRANSFORM FOR jsonb LANGUAGE plperl IS 'transform between jsonb and Perl'; |
||||
@ -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); |
||||
} |
||||
@ -0,0 +1,6 @@ |
||||
# jsonb_plperl extension |
||||
comment = 'transform between jsonb and plperl' |
||||
default_version = '1.0' |
||||
module_pathname = '$libdir/jsonb_plperl' |
||||
relocatable = true |
||||
requires = 'plperl' |
||||
@ -0,0 +1,19 @@ |
||||
/* contrib/json_plperl/jsonb_plperl--1.0.sql */ |
||||
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION |
||||
\echo Use "CREATE EXTENSION jsonb_plperlu" to load this file. \quit |
||||
|
||||
CREATE FUNCTION jsonb_to_plperl(val internal) RETURNS internal |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME'; |
||||
|
||||
CREATE FUNCTION plperl_to_jsonb(val internal) RETURNS jsonb |
||||
LANGUAGE C STRICT IMMUTABLE |
||||
AS 'MODULE_PATHNAME'; |
||||
|
||||
CREATE TRANSFORM FOR jsonb LANGUAGE plperlu ( |
||||
FROM SQL WITH FUNCTION jsonb_to_plperl(internal), |
||||
TO SQL WITH FUNCTION plperl_to_jsonb(internal) |
||||
); |
||||
|
||||
COMMENT ON TRANSFORM FOR jsonb LANGUAGE plperlu IS 'transform between jsonb and Perl'; |
||||
@ -0,0 +1,6 @@ |
||||
# jsonb_plperl extension |
||||
comment = 'transform between jsonb and plperlu' |
||||
default_version = '1.0' |
||||
module_pathname = '$libdir/jsonb_plperl' |
||||
relocatable = true |
||||
requires = 'plperlu' |
||||
@ -0,0 +1,86 @@ |
||||
CREATE EXTENSION jsonb_plperl CASCADE; |
||||
|
||||
|
||||
CREATE FUNCTION testHVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = {a => 1, b => 'boo', c => undef}; |
||||
return $val; |
||||
$$; |
||||
|
||||
SELECT testHVToJsonb(); |
||||
|
||||
|
||||
CREATE FUNCTION testAVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; |
||||
return $val; |
||||
$$; |
||||
|
||||
SELECT testAVToJsonb(); |
||||
|
||||
|
||||
CREATE FUNCTION testSVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = 1; |
||||
return $val; |
||||
$$; |
||||
|
||||
SELECT testSVToJsonb(); |
||||
|
||||
|
||||
CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
return ('1' =~ m(0\t2)); |
||||
$$; |
||||
|
||||
SELECT testRegexpToJsonb(); |
||||
|
||||
|
||||
CREATE FUNCTION roundtrip(val jsonb) RETURNS jsonb |
||||
LANGUAGE plperl |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
return $_[0]; |
||||
$$; |
||||
|
||||
|
||||
SELECT roundtrip('null'); |
||||
SELECT roundtrip('1'); |
||||
SELECT roundtrip('1E+131071'); |
||||
SELECT roundtrip('-1'); |
||||
SELECT roundtrip('1.2'); |
||||
SELECT roundtrip('-1.2'); |
||||
SELECT roundtrip('"string"'); |
||||
SELECT roundtrip('"NaN"'); |
||||
|
||||
SELECT roundtrip('true'); |
||||
SELECT roundtrip('false'); |
||||
|
||||
SELECT roundtrip('[]'); |
||||
SELECT roundtrip('[null, null]'); |
||||
SELECT roundtrip('[1, 2, 3]'); |
||||
SELECT roundtrip('[-1, 2, -3]'); |
||||
SELECT roundtrip('[1.2, 2.3, 3.4]'); |
||||
SELECT roundtrip('[-1.2, 2.3, -3.4]'); |
||||
SELECT roundtrip('["string1", "string2"]'); |
||||
|
||||
SELECT roundtrip('{}'); |
||||
SELECT roundtrip('{"1": null}'); |
||||
SELECT roundtrip('{"1": 1}'); |
||||
SELECT roundtrip('{"1": -1}'); |
||||
SELECT roundtrip('{"1": 1.1}'); |
||||
SELECT roundtrip('{"1": -1.1}'); |
||||
SELECT roundtrip('{"1": "string1"}'); |
||||
|
||||
SELECT roundtrip('{"1": {"2": [3, 4, 5]}, "2": 3}'); |
||||
|
||||
|
||||
DROP EXTENSION plperl CASCADE; |
||||
@ -0,0 +1,86 @@ |
||||
CREATE EXTENSION jsonb_plperlu CASCADE; |
||||
|
||||
|
||||
CREATE FUNCTION testHVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = {a => 1, b => 'boo', c => undef}; |
||||
return $val; |
||||
$$; |
||||
|
||||
SELECT testHVToJsonb(); |
||||
|
||||
|
||||
CREATE FUNCTION testAVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; |
||||
return $val; |
||||
$$; |
||||
|
||||
SELECT testAVToJsonb(); |
||||
|
||||
|
||||
CREATE FUNCTION testSVToJsonb() RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
$val = 1; |
||||
return $val; |
||||
$$; |
||||
|
||||
SELECT testSVToJsonb(); |
||||
|
||||
|
||||
CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
return ('1' =~ m(0\t2)); |
||||
$$; |
||||
|
||||
SELECT testRegexpToJsonb(); |
||||
|
||||
|
||||
CREATE FUNCTION roundtrip(val jsonb) RETURNS jsonb |
||||
LANGUAGE plperlu |
||||
TRANSFORM FOR TYPE jsonb |
||||
AS $$ |
||||
return $_[0]; |
||||
$$; |
||||
|
||||
|
||||
SELECT roundtrip('null'); |
||||
SELECT roundtrip('1'); |
||||
SELECT roundtrip('1E+131071'); |
||||
SELECT roundtrip('-1'); |
||||
SELECT roundtrip('1.2'); |
||||
SELECT roundtrip('-1.2'); |
||||
SELECT roundtrip('"string"'); |
||||
SELECT roundtrip('"NaN"'); |
||||
|
||||
SELECT roundtrip('true'); |
||||
SELECT roundtrip('false'); |
||||
|
||||
SELECT roundtrip('[]'); |
||||
SELECT roundtrip('[null, null]'); |
||||
SELECT roundtrip('[1, 2, 3]'); |
||||
SELECT roundtrip('[-1, 2, -3]'); |
||||
SELECT roundtrip('[1.2, 2.3, 3.4]'); |
||||
SELECT roundtrip('[-1.2, 2.3, -3.4]'); |
||||
SELECT roundtrip('["string1", "string2"]'); |
||||
|
||||
SELECT roundtrip('{}'); |
||||
SELECT roundtrip('{"1": null}'); |
||||
SELECT roundtrip('{"1": 1}'); |
||||
SELECT roundtrip('{"1": -1}'); |
||||
SELECT roundtrip('{"1": 1.1}'); |
||||
SELECT roundtrip('{"1": -1.1}'); |
||||
SELECT roundtrip('{"1": "string1"}'); |
||||
|
||||
SELECT roundtrip('{"1": {"2": [3, 4, 5]}, "2": 3}'); |
||||
|
||||
|
||||
DROP EXTENSION plperlu CASCADE; |
||||
Loading…
Reference in new issue