On Sat, 13 Jan 2018 09:29:46 -0500
Andrew Dunstan <andrew.duns...@2ndquadrant.com> wrote:

> There's a bit of an impedance mismatch and inconsistency here. I think
> we need to deal with json scalars (particularly numerics) the same way
> we do for plain scalar arguments. We don't convert a numeric argument
> to and SvNV. We just do this in plperl_call_perl_func():
> 
>                     tmp = OutputFunctionCall(&(desc->arg_out_func[i]),
>                                              fcinfo->arg[i]);
>                     sv = cstr2sv(tmp);
>                     pfree(tmp)
>     [...]
> 
>                 PUSHs(sv_2mortal(sv));
> 
> Large numerics won't work as SvNV values, which have to fit in a
> standard double. So I think we should treat them the same way we do
> for plain scalar arguments.
> 
> (This also suggests that the tests are a bit deficient in not testing
> jsonb with large numeric values.)
> 
> I'm going to set this back to waiting on author pending discussion.
> 
> 
> cheers
> 
> andrew
> 

Hello,
thank you for your attention.

I'm sorry, but I couldn't understand what types of numerics you was
talking about. Large numerics are just transformed into "inf" (or
"Inf") and the patch contains such test. But there were no tests with
numerics close to "inf" but not "inf" yet. So, I've added such test.

Also I've fixed the thing Thomas Munro was talking about.


--
Anthony Bykov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
diff --git a/contrib/Makefile b/contrib/Makefile
index 8046ca4..53d44fe 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -75,9 +75,9 @@ ALWAYS_SUBDIRS += sepgsql
 endif
 
 ifeq ($(with_perl),yes)
-SUBDIRS += hstore_plperl
+SUBDIRS += hstore_plperl jsonb_plperl
 else
-ALWAYS_SUBDIRS += hstore_plperl
+ALWAYS_SUBDIRS += hstore_plperl jsonb_plperl
 endif
 
 ifeq ($(with_python),yes)
