Age Owner Branch data TLA 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 : :
519 tgl@sss.pgh.pa.us 11 :CBC 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 *
3068 peter_e@gmx.net 21 : 81 : JsonbValue_to_SV(JsonbValue *jbv)
22 : : {
23 : 81 : dTHX;
24 : :
25 [ + + + + : 81 : switch (jbv->type)
+ - ]
26 : : {
27 : 6 : case jbvBinary:
2992 tgl@sss.pgh.pa.us 28 : 6 : return Jsonb_to_SV(jbv->val.binary.data);
29 : :
3068 peter_e@gmx.net 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 : :
3068 peter_e@gmx.net 56 :UBC 0 : default:
57 [ # # ]: 0 : elog(ERROR, "unexpected jsonb value type: %d", jbv->type);
58 : : return NULL;
59 : : }
60 : : }
61 : :
62 : : static SV *
3068 peter_e@gmx.net 63 :CBC 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 tgl@sss.pgh.pa.us 71 : 57 : check_stack_depth();
72 : :
3068 peter_e@gmx.net 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)
3068 peter_e@gmx.net 86 [ # # ]:UBC 0 : elog(ERROR, "unexpected jsonb token: %d", r);
87 : :
2992 tgl@sss.pgh.pa.us 88 :CBC 19 : return JsonbValue_to_SV(&v);
89 : : }
90 : : else
91 : : {
3068 peter_e@gmx.net 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 : :
2992 tgl@sss.pgh.pa.us 100 : 20 : return newRV((SV *) av);
101 : : }
102 : :
3068 peter_e@gmx.net 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 : :
2992 tgl@sss.pgh.pa.us 125 : 18 : return newRV((SV *) hv);
126 : : }
127 : :
3068 peter_e@gmx.net 128 :UBC 0 : default:
129 [ # # ]: 0 : elog(ERROR, "unexpected jsonb token: %d", r);
130 : : return NULL;
131 : : }
132 : : }
133 : :
134 : : static void
263 tgl@sss.pgh.pa.us 135 :CBC 23 : AV_to_JsonbValue(AV *in, JsonbInState *jsonb_state)
136 : : {
3068 peter_e@gmx.net 137 : 23 : dTHX;
10 tgl@sss.pgh.pa.us 138 : 23 : Size_t pcount = av_count(in);
139 : :
3068 peter_e@gmx.net 140 : 23 : pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
141 : :
10 tgl@sss.pgh.pa.us 142 [ + + ]: 73 : for (Size_t i = 0; i < pcount; i++)
143 : : {
3068 peter_e@gmx.net 144 : 50 : SV **value = av_fetch(in, i, FALSE);
145 : :
146 [ + - ]: 50 : if (value)
263 tgl@sss.pgh.pa.us 147 : 50 : SV_to_JsonbValue(*value, jsonb_state, true);
148 : : }
149 : :
150 : 23 : pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
3068 peter_e@gmx.net 151 : 23 : }
152 : :
153 : : static void
263 tgl@sss.pgh.pa.us 154 : 29 : HV_to_JsonbValue(HV *obj, JsonbInState *jsonb_state)
155 : : {
3068 peter_e@gmx.net 156 : 29 : dTHX;
157 : : JsonbValue key;
158 : : HE *he;
159 : :
160 : 29 : key.type = jbvString;
161 : :
162 : 29 : pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
163 : :
164 : 29 : (void) hv_iterinit(obj);
165 : :
9 tgl@sss.pgh.pa.us 166 [ + + ]:GNC 68 : while ((he = hv_iternext(obj)))
167 : : {
168 : 39 : char *k = hek2cstr(he);
8 169 : 39 : SV *val = hv_iterval(obj, he);
170 : :
9 171 : 39 : key.val.string.val = k;
172 : 39 : key.val.string.len = strlen(k);
3068 peter_e@gmx.net 173 :CBC 39 : pushJsonbValue(jsonb_state, WJB_KEY, &key);
263 tgl@sss.pgh.pa.us 174 : 39 : SV_to_JsonbValue(val, jsonb_state, false);
175 : : }
176 : :
177 : 29 : pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
3068 peter_e@gmx.net 178 : 29 : }
179 : :
180 : : static void
263 tgl@sss.pgh.pa.us 181 : 154 : SV_to_JsonbValue(SV *in, JsonbInState *jsonb_state, bool is_elem)
182 : : {
3068 peter_e@gmx.net 183 : 154 : dTHX;
184 : : JsonbValue out; /* result */
185 : :
186 : : /* this can recurse via AV_to_JsonbValue() or HV_to_JsonbValue() */
71 tgl@sss.pgh.pa.us 187 : 154 : check_stack_depth();
188 : :
189 : : /* Dereference references recursively. */
8 tgl@sss.pgh.pa.us 190 [ + - ]:GNC 154 : plperl_materialize_sv(in);
9 tgl@sss.pgh.pa.us 191 [ + - + + ]:CBC 206 : while (in && SvROK(in))
192 : : {
193 : : /*
194 : : * It's possible for circular references to make this an infinite
195 : : * loop. Checking for such a situation seems like much more trouble
196 : : * than it's worth, but let's provide a way to break out of the loop.
197 : : */
71 198 [ - + ]: 52 : CHECK_FOR_INTERRUPTS();
3068 peter_e@gmx.net 199 : 52 : in = SvRV(in);
8 tgl@sss.pgh.pa.us 200 [ + - ]:GNC 52 : plperl_materialize_sv(in);
201 : : }
202 : :
9 tgl@sss.pgh.pa.us 203 [ - + ]:CBC 154 : if (!in)
204 : : {
9 tgl@sss.pgh.pa.us 205 :UBC 0 : out.type = jbvNull;
206 : : }
207 : : else
208 : : {
9 tgl@sss.pgh.pa.us 209 [ + + + ]:CBC 154 : switch (SvTYPE(in))
210 : : {
211 : 23 : case SVt_PVAV:
212 : 23 : AV_to_JsonbValue((AV *) in, jsonb_state);
213 : 52 : return;
214 : :
215 : 29 : case SVt_PVHV:
216 : 29 : HV_to_JsonbValue((HV *) in, jsonb_state);
217 : 29 : return;
218 : :
219 : 102 : default:
220 [ + + ]: 102 : if (!SvOK(in))
221 : : {
222 : 13 : out.type = jbvNull;
223 : : }
224 [ + + ]: 89 : else if (SvUOK(in))
225 : : {
226 : : /*
227 : : * If UV is >=64 bits, we have no better way to make this
228 : : * happen than converting to text and back. Given the low
229 : : * usage of UV in Perl code, it's not clear it's worth
230 : : * working hard to provide alternate code paths.
231 : : */
232 : 2 : const char *strval = SvPV_nolen(in);
233 : :
234 : 2 : out.type = jbvNumeric;
235 : 2 : out.val.numeric =
236 : 2 : DatumGetNumeric(DirectFunctionCall3(numeric_in,
237 : : CStringGetDatum(strval),
238 : : ObjectIdGetDatum(InvalidOid),
239 : : Int32GetDatum(-1)));
240 : : }
241 [ + + ]: 87 : else if (SvIOK(in))
242 : : {
243 : 12 : IV ival = SvIV(in);
244 : :
245 : 12 : out.type = jbvNumeric;
246 : 12 : out.val.numeric = int64_to_numeric(ival);
247 : : }
248 [ + + ]: 75 : else if (SvNOK(in))
249 : : {
250 : 53 : double nval = SvNV(in);
251 : :
252 : : /*
253 : : * jsonb doesn't allow infinity or NaN (per JSON
254 : : * specification), but the numeric type that is used for
255 : : * the storage accepts those, so we have to reject them
256 : : * here explicitly.
257 : : */
258 [ + + ]: 53 : if (isinf(nval))
259 [ + - ]: 1 : ereport(ERROR,
260 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
261 : : errmsg("cannot convert infinity to jsonb")));
262 [ - + ]: 52 : if (isnan(nval))
9 tgl@sss.pgh.pa.us 263 [ # # ]:UBC 0 : ereport(ERROR,
264 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
265 : : errmsg("cannot convert NaN to jsonb")));
266 : :
9 tgl@sss.pgh.pa.us 267 :CBC 52 : out.type = jbvNumeric;
268 : 52 : out.val.numeric =
269 : 52 : DatumGetNumeric(DirectFunctionCall1(float8_numeric,
270 : : Float8GetDatum(nval)));
271 : : }
272 [ + - ]: 22 : else if (SvPOK(in))
273 : : {
274 : 22 : out.type = jbvString;
275 : 22 : out.val.string.val = sv2cstr(in);
276 : 22 : out.val.string.len = strlen(out.val.string.val);
277 : : }
278 : : else
279 : : {
280 : : /*
281 : : * XXX It might be nice if we could include the Perl type
282 : : * in the error message.
283 : : */
3041 peter_e@gmx.net 284 [ # # ]:UBC 0 : ereport(ERROR,
285 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
286 : : errmsg("cannot transform this Perl type to jsonb")));
287 : : }
288 : : }
289 : : }
290 : :
263 tgl@sss.pgh.pa.us 291 [ + + ]:CBC 101 : if (jsonb_state->parseState)
292 : : {
293 : : /* We're in an array or object, so push value as element or field. */
294 [ + + ]: 79 : pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, &out);
295 : : }
296 : : else
297 : : {
298 : : /*
299 : : * We are at top level, so it's a raw scalar. If we just shove the
300 : : * scalar value into jsonb_state->result, JsonbValueToJsonb will take
301 : : * care of wrapping it into a dummy array.
302 : : */
303 : 22 : jsonb_state->result = palloc_object(JsonbValue);
304 : 22 : memcpy(jsonb_state->result, &out, sizeof(JsonbValue));
305 : : }
306 : : }
307 : :
308 : :
3068 peter_e@gmx.net 309 : 4 : PG_FUNCTION_INFO_V1(jsonb_to_plperl);
310 : :
311 : : Datum
312 : 51 : jsonb_to_plperl(PG_FUNCTION_ARGS)
313 : : {
314 : 51 : dTHX;
315 : 51 : Jsonb *in = PG_GETARG_JSONB_P(0);
316 : 51 : SV *sv = Jsonb_to_SV(&in->root);
317 : :
2992 tgl@sss.pgh.pa.us 318 : 51 : return PointerGetDatum(sv);
319 : : }
320 : :
321 : :
3068 peter_e@gmx.net 322 : 4 : PG_FUNCTION_INFO_V1(plperl_to_jsonb);
323 : :
324 : : Datum
325 : 65 : plperl_to_jsonb(PG_FUNCTION_ARGS)
326 : : {
327 : 65 : dTHX;
328 : 65 : SV *in = (SV *) PG_GETARG_POINTER(0);
263 tgl@sss.pgh.pa.us 329 : 65 : JsonbInState jsonb_state = {0};
330 : :
331 : 65 : SV_to_JsonbValue(in, &jsonb_state, true);
332 : 64 : PG_RETURN_JSONB_P(JsonbValueToJsonb(jsonb_state.result));
333 : : }
|