1 #include "postgres.h"
2 
3 #include <float.h>
4 #include <math.h>
5 
6 /* Defined by Perl */
7 #undef _
8 
9 #include "fmgr.h"
10 #include "plperl.h"
11 #include "plperl_helpers.h"
12 #include "utils/jsonb.h"
13 #include "utils/fmgrprotos.h"
14 
15 PG_MODULE_MAGIC;
16 
17 static SV  *Jsonb_to_SV(JsonbContainer *jsonb);
18 static JsonbValue *SV_to_JsonbValue(SV *obj, JsonbParseState **ps, bool is_elem);
19 
20 
21 static SV  *
JsonbValue_to_SV(JsonbValue * jbv)22 JsonbValue_to_SV(JsonbValue *jbv)
23 {
24 	dTHX;
25 
26 	switch (jbv->type)
27 	{
28 		case jbvBinary:
29 			return Jsonb_to_SV(jbv->val.binary.data);
30 
31 		case jbvNumeric:
32 			{
33 				char	   *str = DatumGetCString(DirectFunctionCall1(numeric_out,
34 																	  NumericGetDatum(jbv->val.numeric)));
35 				SV		   *result = newSVnv(SvNV(cstr2sv(str)));
36 
37 				pfree(str);
38 				return result;
39 			}
40 
41 		case jbvString:
42 			{
43 				char	   *str = pnstrdup(jbv->val.string.val,
44 										   jbv->val.string.len);
45 				SV		   *result = cstr2sv(str);
46 
47 				pfree(str);
48 				return result;
49 			}
50 
51 		case jbvBool:
52 			return newSVnv(SvNV(jbv->val.boolean ? &PL_sv_yes : &PL_sv_no));
53 
54 		case jbvNull:
55 			return newSV(0);
56 
57 		default:
58 			elog(ERROR, "unexpected jsonb value type: %d", jbv->type);
59 			return NULL;
60 	}
61 }
62 
63 static SV  *
Jsonb_to_SV(JsonbContainer * jsonb)64 Jsonb_to_SV(JsonbContainer *jsonb)
65 {
66 	dTHX;
67 	JsonbValue	v;
68 	JsonbIterator *it;
69 	JsonbIteratorToken r;
70 
71 	it = JsonbIteratorInit(jsonb);
72 	r = JsonbIteratorNext(&it, &v, true);
73 
74 	switch (r)
75 	{
76 		case WJB_BEGIN_ARRAY:
77 			if (v.val.array.rawScalar)
78 			{
79 				JsonbValue	tmp;
80 
81 				if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
82 					(r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
83 					(r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
84 					elog(ERROR, "unexpected jsonb token: %d", r);
85 
86 				return JsonbValue_to_SV(&v);
87 			}
88 			else
89 			{
90 				AV		   *av = newAV();
91 
92 				while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
93 				{
94 					if (r == WJB_ELEM)
95 						av_push(av, JsonbValue_to_SV(&v));
96 				}
97 
98 				return newRV((SV *) av);
99 			}
100 
101 		case WJB_BEGIN_OBJECT:
102 			{
103 				HV		   *hv = newHV();
104 
105 				while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
106 				{
107 					if (r == WJB_KEY)
108 					{
109 						/* json key in v, json value in val */
110 						JsonbValue	val;
111 
112 						if (JsonbIteratorNext(&it, &val, true) == WJB_VALUE)
113 						{
114 							SV		   *value = JsonbValue_to_SV(&val);
115 
116 							(void) hv_store(hv,
117 											v.val.string.val, v.val.string.len,
118 											value, 0);
119 						}
120 					}
121 				}
122 
123 				return newRV((SV *) hv);
124 			}
125 
126 		default:
127 			elog(ERROR, "unexpected jsonb token: %d", r);
128 			return NULL;
129 	}
130 }
131 
132 static JsonbValue *
AV_to_JsonbValue(AV * in,JsonbParseState ** jsonb_state)133 AV_to_JsonbValue(AV *in, JsonbParseState **jsonb_state)
134 {
135 	dTHX;
136 	SSize_t		pcount = av_len(in) + 1;
137 	SSize_t		i;
138 
139 	pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
140 
141 	for (i = 0; i < pcount; i++)
142 	{
143 		SV		  **value = av_fetch(in, i, FALSE);
144 
145 		if (value)
146 			(void) SV_to_JsonbValue(*value, jsonb_state, true);
147 	}
148 
149 	return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
150 }
151 
152 static JsonbValue *
HV_to_JsonbValue(HV * obj,JsonbParseState ** jsonb_state)153 HV_to_JsonbValue(HV *obj, JsonbParseState **jsonb_state)
154 {
155 	dTHX;
156 	JsonbValue	key;
157 	SV		   *val;
158 	char	   *kstr;
159 	I32			klen;
160 
161 	key.type = jbvString;
162 
163 	pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
164 
165 	(void) hv_iterinit(obj);
166 
167 	while ((val = hv_iternextsv(obj, &kstr, &klen)))
168 	{
169 		key.val.string.val = pnstrdup(kstr, klen);
170 		key.val.string.len = klen;
171 		pushJsonbValue(jsonb_state, WJB_KEY, &key);
172 		(void) SV_to_JsonbValue(val, jsonb_state, false);
173 	}
174 
175 	return pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
176 }
177 
178 static JsonbValue *
SV_to_JsonbValue(SV * in,JsonbParseState ** jsonb_state,bool is_elem)179 SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem)
180 {
181 	dTHX;
182 	JsonbValue	out;			/* result */
183 
184 	/* Dereference references recursively. */
185 	while (SvROK(in))
186 		in = SvRV(in);
187 
188 	switch (SvTYPE(in))
189 	{
190 		case SVt_PVAV:
191 			return AV_to_JsonbValue((AV *) in, jsonb_state);
192 
193 		case SVt_PVHV:
194 			return HV_to_JsonbValue((HV *) in, jsonb_state);
195 
196 		default:
197 			if (!SvOK(in))
198 			{
199 				out.type = jbvNull;
200 			}
201 			else if (SvUOK(in))
202 			{
203 				/*
204 				 * If UV is >=64 bits, we have no better way to make this
205 				 * happen than converting to text and back.  Given the low
206 				 * usage of UV in Perl code, it's not clear it's worth working
207 				 * hard to provide alternate code paths.
208 				 */
209 				const char *strval = SvPV_nolen(in);
210 
211 				out.type = jbvNumeric;
212 				out.val.numeric =
213 					DatumGetNumeric(DirectFunctionCall3(numeric_in,
214 														CStringGetDatum(strval),
215 														ObjectIdGetDatum(InvalidOid),
216 														Int32GetDatum(-1)));
217 			}
218 			else if (SvIOK(in))
219 			{
220 				IV			ival = SvIV(in);
221 
222 				out.type = jbvNumeric;
223 				out.val.numeric =
224 					DatumGetNumeric(DirectFunctionCall1(int8_numeric,
225 														Int64GetDatum((int64) ival)));
226 			}
227 			else if (SvNOK(in))
228 			{
229 				double		nval = SvNV(in);
230 
231 				/*
232 				 * jsonb doesn't allow infinity or NaN (per JSON
233 				 * specification), but the numeric type that is used for the
234 				 * storage accepts NaN, so we have to prevent it here
235 				 * explicitly.  We don't really have to check for isinf()
236 				 * here, as numeric doesn't allow it and it would be caught
237 				 * later, but it makes for a nicer error message.
238 				 */
239 				if (isinf(nval))
240 					ereport(ERROR,
241 							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
242 							 (errmsg("cannot convert infinity to jsonb"))));
243 				if (isnan(nval))
244 					ereport(ERROR,
245 							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
246 							 (errmsg("cannot convert NaN to jsonb"))));
247 
248 				out.type = jbvNumeric;
249 				out.val.numeric =
250 					DatumGetNumeric(DirectFunctionCall1(float8_numeric,
251 														Float8GetDatum(nval)));
252 			}
253 			else if (SvPOK(in))
254 			{
255 				out.type = jbvString;
256 				out.val.string.val = sv2cstr(in);
257 				out.val.string.len = strlen(out.val.string.val);
258 			}
259 			else
260 			{
261 				/*
262 				 * XXX It might be nice if we could include the Perl type in
263 				 * the error message.
264 				 */
265 				ereport(ERROR,
266 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
267 						 (errmsg("cannot transform this Perl type to jsonb"))));
268 				return NULL;
269 			}
270 	}
271 
272 	/* Push result into 'jsonb_state' unless it is a raw scalar. */
273 	return *jsonb_state
274 		? pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, &out)
275 		: memcpy(palloc(sizeof(JsonbValue)), &out, sizeof(JsonbValue));
276 }
277 
278 
279 PG_FUNCTION_INFO_V1(jsonb_to_plperl);
280 
281 Datum
jsonb_to_plperl(PG_FUNCTION_ARGS)282 jsonb_to_plperl(PG_FUNCTION_ARGS)
283 {
284 	dTHX;
285 	Jsonb	   *in = PG_GETARG_JSONB_P(0);
286 	SV		   *sv = Jsonb_to_SV(&in->root);
287 
288 	return PointerGetDatum(sv);
289 }
290 
291 
292 PG_FUNCTION_INFO_V1(plperl_to_jsonb);
293 
294 Datum
plperl_to_jsonb(PG_FUNCTION_ARGS)295 plperl_to_jsonb(PG_FUNCTION_ARGS)
296 {
297 	dTHX;
298 	JsonbParseState *jsonb_state = NULL;
299 	SV		   *in = (SV *) PG_GETARG_POINTER(0);
300 	JsonbValue *out = SV_to_JsonbValue(in, &jsonb_state, true);
301 	Jsonb	   *result = JsonbValueToJsonb(out);
302 
303 	PG_RETURN_JSONB_P(result);
304 }
305