diff --git a/contrib/jsonb_plperl/Makefile b/contrib/jsonb_plperl/Makefile
new file mode 100644
index 0000000..cd86553
--- /dev/null
+++ b/contrib/jsonb_plperl/Makefile
@@ -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_plperl_relocatability jsonb_plperlu jsonb_plperlu_relocatability
+
+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
diff --git a/contrib/jsonb_plperl/expected/jsonb_plperl.out b/contrib/jsonb_plperl/expected/jsonb_plperl.out
new file mode 100644
index 0000000..9032f7e
--- /dev/null
+++ b/contrib/jsonb_plperl/expected/jsonb_plperl.out
@@ -0,0 +1,235 @@
+CREATE EXTENSION jsonb_plperl CASCADE;
+NOTICE:  installing required extension "plperl"
+-- test hash -> jsonb
+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)
+
+-- test array -> jsonb
+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)
+
+-- test scalar -> jsonb
+CREATE FUNCTION testSVToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = 1;
+return $val;
+$$;
+SELECT testSVToJsonb();
+ testsvtojsonb 
+---------------
+ 1
+(1 row)
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testBlessedToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+my $class = shift;
+my $tmp = { a=>"a", 1=>"1" };
+bless $tmp, $class;
+return $tmp;
+$$;
+SELECT testBlessedToJsonb();
+  testblessedtojsonb  
+----------------------
+ {"1": "1", "a": "a"}
+(1 row)
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+return ('1' =~ m(0\t2));
+$$;
+SELECT testRegexpToJsonb();
+ERROR:  could not transform to type "jsonb"
+DETAIL:  The type you are trying to transform can't be transformed to jsonb
+CONTEXT:  PL/Perl function "testregexptojsonb"
+-- test jsonb -> scalar -> jsonb
+CREATE FUNCTION testSVToJsonb2(val jsonb) RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+return $_[0];
+$$;
+SELECT testSVToJsonb2('null');
+ testsvtojsonb2 
+----------------
+ null
+(1 row)
+
+SELECT testSVToJsonb2('1');
+ testsvtojsonb2 
+----------------
+ 1
+(1 row)
+
+SELECT testSVToJsonb2('1E+131071');
+ERROR:  could not transform to type "jsonb"
+DETAIL:  The type you are trying to transform can't be transformed to jsonb
+CONTEXT:  PL/Perl function "testsvtojsonb2"
+SELECT testSVToJsonb2('-1');
+ testsvtojsonb2 
+----------------
+ -1
+(1 row)
+
+SELECT testSVToJsonb2('1.2');
+ testsvtojsonb2 
+----------------
+ 1.2
+(1 row)
+
+SELECT testSVToJsonb2('-1.2');
+ testsvtojsonb2 
+----------------
+ -1.2
+(1 row)
+
+SELECT testSVToJsonb2('"string"');
+ testsvtojsonb2 
+----------------
+ "string"
+(1 row)
+
+SELECT testSVToJsonb2('"NaN"');
+ testsvtojsonb2 
+----------------
+ "NaN"
+(1 row)
+
+SELECT testSVToJsonb2('true');
+ testsvtojsonb2 
+----------------
+ 1
+(1 row)
+
+SELECT testSVToJsonb2('false');
+ testsvtojsonb2 
+----------------
+ 0
+(1 row)
+
+SELECT testSVToJsonb2('[]');
+ testsvtojsonb2 
+----------------
+ []
+(1 row)
+
+SELECT testSVToJsonb2('[null,null]');
+ testsvtojsonb2 
+----------------
+ [null, null]
+(1 row)
+
+SELECT testSVToJsonb2('[1,2,3]');
+ testsvtojsonb2 
+----------------
+ [1, 2, 3]
+(1 row)
+
+SELECT testSVToJsonb2('[-1,2,-3]');
+ testsvtojsonb2 
+----------------
+ [-1, 2, -3]
+(1 row)
+
+SELECT testSVToJsonb2('[1.2,2.3,3.4]');
+ testsvtojsonb2  
+-----------------
+ [1.2, 2.3, 3.4]
+(1 row)
+
+SELECT testSVToJsonb2('[-1.2,2.3,-3.4]');
+  testsvtojsonb2   
+-------------------
+ [-1.2, 2.3, -3.4]
+(1 row)
+
+SELECT testSVToJsonb2('["string1","string2"]');
+     testsvtojsonb2     
+------------------------
+ ["string1", "string2"]
+(1 row)
+
+SELECT testSVToJsonb2('{}');
+ testsvtojsonb2 
+----------------
+ {}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":null}');
+ testsvtojsonb2 
+----------------
+ {"1": null}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":1}');
+ testsvtojsonb2 
+----------------
+ {"1": 1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":-1}');
+ testsvtojsonb2 
+----------------
+ {"1": -1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":1.1}');
+ testsvtojsonb2 
+----------------
+ {"1": 1.1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":-1.1}');
+ testsvtojsonb2 
+----------------
+ {"1": -1.1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":"string1"}');
+  testsvtojsonb2  
+------------------
+ {"1": "string1"}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":{"2":[3,4,5]},"2":3}');
+         testsvtojsonb2          
+---------------------------------
+ {"1": {"2": [3, 4, 5]}, "2": 3}
+(1 row)
+
+DROP EXTENSION plperl CASCADE;
+NOTICE:  drop cascades to 7 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 testblessedtojsonb()
+drop cascades to function testregexptojsonb()
+drop cascades to function testsvtojsonb2(jsonb)
diff --git a/contrib/jsonb_plperl/expected/jsonb_plperl_relocatability.out b/contrib/jsonb_plperl/expected/jsonb_plperl_relocatability.out
new file mode 100644
index 0000000..b334d0c
--- /dev/null
+++ b/contrib/jsonb_plperl/expected/jsonb_plperl_relocatability.out
@@ -0,0 +1,21 @@
+CREATE EXTENSION jsonb_plperl CASCADE;
+NOTICE:  installing required extension "plperl"
+CREATE SCHEMA test;
+alter extension jsonb_plperl set schema test;
+create function test.test(val jsonb) returns jsonb
+language plperl
+transform for type jsonb
+as $$
+return val
+$$;
+select test.test('1'::jsonb);
+ test  
+-------
+ "val"
+(1 row)
+
+drop extension plperl cascade;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to extension jsonb_plperl
+drop cascades to function test.test(jsonb)
+drop schema test cascade;
diff --git a/contrib/jsonb_plperl/expected/jsonb_plperlu.out b/contrib/jsonb_plperl/expected/jsonb_plperlu.out
new file mode 100644
index 0000000..e0309ba
--- /dev/null
+++ b/contrib/jsonb_plperl/expected/jsonb_plperlu.out
@@ -0,0 +1,235 @@
+CREATE EXTENSION jsonb_plperlu CASCADE;
+NOTICE:  installing required extension "plperlu"
+-- test hash -> jsonb
+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)
+
+-- test array -> jsonb
+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)
+
+-- test scalar -> jsonb
+CREATE FUNCTION testSVToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = 1;
+return $val;
+$$;
+SELECT testSVToJsonb();
+ testsvtojsonb 
+---------------
+ 1
+(1 row)
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testBlessedToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+my $class = shift;
+my $tmp = { a=>"a", 1=>"1" };
+bless $tmp, $class;
+return $tmp;
+$$;
+SELECT testBlessedToJsonb();
+  testblessedtojsonb  
+----------------------
+ {"1": "1", "a": "a"}
+(1 row)
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+return ('1' =~ m(0\t2));
+$$;
+SELECT testRegexpToJsonb();
+ERROR:  could not transform to type "jsonb"
+DETAIL:  The type you are trying to transform can't be transformed to jsonb
+CONTEXT:  PL/Perl function "testregexptojsonb"
+-- test jsonb -> scalar -> jsonb
+CREATE FUNCTION testSVToJsonb2(val jsonb) RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+return $_[0];
+$$;
+SELECT testSVToJsonb2('null');
+ testsvtojsonb2 
+----------------
+ null
+(1 row)
+
+SELECT testSVToJsonb2('1');
+ testsvtojsonb2 
+----------------
+ 1
+(1 row)
+
+SELECT testSVToJsonb2('1E+131071');
+ERROR:  could not transform to type "jsonb"
+DETAIL:  The type you are trying to transform can't be transformed to jsonb
+CONTEXT:  PL/Perl function "testsvtojsonb2"
+SELECT testSVToJsonb2('-1');
+ testsvtojsonb2 
+----------------
+ -1
+(1 row)
+
+SELECT testSVToJsonb2('1.2');
+ testsvtojsonb2 
+----------------
+ 1.2
+(1 row)
+
+SELECT testSVToJsonb2('-1.2');
+ testsvtojsonb2 
+----------------
+ -1.2
+(1 row)
+
+SELECT testSVToJsonb2('"string"');
+ testsvtojsonb2 
+----------------
+ "string"
+(1 row)
+
+SELECT testSVToJsonb2('"NaN"');
+ testsvtojsonb2 
+----------------
+ "NaN"
+(1 row)
+
+SELECT testSVToJsonb2('true');
+ testsvtojsonb2 
+----------------
+ 1
+(1 row)
+
+SELECT testSVToJsonb2('false');
+ testsvtojsonb2 
+----------------
+ 0
+(1 row)
+
+SELECT testSVToJsonb2('[]');
+ testsvtojsonb2 
+----------------
+ []
+(1 row)
+
+SELECT testSVToJsonb2('[null,null]');
+ testsvtojsonb2 
+----------------
+ [null, null]
+(1 row)
+
+SELECT testSVToJsonb2('[1,2,3]');
+ testsvtojsonb2 
+----------------
+ [1, 2, 3]
+(1 row)
+
+SELECT testSVToJsonb2('[-1,2,-3]');
+ testsvtojsonb2 
+----------------
+ [-1, 2, -3]
+(1 row)
+
+SELECT testSVToJsonb2('[1.2,2.3,3.4]');
+ testsvtojsonb2  
+-----------------
+ [1.2, 2.3, 3.4]
+(1 row)
+
+SELECT testSVToJsonb2('[-1.2,2.3,-3.4]');
+  testsvtojsonb2   
+-------------------
+ [-1.2, 2.3, -3.4]
+(1 row)
+
+SELECT testSVToJsonb2('["string1","string2"]');
+     testsvtojsonb2     
+------------------------
+ ["string1", "string2"]
+(1 row)
+
+SELECT testSVToJsonb2('{}');
+ testsvtojsonb2 
+----------------
+ {}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":null}');
+ testsvtojsonb2 
+----------------
+ {"1": null}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":1}');
+ testsvtojsonb2 
+----------------
+ {"1": 1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":-1}');
+ testsvtojsonb2 
+----------------
+ {"1": -1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":1.1}');
+ testsvtojsonb2 
+----------------
+ {"1": 1.1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":-1.1}');
+ testsvtojsonb2 
+----------------
+ {"1": -1.1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":"string1"}');
+  testsvtojsonb2  
+------------------
+ {"1": "string1"}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":{"2":[3,4,5]},"2":3}');
+         testsvtojsonb2          
+---------------------------------
+ {"1": {"2": [3, 4, 5]}, "2": 3}
+(1 row)
+
+DROP EXTENSION plperlu CASCADE;
+NOTICE:  drop cascades to 7 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 testblessedtojsonb()
+drop cascades to function testregexptojsonb()
+drop cascades to function testsvtojsonb2(jsonb)
diff --git a/contrib/jsonb_plperl/expected/jsonb_plperlu_relocatability.out b/contrib/jsonb_plperl/expected/jsonb_plperlu_relocatability.out
new file mode 100644
index 0000000..a640da7
--- /dev/null
+++ b/contrib/jsonb_plperl/expected/jsonb_plperlu_relocatability.out
@@ -0,0 +1,21 @@
+CREATE EXTENSION jsonb_plperlu CASCADE;
+NOTICE:  installing required extension "plperlu"
+CREATE SCHEMA test;
+alter extension jsonb_plperlu set schema test;
+create function test.test(val jsonb) returns jsonb
+language plperlu
+transform for type jsonb
+as $$
+return val
+$$;
+select test.test('1'::jsonb);
+ test  
+-------
+ "val"
+(1 row)
+
+drop extension plperlu cascade;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to extension jsonb_plperlu
+drop cascades to function test.test(jsonb)
+drop schema test cascade;
diff --git a/contrib/jsonb_plperl/jsonb_plperl--1.0.sql b/contrib/jsonb_plperl/jsonb_plperl--1.0.sql
new file mode 100644
index 0000000..25dedbe
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperl--1.0.sql
@@ -0,0 +1,17 @@
+/* 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)
+);
diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c
new file mode 100644
index 0000000..e0cf40c
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperl.c
@@ -0,0 +1,331 @@
+/* This document contains an implementation of transformations from perl
+ * object to jsonb and vise versa.
+ * In this file you can find implementation of transformations:
+ * - jsonb_to_plperl(PG_FUNCTION_ARGS)
+ * - plperl_to_jsonb(PG_FUNCTION_ARGS)
+ */
+#include "postgres.h"
+
+/* #undef _ is needed because "_" was already defined in include/c.h:971:0 */
+#undef _
+
+#include "fmgr.h"
+#include "plperl.h"
+#include "plperl_helpers.h"
+
+#include "utils/jsonb.h"
+#include "utils/fmgrprotos.h"
+
+PG_MODULE_MAGIC;
+
+static SV  *SV_FromJsonb(JsonbContainer *jsonb);
+
+static JsonbValue *HV_ToJsonbValue(HV *obj, JsonbParseState *jsonb_state);
+
+static JsonbValue *SV_ToJsonbValue(SV *obj, JsonbParseState *jsonb_state);
+
+/*
+ * SV_FromJsonbValue
+ *
+ * Transform JsonbValue into SV
+ */
+static SV  *
+SV_FromJsonbValue(JsonbValue *jsonbValue)
+{
+	dTHX;
+	SV		   *result;
+	char	   *str;
+
+	switch (jsonbValue->type)
+	{
+		case jbvBinary:
+			result = (SV *) newRV((SV *) SV_FromJsonb(jsonbValue->val.binary.data));
+			break;
+		case jbvNumeric:
+
+			/*
+			 * Transform incoming value into string and generate SV from
+			 * string
+			 */
+			str = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(jsonbValue->val.numeric)));
+			result = newSVnv(SvNV(cstr2sv(pstrdup(str))));
+			break;
+		case jbvString:
+			result = cstr2sv(pnstrdup(jsonbValue->val.string.val, jsonbValue->val.string.len));
+			break;
+		case jbvBool:
+			result = newSVnv(SvNV(jsonbValue->val.boolean ? &PL_sv_yes : &PL_sv_no));
+			break;
+		case jbvArray:
+			result = SV_FromJsonbValue(jsonbValue->val.array.elems);
+			break;
+		case jbvObject:
+			result = SV_FromJsonbValue(&(jsonbValue->val.object.pairs->value));
+			break;
+		case jbvNull:
+			result = newSV(0);
+			break;
+	}
+	return result;
+}
+
+/*
+ * SV_FromJsonb
+ *
+ * Transform JsonbContainer into SV
+ */
+static SV  *
+SV_FromJsonb(JsonbContainer *jsonb)
+{
+	dTHX;
+	SV		   *result;
+	SV		   *value;
+	JsonbIterator *it;
+	JsonbValue	v;
+
+	it = JsonbIteratorInit(jsonb);
+
+	switch (JsonbIteratorNext(&it, &v, true))
+	{
+		case WJB_BEGIN_ARRAY:
+			{
+				AV		   *av;
+				bool		raw_scalar;
+
+				/* array in v */
+				av = newAV();
+				raw_scalar = (v.val.array.rawScalar);
+				value = newSV(0);
+				while (JsonbIteratorNext(&it, &v, true) == WJB_ELEM)
+				{
+					value = SV_FromJsonbValue(&v);
+					av_push(av, value);
+				}
+				if (raw_scalar)
+					result = newRV(value);
+				else
+					result = (SV *) av;
+				break;
+			}
+		case WJB_BEGIN_OBJECT:
+			{
+				HV		   *object;
+				const char *key;
+				int			keyLength;
+
+				/* hash in v */
+				object = newHV();
+				while (JsonbIteratorNext(&it, &v, true) == WJB_KEY)
+				{
+					/* json key in v */
+					key = pstrdup(v.val.string.val);
+					keyLength = v.val.string.len;
+					JsonbIteratorNext(&it, &v, true);
+					value = SV_FromJsonbValue(&v);
+					(void) hv_store(object, key, keyLength, value, 0);
+				}
+				result = (SV *) object;
+				break;
+			}
+		case WJB_ELEM:
+		case WJB_VALUE:
+		case WJB_KEY:
+			/* simple objects */
+			result = SV_FromJsonbValue(&v);
+			break;
+		case WJB_DONE:
+		case WJB_END_OBJECT:
+		case WJB_END_ARRAY:
+			pg_unreachable();
+			break;
+	}
+	return result;
+}
+
+/*
+ * jsonb_to_plperl
+ *
+ * Transform Jsonb into SV
+ */
+PG_FUNCTION_INFO_V1(jsonb_to_plperl);
+Datum
+jsonb_to_plperl(PG_FUNCTION_ARGS)
+{
+	dTHX;
+	Jsonb	   *in = PG_GETARG_JSONB_P(0);
+	SV		   *sv;
+
+	sv = SV_FromJsonb(&in->root);
+
+	return PointerGetDatum(newRV(sv));
+}
+
+/*
+ * AV_ToJsonbValue
+ *
+ * Transform AV into JsonbValue
+ * jsonb_state defines conversion state
+ */
+static JsonbValue *
+AV_ToJsonbValue(AV *in, JsonbParseState *jsonb_state)
+{
+	dTHX;
+
+	JsonbValue *jbvElem;
+	JsonbValue *out = NULL;
+	ssize_t		pcount;
+	ssize_t		i;
+
+	pcount = av_len(in) + 1;
+	pushJsonbValue(&jsonb_state, WJB_BEGIN_ARRAY, NULL);
+
+	for (i = 0; i < pcount; i++)
+	{
+		SV		  **value;
+
+		value = av_fetch(in, i, false);
+		jbvElem = SV_ToJsonbValue(*value, jsonb_state);
+
+		/*
+		 * If "value" was a complex structure, it was already pushed to jsonb
+		 * and there is no need to push it again
+		 */
+		if (IsAJsonbScalar(jbvElem))
+			pushJsonbValue(&jsonb_state, WJB_ELEM, jbvElem);
+	}
+	out = pushJsonbValue(&jsonb_state, WJB_END_ARRAY, NULL);
+	return out;
+}
+
+/*
+ * SV_ToJsonbValue
+ *
+ * Transform Jsonb into SV
+ */
+static JsonbValue *
+SV_ToJsonbValue(SV *in, JsonbParseState *jsonb_state)
+{
+	dTHX;
+	svtype		type;			/* type of incoming object */
+	JsonbValue *out;			/* result */
+
+	type = SvTYPE(in);
+	switch (type)
+	{
+		case SVt_PVAV:
+			out = AV_ToJsonbValue((AV *) in, jsonb_state);
+			break;
+		case SVt_PVHV:
+			out = HV_ToJsonbValue((HV *) in, jsonb_state);
+			break;
+		case SVt_NV:
+		case SVt_IV:
+			{
+				if (SvROK(in))
+					/* if "in" is a pointer */
+					out = SV_ToJsonbValue((SV *) SvRV(in), jsonb_state);
+				else
+				{
+					/* if "in" is a numeric */
+					char	   *str;
+					int			i;
+
+					out = palloc(sizeof(JsonbValue));
+					str = sv2cstr(in);
+
+					/*
+					 * We need to lowercase the string because infinity
+					 * representation varies from version to version
+					 */
+					for (i = 0; str[i]; i++)
+						str[i] = tolower(str[i]);
+
+					if (strcmp(str, "inf") == 0)
+						/* in case when variable in is "inf" */
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+								 (errmsg("could not transform to type \"%s\"", "jsonb"),
+								  errdetail("The type you are trying to transform can't be transformed to jsonb"))));
+					else
+					{
+						Datum		tmp;
+
+						tmp = DirectFunctionCall3(numeric_in, CStringGetDatum(str), 0, -1);
+						out->val.numeric = DatumGetNumeric(tmp);
+						out->type = jbvNumeric;
+					}
+				}
+				break;
+			}
+		case SVt_NULL:
+			out = palloc(sizeof(JsonbValue));
+			out->type = jbvNull;
+			break;
+		case SVt_PV:
+
+			/*
+			 * String
+			 */
+			out = palloc(sizeof(JsonbValue));
+			out->val.string.val = sv2cstr(in);
+			out->val.string.len = strlen(out->val.string.val);
+			out->type = jbvString;
+			break;
+		default:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 (errmsg("could not transform to type \"%s\"", "jsonb"),
+					  errdetail("The type you are trying to transform can't be transformed to jsonb"))));
+			break;
+	}
+	return out;
+}
+
+/*
+ * HV_ToJsonbValue
+ *
+ * Transform Jsonb into SV
+ */
+static JsonbValue *
+HV_ToJsonbValue(HV *obj, JsonbParseState *jsonb_state)
+{
+	dTHX;
+	JsonbValue *out;
+	HE		   *he;
+
+	pushJsonbValue(&jsonb_state, WJB_BEGIN_OBJECT, NULL);
+	while ((he = hv_iternext(obj)) != NULL)
+	{
+		JsonbValue *key;
+		JsonbValue *val;
+
+		key = SV_ToJsonbValue(HeSVKEY_force(he), jsonb_state);
+		pushJsonbValue(&jsonb_state, WJB_KEY, key);
+		val = SV_ToJsonbValue(HeVAL(he), jsonb_state);
+		if ((val == NULL) || (IsAJsonbScalar(val)))
+			pushJsonbValue(&jsonb_state, WJB_VALUE, val);
+	}
+	out = pushJsonbValue(&jsonb_state, WJB_END_OBJECT, NULL);
+	return out;
+}
+
+/*
+ * plperl_to_jsonb(SV *in)
+ *
+ * Transform Jsonb into SV
+ */
+PG_FUNCTION_INFO_V1(plperl_to_jsonb);
+Datum
+plperl_to_jsonb(PG_FUNCTION_ARGS)
+{
+	dTHX;
+	JsonbValue *out = NULL;
+	Jsonb	   *result;
+	JsonbParseState *jsonb_state = NULL;
+	SV		   *in;
+
+	in = (SV *) PG_GETARG_POINTER(0);
+	out = SV_ToJsonbValue(in, jsonb_state);
+	result = JsonbValueToJsonb(out);
+	PG_RETURN_POINTER(result);
+}
diff --git a/contrib/jsonb_plperl/jsonb_plperl.control b/contrib/jsonb_plperl/jsonb_plperl.control
new file mode 100644
index 0000000..26c86a7
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperl.control
@@ -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'
diff --git a/contrib/jsonb_plperl/jsonb_plperlu--1.0.sql b/contrib/jsonb_plperl/jsonb_plperlu--1.0.sql
new file mode 100644
index 0000000..65404f6
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperlu--1.0.sql
@@ -0,0 +1,17 @@
+/* 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)
+);
diff --git a/contrib/jsonb_plperl/jsonb_plperlu.control b/contrib/jsonb_plperl/jsonb_plperlu.control
new file mode 100644
index 0000000..946fc51
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperlu.control
@@ -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'
diff --git a/contrib/jsonb_plperl/sql/jsonb_plperl.sql b/contrib/jsonb_plperl/sql/jsonb_plperl.sql
new file mode 100644
index 0000000..9c5d64e
--- /dev/null
+++ b/contrib/jsonb_plperl/sql/jsonb_plperl.sql
@@ -0,0 +1,99 @@
+CREATE EXTENSION jsonb_plperl CASCADE;
+
+-- test hash -> jsonb
+CREATE FUNCTION testHVToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = {a => 1, b => 'boo', c => undef};
+return $val;
+$$;
+
+SELECT testHVToJsonb();
+
+-- test array -> jsonb
+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();
+
+-- test scalar -> jsonb
+CREATE FUNCTION testSVToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = 1;
+return $val;
+$$;
+
+SELECT testSVToJsonb();
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testBlessedToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+my $class = shift;
+my $tmp = { a=>"a", 1=>"1" };
+bless $tmp, $class;
+return $tmp;
+$$;
+
+SELECT testBlessedToJsonb();
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+return ('1' =~ m(0\t2));
+$$;
+
+SELECT testRegexpToJsonb();
+
+
+-- test jsonb -> scalar -> jsonb
+CREATE FUNCTION testSVToJsonb2(val jsonb) RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+return $_[0];
+$$;
+
+
+SELECT testSVToJsonb2('null');
+SELECT testSVToJsonb2('1');
+SELECT testSVToJsonb2('1E+131071');
+SELECT testSVToJsonb2('-1');
+SELECT testSVToJsonb2('1.2');
+SELECT testSVToJsonb2('-1.2');
+SELECT testSVToJsonb2('"string"');
+SELECT testSVToJsonb2('"NaN"');
+
+SELECT testSVToJsonb2('true');
+SELECT testSVToJsonb2('false');
+
+SELECT testSVToJsonb2('[]');
+SELECT testSVToJsonb2('[null,null]');
+SELECT testSVToJsonb2('[1,2,3]');
+SELECT testSVToJsonb2('[-1,2,-3]');
+SELECT testSVToJsonb2('[1.2,2.3,3.4]');
+SELECT testSVToJsonb2('[-1.2,2.3,-3.4]');
+SELECT testSVToJsonb2('["string1","string2"]');
+
+SELECT testSVToJsonb2('{}');
+SELECT testSVToJsonb2('{"1":null}');
+SELECT testSVToJsonb2('{"1":1}');
+SELECT testSVToJsonb2('{"1":-1}');
+SELECT testSVToJsonb2('{"1":1.1}');
+SELECT testSVToJsonb2('{"1":-1.1}');
+SELECT testSVToJsonb2('{"1":"string1"}');
+
+SELECT testSVToJsonb2('{"1":{"2":[3,4,5]},"2":3}');
+
+DROP EXTENSION plperl CASCADE;
diff --git a/contrib/jsonb_plperl/sql/jsonb_plperl_relocatability.sql b/contrib/jsonb_plperl/sql/jsonb_plperl_relocatability.sql
new file mode 100644
index 0000000..4745443
--- /dev/null
+++ b/contrib/jsonb_plperl/sql/jsonb_plperl_relocatability.sql
@@ -0,0 +1,14 @@
+CREATE EXTENSION jsonb_plperl CASCADE;
+CREATE SCHEMA test;
+alter extension jsonb_plperl set schema test;
+create function test.test(val jsonb) returns jsonb
+language plperl
+transform for type jsonb
+as $$
+return val
+$$;
+
+select test.test('1'::jsonb);
+
+drop extension plperl cascade;
+drop schema test cascade;
diff --git a/contrib/jsonb_plperl/sql/jsonb_plperlu.sql b/contrib/jsonb_plperl/sql/jsonb_plperlu.sql
new file mode 100644
index 0000000..a09052c
--- /dev/null
+++ b/contrib/jsonb_plperl/sql/jsonb_plperlu.sql
@@ -0,0 +1,99 @@
+CREATE EXTENSION jsonb_plperlu CASCADE;
+
+-- test hash -> jsonb
+CREATE FUNCTION testHVToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = {a => 1, b => 'boo', c => undef};
+return $val;
+$$;
+
+SELECT testHVToJsonb();
+
+-- test array -> jsonb
+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();
+
+-- test scalar -> jsonb
+CREATE FUNCTION testSVToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = 1;
+return $val;
+$$;
+
+SELECT testSVToJsonb();
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testBlessedToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+my $class = shift;
+my $tmp = { a=>"a", 1=>"1" };
+bless $tmp, $class;
+return $tmp;
+$$;
+
+SELECT testBlessedToJsonb();
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+return ('1' =~ m(0\t2));
+$$;
+
+SELECT testRegexpToJsonb();
+
+
+-- test jsonb -> scalar -> jsonb
+CREATE FUNCTION testSVToJsonb2(val jsonb) RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+return $_[0];
+$$;
+
+
+SELECT testSVToJsonb2('null');
+SELECT testSVToJsonb2('1');
+SELECT testSVToJsonb2('1E+131071');
+SELECT testSVToJsonb2('-1');
+SELECT testSVToJsonb2('1.2');
+SELECT testSVToJsonb2('-1.2');
+SELECT testSVToJsonb2('"string"');
+SELECT testSVToJsonb2('"NaN"');
+
+SELECT testSVToJsonb2('true');
+SELECT testSVToJsonb2('false');
+
+SELECT testSVToJsonb2('[]');
+SELECT testSVToJsonb2('[null,null]');
+SELECT testSVToJsonb2('[1,2,3]');
+SELECT testSVToJsonb2('[-1,2,-3]');
+SELECT testSVToJsonb2('[1.2,2.3,3.4]');
+SELECT testSVToJsonb2('[-1.2,2.3,-3.4]');
+SELECT testSVToJsonb2('["string1","string2"]');
+
+SELECT testSVToJsonb2('{}');
+SELECT testSVToJsonb2('{"1":null}');
+SELECT testSVToJsonb2('{"1":1}');
+SELECT testSVToJsonb2('{"1":-1}');
+SELECT testSVToJsonb2('{"1":1.1}');
+SELECT testSVToJsonb2('{"1":-1.1}');
+SELECT testSVToJsonb2('{"1":"string1"}');
+
+SELECT testSVToJsonb2('{"1":{"2":[3,4,5]},"2":3}');
+
+DROP EXTENSION plperlu CASCADE;
diff --git a/contrib/jsonb_plperl/sql/jsonb_plperlu_relocatability.sql b/contrib/jsonb_plperl/sql/jsonb_plperlu_relocatability.sql
new file mode 100644
index 0000000..29233e4
--- /dev/null
+++ b/contrib/jsonb_plperl/sql/jsonb_plperlu_relocatability.sql
@@ -0,0 +1,14 @@
+CREATE EXTENSION jsonb_plperlu CASCADE;
+CREATE SCHEMA test;
+alter extension jsonb_plperlu set schema test;
+create function test.test(val jsonb) returns jsonb
+language plperlu
+transform for type jsonb
+as $$
+return val
+$$;
+
+select test.test('1'::jsonb);
+
+drop extension plperlu cascade;
+drop schema test cascade;
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 731b469..c461dba 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -569,4 +569,18 @@ SELECT jdoc-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"tags": ["qu
       compared using the default database collation.
   </para>
  </sect2>
+  <sect2>
+  <title>Transforms</title>
+
+  <para>
+   Additional extensions are available that implement transforms for
+   the <type>jsonb</type> type for the language PL/Perl.  The
+   extensions for PL/Perl are called
+   <literal>jsonb_plperlu</literal> and <literal>jsonb_plperl</literal>
+   If you use them, <type>jsonb</type> values are mapped to
+   Perl RV.
+  </para>
+ </sect2>
+
+
 </sect1>
diff --git a/contrib/Makefile b/contrib/Makefile
index 8046ca4..53d44fe 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -75,9 +75,9 @@ ALWAYS_SUBDIRS += sepgsql
 endif
 
 ifeq ($(with_perl),yes)
-SUBDIRS += hstore_plperl
+SUBDIRS += hstore_plperl jsonb_plperl
 else
-ALWAYS_SUBDIRS += hstore_plperl
+ALWAYS_SUBDIRS += hstore_plperl jsonb_plperl
 endif
 
 ifeq ($(with_python),yes)
diff --git a/contrib/jsonb_plperl/Makefile b/contrib/jsonb_plperl/Makefile
new file mode 100644
index 0000000..cd86553
--- /dev/null
+++ b/contrib/jsonb_plperl/Makefile
@@ -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_plperl_relocatability jsonb_plperlu jsonb_plperlu_relocatability
+
+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
diff --git a/contrib/jsonb_plperl/expected/jsonb_plperl.out b/contrib/jsonb_plperl/expected/jsonb_plperl.out
new file mode 100644
index 0000000..152e62d
--- /dev/null
+++ b/contrib/jsonb_plperl/expected/jsonb_plperl.out
@@ -0,0 +1,243 @@
+CREATE EXTENSION jsonb_plperl CASCADE;
+NOTICE:  installing required extension "plperl"
+-- test hash -> jsonb
+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)
+
+-- test array -> jsonb
+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)
+
+-- test scalar -> jsonb
+CREATE FUNCTION testSVToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = 1;
+return $val;
+$$;
+SELECT testSVToJsonb();
+ testsvtojsonb 
+---------------
+ 1
+(1 row)
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testBlessedToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+my $class = shift;
+my $tmp = { a=>"a", 1=>"1" };
+bless $tmp, $class;
+return $tmp;
+$$;
+SELECT testBlessedToJsonb();
+  testblessedtojsonb  
+----------------------
+ {"1": "1", "a": "a"}
+(1 row)
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+return ('1' =~ m(0\t2));
+$$;
+SELECT testRegexpToJsonb();
+ERROR:  could not transform to type "jsonb"
+DETAIL:  The type you are trying to transform can't be transformed to jsonb
+CONTEXT:  PL/Perl function "testregexptojsonb"
+-- test jsonb -> scalar -> jsonb
+CREATE FUNCTION testSVToJsonb2(val jsonb) RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+return $_[0];
+$$;
+SELECT testSVToJsonb2('null');
+ testsvtojsonb2 
+----------------
+ null
+(1 row)
+
+SELECT testSVToJsonb2('1');
+ testsvtojsonb2 
+----------------
+ 1
+(1 row)
+
+SELECT testSVToJsonb2('1E+131071');
+ERROR:  could not transform to type "jsonb"
+DETAIL:  The type you are trying to transform can't be transformed to jsonb
+CONTEXT:  PL/Perl function "testsvtojsonb2"
+SELECT testSVToJsonb2('-1');
+ testsvtojsonb2 
+----------------
+ -1
+(1 row)
+
+SELECT testSVToJsonb2('1.2');
+ testsvtojsonb2 
+----------------
+ 1.2
+(1 row)
+
+SELECT testSVToJsonb2('-1.2');
+ testsvtojsonb2 
+----------------
+ -1.2
+(1 row)
+
+SELECT testSVToJsonb2('"string"');
+ testsvtojsonb2 
+----------------
+ "string"
+(1 row)
+
+SELECT testSVToJsonb2('"NaN"');
+ testsvtojsonb2 
+----------------
+ "NaN"
+(1 row)
+
+SELECT testSVToJsonb2('true');
+ testsvtojsonb2 
+----------------
+ 1
+(1 row)
+
+SELECT testSVToJsonb2('false');
+ testsvtojsonb2 
+----------------
+ 0
+(1 row)
+
+SELECT testSVToJsonb2('[]');
+ testsvtojsonb2 
+----------------
+ []
+(1 row)
+
+SELECT testSVToJsonb2('[null,null]');
+ testsvtojsonb2 
+----------------
+ [null, null]
+(1 row)
+
+SELECT testSVToJsonb2('[1,2,3]');
+ testsvtojsonb2 
+----------------
+ [1, 2, 3]
+(1 row)
+
+SELECT testSVToJsonb2('[-1,2,-3]');
+ testsvtojsonb2 
+----------------
+ [-1, 2, -3]
+(1 row)
+
+SELECT testSVToJsonb2('[1.2,2.3,3.4]');
+ testsvtojsonb2  
+-----------------
+ [1.2, 2.3, 3.4]
+(1 row)
+
+SELECT testSVToJsonb2('[-1.2,2.3,-3.4]');
+  testsvtojsonb2   
+-------------------
+ [-1.2, 2.3, -3.4]
+(1 row)
+
+SELECT testSVToJsonb2('["string1","string2"]');
+     testsvtojsonb2     
+------------------------
+ ["string1", "string2"]
+(1 row)
+
+SELECT testSVToJsonb2('{}');
+ testsvtojsonb2 
+----------------
+ {}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":null}');
+ testsvtojsonb2 
+----------------
+ {"1": null}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":1}');
+ testsvtojsonb2 
+----------------
+ {"1": 1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":-1}');
+ testsvtojsonb2 
+----------------
+ {"1": -1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":1.1}');
+ testsvtojsonb2 
+----------------
+ {"1": 1.1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":-1.1}');
+ testsvtojsonb2 
+----------------
+ {"1": -1.1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":"string1"}');
+  testsvtojsonb2  
+------------------
+ {"1": "string1"}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":{"2":[3,4,5]},"2":3}');
+         testsvtojsonb2          
+---------------------------------
+ {"1": {"2": [3, 4, 5]}, "2": 3}
+(1 row)
+
+-- testing large numbers which are not represented as "inf" inside perl.
+-- 1E+309 - is inf while 1E+308 is not
+SELECT testSVToJsonb2('1E+308');
+                                                                                                                                                    testsvtojsonb2                                                                                                                                                     
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+(1 row)
+
+DROP EXTENSION plperl CASCADE;
+NOTICE:  drop cascades to 7 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 testblessedtojsonb()
+drop cascades to function testregexptojsonb()
+drop cascades to function testsvtojsonb2(jsonb)
diff --git a/contrib/jsonb_plperl/expected/jsonb_plperl_relocatability.out b/contrib/jsonb_plperl/expected/jsonb_plperl_relocatability.out
new file mode 100644
index 0000000..b334d0c
--- /dev/null
+++ b/contrib/jsonb_plperl/expected/jsonb_plperl_relocatability.out
@@ -0,0 +1,21 @@
+CREATE EXTENSION jsonb_plperl CASCADE;
+NOTICE:  installing required extension "plperl"
+CREATE SCHEMA test;
+alter extension jsonb_plperl set schema test;
+create function test.test(val jsonb) returns jsonb
+language plperl
+transform for type jsonb
+as $$
+return val
+$$;
+select test.test('1'::jsonb);
+ test  
+-------
+ "val"
+(1 row)
+
+drop extension plperl cascade;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to extension jsonb_plperl
+drop cascades to function test.test(jsonb)
+drop schema test cascade;
diff --git a/contrib/jsonb_plperl/expected/jsonb_plperlu.out b/contrib/jsonb_plperl/expected/jsonb_plperlu.out
new file mode 100644
index 0000000..fc989ce
--- /dev/null
+++ b/contrib/jsonb_plperl/expected/jsonb_plperlu.out
@@ -0,0 +1,243 @@
+CREATE EXTENSION jsonb_plperlu CASCADE;
+NOTICE:  installing required extension "plperlu"
+-- test hash -> jsonb
+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)
+
+-- test array -> jsonb
+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)
+
+-- test scalar -> jsonb
+CREATE FUNCTION testSVToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = 1;
+return $val;
+$$;
+SELECT testSVToJsonb();
+ testsvtojsonb 
+---------------
+ 1
+(1 row)
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testBlessedToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+my $class = shift;
+my $tmp = { a=>"a", 1=>"1" };
+bless $tmp, $class;
+return $tmp;
+$$;
+SELECT testBlessedToJsonb();
+  testblessedtojsonb  
+----------------------
+ {"1": "1", "a": "a"}
+(1 row)
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+return ('1' =~ m(0\t2));
+$$;
+SELECT testRegexpToJsonb();
+ERROR:  could not transform to type "jsonb"
+DETAIL:  The type you are trying to transform can't be transformed to jsonb
+CONTEXT:  PL/Perl function "testregexptojsonb"
+-- test jsonb -> scalar -> jsonb
+CREATE FUNCTION testSVToJsonb2(val jsonb) RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+return $_[0];
+$$;
+SELECT testSVToJsonb2('null');
+ testsvtojsonb2 
+----------------
+ null
+(1 row)
+
+SELECT testSVToJsonb2('1');
+ testsvtojsonb2 
+----------------
+ 1
+(1 row)
+
+SELECT testSVToJsonb2('1E+131071');
+ERROR:  could not transform to type "jsonb"
+DETAIL:  The type you are trying to transform can't be transformed to jsonb
+CONTEXT:  PL/Perl function "testsvtojsonb2"
+SELECT testSVToJsonb2('-1');
+ testsvtojsonb2 
+----------------
+ -1
+(1 row)
+
+SELECT testSVToJsonb2('1.2');
+ testsvtojsonb2 
+----------------
+ 1.2
+(1 row)
+
+SELECT testSVToJsonb2('-1.2');
+ testsvtojsonb2 
+----------------
+ -1.2
+(1 row)
+
+SELECT testSVToJsonb2('"string"');
+ testsvtojsonb2 
+----------------
+ "string"
+(1 row)
+
+SELECT testSVToJsonb2('"NaN"');
+ testsvtojsonb2 
+----------------
+ "NaN"
+(1 row)
+
+SELECT testSVToJsonb2('true');
+ testsvtojsonb2 
+----------------
+ 1
+(1 row)
+
+SELECT testSVToJsonb2('false');
+ testsvtojsonb2 
+----------------
+ 0
+(1 row)
+
+SELECT testSVToJsonb2('[]');
+ testsvtojsonb2 
+----------------
+ []
+(1 row)
+
+SELECT testSVToJsonb2('[null,null]');
+ testsvtojsonb2 
+----------------
+ [null, null]
+(1 row)
+
+SELECT testSVToJsonb2('[1,2,3]');
+ testsvtojsonb2 
+----------------
+ [1, 2, 3]
+(1 row)
+
+SELECT testSVToJsonb2('[-1,2,-3]');
+ testsvtojsonb2 
+----------------
+ [-1, 2, -3]
+(1 row)
+
+SELECT testSVToJsonb2('[1.2,2.3,3.4]');
+ testsvtojsonb2  
+-----------------
+ [1.2, 2.3, 3.4]
+(1 row)
+
+SELECT testSVToJsonb2('[-1.2,2.3,-3.4]');
+  testsvtojsonb2   
+-------------------
+ [-1.2, 2.3, -3.4]
+(1 row)
+
+SELECT testSVToJsonb2('["string1","string2"]');
+     testsvtojsonb2     
+------------------------
+ ["string1", "string2"]
+(1 row)
+
+SELECT testSVToJsonb2('{}');
+ testsvtojsonb2 
+----------------
+ {}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":null}');
+ testsvtojsonb2 
+----------------
+ {"1": null}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":1}');
+ testsvtojsonb2 
+----------------
+ {"1": 1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":-1}');
+ testsvtojsonb2 
+----------------
+ {"1": -1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":1.1}');
+ testsvtojsonb2 
+----------------
+ {"1": 1.1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":-1.1}');
+ testsvtojsonb2 
+----------------
+ {"1": -1.1}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":"string1"}');
+  testsvtojsonb2  
+------------------
+ {"1": "string1"}
+(1 row)
+
+SELECT testSVToJsonb2('{"1":{"2":[3,4,5]},"2":3}');
+         testsvtojsonb2          
+---------------------------------
+ {"1": {"2": [3, 4, 5]}, "2": 3}
+(1 row)
+
+-- testing large numbers which are not represented as "inf" inside perl.
+-- 1E+309 - is inf while 1E+308 is not
+SELECT testSVToJsonb2('1E+308');
+                                                                                                                                                    testsvtojsonb2                                                                                                                                                     
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+(1 row)
+
+DROP EXTENSION plperlu CASCADE;
+NOTICE:  drop cascades to 7 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 testblessedtojsonb()
+drop cascades to function testregexptojsonb()
+drop cascades to function testsvtojsonb2(jsonb)
diff --git a/contrib/jsonb_plperl/expected/jsonb_plperlu_relocatability.out b/contrib/jsonb_plperl/expected/jsonb_plperlu_relocatability.out
new file mode 100644
index 0000000..a640da7
--- /dev/null
+++ b/contrib/jsonb_plperl/expected/jsonb_plperlu_relocatability.out
@@ -0,0 +1,21 @@
+CREATE EXTENSION jsonb_plperlu CASCADE;
+NOTICE:  installing required extension "plperlu"
+CREATE SCHEMA test;
+alter extension jsonb_plperlu set schema test;
+create function test.test(val jsonb) returns jsonb
+language plperlu
+transform for type jsonb
+as $$
+return val
+$$;
+select test.test('1'::jsonb);
+ test  
+-------
+ "val"
+(1 row)
+
+drop extension plperlu cascade;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to extension jsonb_plperlu
+drop cascades to function test.test(jsonb)
+drop schema test cascade;
diff --git a/contrib/jsonb_plperl/jsonb_plperl--1.0.sql b/contrib/jsonb_plperl/jsonb_plperl--1.0.sql
new file mode 100644
index 0000000..25dedbe
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperl--1.0.sql
@@ -0,0 +1,17 @@
+/* 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)
+);
diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c
new file mode 100644
index 0000000..e0cf40c
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperl.c
@@ -0,0 +1,331 @@
+/* This document contains an implementation of transformations from perl
+ * object to jsonb and vise versa.
+ * In this file you can find implementation of transformations:
+ * - jsonb_to_plperl(PG_FUNCTION_ARGS)
+ * - plperl_to_jsonb(PG_FUNCTION_ARGS)
+ */
+#include "postgres.h"
+
+/* #undef _ is needed because "_" was already defined in include/c.h:971:0 */
+#undef _
+
+#include "fmgr.h"
+#include "plperl.h"
+#include "plperl_helpers.h"
+
+#include "utils/jsonb.h"
+#include "utils/fmgrprotos.h"
+
+PG_MODULE_MAGIC;
+
+static SV  *SV_FromJsonb(JsonbContainer *jsonb);
+
+static JsonbValue *HV_ToJsonbValue(HV *obj, JsonbParseState *jsonb_state);
+
+static JsonbValue *SV_ToJsonbValue(SV *obj, JsonbParseState *jsonb_state);
+
+/*
+ * SV_FromJsonbValue
+ *
+ * Transform JsonbValue into SV
+ */
+static SV  *
+SV_FromJsonbValue(JsonbValue *jsonbValue)
+{
+	dTHX;
+	SV		   *result;
+	char	   *str;
+
+	switch (jsonbValue->type)
+	{
+		case jbvBinary:
+			result = (SV *) newRV((SV *) SV_FromJsonb(jsonbValue->val.binary.data));
+			break;
+		case jbvNumeric:
+
+			/*
+			 * Transform incoming value into string and generate SV from
+			 * string
+			 */
+			str = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(jsonbValue->val.numeric)));
+			result = newSVnv(SvNV(cstr2sv(pstrdup(str))));
+			break;
+		case jbvString:
+			result = cstr2sv(pnstrdup(jsonbValue->val.string.val, jsonbValue->val.string.len));
+			break;
+		case jbvBool:
+			result = newSVnv(SvNV(jsonbValue->val.boolean ? &PL_sv_yes : &PL_sv_no));
+			break;
+		case jbvArray:
+			result = SV_FromJsonbValue(jsonbValue->val.array.elems);
+			break;
+		case jbvObject:
+			result = SV_FromJsonbValue(&(jsonbValue->val.object.pairs->value));
+			break;
+		case jbvNull:
+			result = newSV(0);
+			break;
+	}
+	return result;
+}
+
+/*
+ * SV_FromJsonb
+ *
+ * Transform JsonbContainer into SV
+ */
+static SV  *
+SV_FromJsonb(JsonbContainer *jsonb)
+{
+	dTHX;
+	SV		   *result;
+	SV		   *value;
+	JsonbIterator *it;
+	JsonbValue	v;
+
+	it = JsonbIteratorInit(jsonb);
+
+	switch (JsonbIteratorNext(&it, &v, true))
+	{
+		case WJB_BEGIN_ARRAY:
+			{
+				AV		   *av;
+				bool		raw_scalar;
+
+				/* array in v */
+				av = newAV();
+				raw_scalar = (v.val.array.rawScalar);
+				value = newSV(0);
+				while (JsonbIteratorNext(&it, &v, true) == WJB_ELEM)
+				{
+					value = SV_FromJsonbValue(&v);
+					av_push(av, value);
+				}
+				if (raw_scalar)
+					result = newRV(value);
+				else
+					result = (SV *) av;
+				break;
+			}
+		case WJB_BEGIN_OBJECT:
+			{
+				HV		   *object;
+				const char *key;
+				int			keyLength;
+
+				/* hash in v */
+				object = newHV();
+				while (JsonbIteratorNext(&it, &v, true) == WJB_KEY)
+				{
+					/* json key in v */
+					key = pstrdup(v.val.string.val);
+					keyLength = v.val.string.len;
+					JsonbIteratorNext(&it, &v, true);
+					value = SV_FromJsonbValue(&v);
+					(void) hv_store(object, key, keyLength, value, 0);
+				}
+				result = (SV *) object;
+				break;
+			}
+		case WJB_ELEM:
+		case WJB_VALUE:
+		case WJB_KEY:
+			/* simple objects */
+			result = SV_FromJsonbValue(&v);
+			break;
+		case WJB_DONE:
+		case WJB_END_OBJECT:
+		case WJB_END_ARRAY:
+			pg_unreachable();
+			break;
+	}
+	return result;
+}
+
+/*
+ * jsonb_to_plperl
+ *
+ * Transform Jsonb into SV
+ */
+PG_FUNCTION_INFO_V1(jsonb_to_plperl);
+Datum
+jsonb_to_plperl(PG_FUNCTION_ARGS)
+{
+	dTHX;
+	Jsonb	   *in = PG_GETARG_JSONB_P(0);
+	SV		   *sv;
+
+	sv = SV_FromJsonb(&in->root);
+
+	return PointerGetDatum(newRV(sv));
+}
+
+/*
+ * AV_ToJsonbValue
+ *
+ * Transform AV into JsonbValue
+ * jsonb_state defines conversion state
+ */
+static JsonbValue *
+AV_ToJsonbValue(AV *in, JsonbParseState *jsonb_state)
+{
+	dTHX;
+
+	JsonbValue *jbvElem;
+	JsonbValue *out = NULL;
+	ssize_t		pcount;
+	ssize_t		i;
+
+	pcount = av_len(in) + 1;
+	pushJsonbValue(&jsonb_state, WJB_BEGIN_ARRAY, NULL);
+
+	for (i = 0; i < pcount; i++)
+	{
+		SV		  **value;
+
+		value = av_fetch(in, i, false);
+		jbvElem = SV_ToJsonbValue(*value, jsonb_state);
+
+		/*
+		 * If "value" was a complex structure, it was already pushed to jsonb
+		 * and there is no need to push it again
+		 */
+		if (IsAJsonbScalar(jbvElem))
+			pushJsonbValue(&jsonb_state, WJB_ELEM, jbvElem);
+	}
+	out = pushJsonbValue(&jsonb_state, WJB_END_ARRAY, NULL);
+	return out;
+}
+
+/*
+ * SV_ToJsonbValue
+ *
+ * Transform Jsonb into SV
+ */
+static JsonbValue *
+SV_ToJsonbValue(SV *in, JsonbParseState *jsonb_state)
+{
+	dTHX;
+	svtype		type;			/* type of incoming object */
+	JsonbValue *out;			/* result */
+
+	type = SvTYPE(in);
+	switch (type)
+	{
+		case SVt_PVAV:
+			out = AV_ToJsonbValue((AV *) in, jsonb_state);
+			break;
+		case SVt_PVHV:
+			out = HV_ToJsonbValue((HV *) in, jsonb_state);
+			break;
+		case SVt_NV:
+		case SVt_IV:
+			{
+				if (SvROK(in))
+					/* if "in" is a pointer */
+					out = SV_ToJsonbValue((SV *) SvRV(in), jsonb_state);
+				else
+				{
+					/* if "in" is a numeric */
+					char	   *str;
+					int			i;
+
+					out = palloc(sizeof(JsonbValue));
+					str = sv2cstr(in);
+
+					/*
+					 * We need to lowercase the string because infinity
+					 * representation varies from version to version
+					 */
+					for (i = 0; str[i]; i++)
+						str[i] = tolower(str[i]);
+
+					if (strcmp(str, "inf") == 0)
+						/* in case when variable in is "inf" */
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+								 (errmsg("could not transform to type \"%s\"", "jsonb"),
+								  errdetail("The type you are trying to transform can't be transformed to jsonb"))));
+					else
+					{
+						Datum		tmp;
+
+						tmp = DirectFunctionCall3(numeric_in, CStringGetDatum(str), 0, -1);
+						out->val.numeric = DatumGetNumeric(tmp);
+						out->type = jbvNumeric;
+					}
+				}
+				break;
+			}
+		case SVt_NULL:
+			out = palloc(sizeof(JsonbValue));
+			out->type = jbvNull;
+			break;
+		case SVt_PV:
+
+			/*
+			 * String
+			 */
+			out = palloc(sizeof(JsonbValue));
+			out->val.string.val = sv2cstr(in);
+			out->val.string.len = strlen(out->val.string.val);
+			out->type = jbvString;
+			break;
+		default:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 (errmsg("could not transform to type \"%s\"", "jsonb"),
+					  errdetail("The type you are trying to transform can't be transformed to jsonb"))));
+			break;
+	}
+	return out;
+}
+
+/*
+ * HV_ToJsonbValue
+ *
+ * Transform Jsonb into SV
+ */
+static JsonbValue *
+HV_ToJsonbValue(HV *obj, JsonbParseState *jsonb_state)
+{
+	dTHX;
+	JsonbValue *out;
+	HE		   *he;
+
+	pushJsonbValue(&jsonb_state, WJB_BEGIN_OBJECT, NULL);
+	while ((he = hv_iternext(obj)) != NULL)
+	{
+		JsonbValue *key;
+		JsonbValue *val;
+
+		key = SV_ToJsonbValue(HeSVKEY_force(he), jsonb_state);
+		pushJsonbValue(&jsonb_state, WJB_KEY, key);
+		val = SV_ToJsonbValue(HeVAL(he), jsonb_state);
+		if ((val == NULL) || (IsAJsonbScalar(val)))
+			pushJsonbValue(&jsonb_state, WJB_VALUE, val);
+	}
+	out = pushJsonbValue(&jsonb_state, WJB_END_OBJECT, NULL);
+	return out;
+}
+
+/*
+ * plperl_to_jsonb(SV *in)
+ *
+ * Transform Jsonb into SV
+ */
+PG_FUNCTION_INFO_V1(plperl_to_jsonb);
+Datum
+plperl_to_jsonb(PG_FUNCTION_ARGS)
+{
+	dTHX;
+	JsonbValue *out = NULL;
+	Jsonb	   *result;
+	JsonbParseState *jsonb_state = NULL;
+	SV		   *in;
+
+	in = (SV *) PG_GETARG_POINTER(0);
+	out = SV_ToJsonbValue(in, jsonb_state);
+	result = JsonbValueToJsonb(out);
+	PG_RETURN_POINTER(result);
+}
diff --git a/contrib/jsonb_plperl/jsonb_plperl.control b/contrib/jsonb_plperl/jsonb_plperl.control
new file mode 100644
index 0000000..26c86a7
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperl.control
@@ -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'
diff --git a/contrib/jsonb_plperl/jsonb_plperlu--1.0.sql b/contrib/jsonb_plperl/jsonb_plperlu--1.0.sql
new file mode 100644
index 0000000..65404f6
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperlu--1.0.sql
@@ -0,0 +1,17 @@
+/* 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)
+);
diff --git a/contrib/jsonb_plperl/jsonb_plperlu.control b/contrib/jsonb_plperl/jsonb_plperlu.control
new file mode 100644
index 0000000..946fc51
--- /dev/null
+++ b/contrib/jsonb_plperl/jsonb_plperlu.control
@@ -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'
diff --git a/contrib/jsonb_plperl/sql/jsonb_plperl.sql b/contrib/jsonb_plperl/sql/jsonb_plperl.sql
new file mode 100644
index 0000000..d4d7973
--- /dev/null
+++ b/contrib/jsonb_plperl/sql/jsonb_plperl.sql
@@ -0,0 +1,104 @@
+CREATE EXTENSION jsonb_plperl CASCADE;
+
+-- test hash -> jsonb
+CREATE FUNCTION testHVToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = {a => 1, b => 'boo', c => undef};
+return $val;
+$$;
+
+SELECT testHVToJsonb();
+
+-- test array -> jsonb
+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();
+
+-- test scalar -> jsonb
+CREATE FUNCTION testSVToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = 1;
+return $val;
+$$;
+
+SELECT testSVToJsonb();
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testBlessedToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+my $class = shift;
+my $tmp = { a=>"a", 1=>"1" };
+bless $tmp, $class;
+return $tmp;
+$$;
+
+SELECT testBlessedToJsonb();
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+return ('1' =~ m(0\t2));
+$$;
+
+SELECT testRegexpToJsonb();
+
+
+-- test jsonb -> scalar -> jsonb
+CREATE FUNCTION testSVToJsonb2(val jsonb) RETURNS jsonb
+LANGUAGE plperl
+TRANSFORM FOR TYPE jsonb
+AS $$
+return $_[0];
+$$;
+
+
+SELECT testSVToJsonb2('null');
+SELECT testSVToJsonb2('1');
+SELECT testSVToJsonb2('1E+131071');
+SELECT testSVToJsonb2('-1');
+SELECT testSVToJsonb2('1.2');
+SELECT testSVToJsonb2('-1.2');
+SELECT testSVToJsonb2('"string"');
+SELECT testSVToJsonb2('"NaN"');
+
+SELECT testSVToJsonb2('true');
+SELECT testSVToJsonb2('false');
+
+SELECT testSVToJsonb2('[]');
+SELECT testSVToJsonb2('[null,null]');
+SELECT testSVToJsonb2('[1,2,3]');
+SELECT testSVToJsonb2('[-1,2,-3]');
+SELECT testSVToJsonb2('[1.2,2.3,3.4]');
+SELECT testSVToJsonb2('[-1.2,2.3,-3.4]');
+SELECT testSVToJsonb2('["string1","string2"]');
+
+SELECT testSVToJsonb2('{}');
+SELECT testSVToJsonb2('{"1":null}');
+SELECT testSVToJsonb2('{"1":1}');
+SELECT testSVToJsonb2('{"1":-1}');
+SELECT testSVToJsonb2('{"1":1.1}');
+SELECT testSVToJsonb2('{"1":-1.1}');
+SELECT testSVToJsonb2('{"1":"string1"}');
+
+SELECT testSVToJsonb2('{"1":{"2":[3,4,5]},"2":3}');
+
+-- testing large numbers which are not represented as "inf" inside perl.
+-- 1E+309 - is inf while 1E+308 is not
+SELECT testSVToJsonb2('1E+308');
+
+
+DROP EXTENSION plperl CASCADE;
diff --git a/contrib/jsonb_plperl/sql/jsonb_plperl_relocatability.sql b/contrib/jsonb_plperl/sql/jsonb_plperl_relocatability.sql
new file mode 100644
index 0000000..4745443
--- /dev/null
+++ b/contrib/jsonb_plperl/sql/jsonb_plperl_relocatability.sql
@@ -0,0 +1,14 @@
+CREATE EXTENSION jsonb_plperl CASCADE;
+CREATE SCHEMA test;
+alter extension jsonb_plperl set schema test;
+create function test.test(val jsonb) returns jsonb
+language plperl
+transform for type jsonb
+as $$
+return val
+$$;
+
+select test.test('1'::jsonb);
+
+drop extension plperl cascade;
+drop schema test cascade;
diff --git a/contrib/jsonb_plperl/sql/jsonb_plperlu.sql b/contrib/jsonb_plperl/sql/jsonb_plperlu.sql
new file mode 100644
index 0000000..4a040a6
--- /dev/null
+++ b/contrib/jsonb_plperl/sql/jsonb_plperlu.sql
@@ -0,0 +1,104 @@
+CREATE EXTENSION jsonb_plperlu CASCADE;
+
+-- test hash -> jsonb
+CREATE FUNCTION testHVToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = {a => 1, b => 'boo', c => undef};
+return $val;
+$$;
+
+SELECT testHVToJsonb();
+
+-- test array -> jsonb
+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();
+
+-- test scalar -> jsonb
+CREATE FUNCTION testSVToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+$val = 1;
+return $val;
+$$;
+
+SELECT testSVToJsonb();
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testBlessedToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+my $class = shift;
+my $tmp = { a=>"a", 1=>"1" };
+bless $tmp, $class;
+return $tmp;
+$$;
+
+SELECT testBlessedToJsonb();
+
+-- test blessed scalar -> jsonb
+CREATE FUNCTION testRegexpToJsonb() RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+return ('1' =~ m(0\t2));
+$$;
+
+SELECT testRegexpToJsonb();
+
+
+-- test jsonb -> scalar -> jsonb
+CREATE FUNCTION testSVToJsonb2(val jsonb) RETURNS jsonb
+LANGUAGE plperlu
+TRANSFORM FOR TYPE jsonb
+AS $$
+return $_[0];
+$$;
+
+
+SELECT testSVToJsonb2('null');
+SELECT testSVToJsonb2('1');
+SELECT testSVToJsonb2('1E+131071');
+SELECT testSVToJsonb2('-1');
+SELECT testSVToJsonb2('1.2');
+SELECT testSVToJsonb2('-1.2');
+SELECT testSVToJsonb2('"string"');
+SELECT testSVToJsonb2('"NaN"');
+
+SELECT testSVToJsonb2('true');
+SELECT testSVToJsonb2('false');
+
+SELECT testSVToJsonb2('[]');
+SELECT testSVToJsonb2('[null,null]');
+SELECT testSVToJsonb2('[1,2,3]');
+SELECT testSVToJsonb2('[-1,2,-3]');
+SELECT testSVToJsonb2('[1.2,2.3,3.4]');
+SELECT testSVToJsonb2('[-1.2,2.3,-3.4]');
+SELECT testSVToJsonb2('["string1","string2"]');
+
+SELECT testSVToJsonb2('{}');
+SELECT testSVToJsonb2('{"1":null}');
+SELECT testSVToJsonb2('{"1":1}');
+SELECT testSVToJsonb2('{"1":-1}');
+SELECT testSVToJsonb2('{"1":1.1}');
+SELECT testSVToJsonb2('{"1":-1.1}');
+SELECT testSVToJsonb2('{"1":"string1"}');
+
+SELECT testSVToJsonb2('{"1":{"2":[3,4,5]},"2":3}');
+
+-- testing large numbers which are not represented as "inf" inside perl.
+-- 1E+309 - is inf while 1E+308 is not
+SELECT testSVToJsonb2('1E+308');
+
+
+DROP EXTENSION plperlu CASCADE;
diff --git a/contrib/jsonb_plperl/sql/jsonb_plperlu_relocatability.sql b/contrib/jsonb_plperl/sql/jsonb_plperlu_relocatability.sql
new file mode 100644
index 0000000..29233e4
--- /dev/null
+++ b/contrib/jsonb_plperl/sql/jsonb_plperlu_relocatability.sql
@@ -0,0 +1,14 @@
+CREATE EXTENSION jsonb_plperlu CASCADE;
+CREATE SCHEMA test;
+alter extension jsonb_plperlu set schema test;
+create function test.test(val jsonb) returns jsonb
+language plperlu
+transform for type jsonb
+as $$
+return val
+$$;
+
+select test.test('1'::jsonb);
+
+drop extension plperlu cascade;
+drop schema test cascade;
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 731b469..c461dba 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -569,4 +569,18 @@ SELECT jdoc-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"tags": ["qu
       compared using the default database collation.
   </para>
  </sect2>
+  <sect2>
+  <title>Transforms</title>
+
+  <para>
+   Additional extensions are available that implement transforms for
+   the <type>jsonb</type> type for the language PL/Perl.  The
+   extensions for PL/Perl are called
+   <literal>jsonb_plperlu</literal> and <literal>jsonb_plperl</literal>
+   If you use them, <type>jsonb</type> values are mapped to
+   Perl RV.
+  </para>
+ </sect2>
+
+
 </sect1>

Reply via email to