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