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