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 : :
461 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 *
3010 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:
2934 tgl@sss.pgh.pa.us 28 : 6 : return Jsonb_to_SV(jbv->val.binary.data);
29 : :
3010 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 : :
3010 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 *
3010 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() */
13 tgl@sss.pgh.pa.us 71 : 57 : check_stack_depth();
72 : :
3010 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)
3010 peter_e@gmx.net 86 [ # # ]:UBC 0 : elog(ERROR, "unexpected jsonb token: %d", r);
87 : :
2934 tgl@sss.pgh.pa.us 88 :CBC 19 : return JsonbValue_to_SV(&v);
89 : : }
90 : : else
91 : : {
3010 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 : :
2934 tgl@sss.pgh.pa.us 100 : 20 : return newRV((SV *) av);
101 : : }
102 : :
3010 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 : :
2934 tgl@sss.pgh.pa.us 125 : 18 : return newRV((SV *) hv);
126 : : }
127 : :
3010 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
205 tgl@sss.pgh.pa.us 135 :GNC 22 : AV_to_JsonbValue(AV *in, JsonbInState *jsonb_state)
136 : : {
3010 peter_e@gmx.net 137 :CBC 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)
205 tgl@sss.pgh.pa.us 148 :GNC 48 : SV_to_JsonbValue(*value, jsonb_state, true);
149 : : }
150 : :
151 : 22 : pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
3010 peter_e@gmx.net 152 :GIC 22 : }
153 : :
154 : : static void
205 tgl@sss.pgh.pa.us 155 :GNC 28 : HV_to_JsonbValue(HV *obj, JsonbInState *jsonb_state)
156 : : {
3010 peter_e@gmx.net 157 :CBC 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 : :
tgl@sss.pgh.pa.us 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;
peter_e@gmx.net 173 : 36 : pushJsonbValue(jsonb_state, WJB_KEY, &key);
205 tgl@sss.pgh.pa.us 174 :GNC 36 : SV_to_JsonbValue(val, jsonb_state, false);
175 : : }
176 : :
177 : 28 : pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
3010 peter_e@gmx.net 178 :GIC 28 : }
179 : :
180 : : static void
205 tgl@sss.pgh.pa.us 181 :GNC 147 : SV_to_JsonbValue(SV *in, JsonbInState *jsonb_state, bool is_elem)
182 : : {
3010 peter_e@gmx.net 183 :CBC 147 : dTHX;
184 : : JsonbValue out; /* result */
185 : :
186 : : /* this can recurse via AV_to_JsonbValue() or HV_to_JsonbValue() */
13 tgl@sss.pgh.pa.us 187 : 147 : check_stack_depth();
188 : :
189 : : /* Dereference references recursively. */
3010 peter_e@gmx.net 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 : : */
13 tgl@sss.pgh.pa.us 197 [ - + ]: 50 : CHECK_FOR_INTERRUPTS();
3010 peter_e@gmx.net 198 : 50 : in = SvRV(in);
199 : : }
200 : :
201 [ + + + ]: 147 : switch (SvTYPE(in))
202 : : {
203 : 22 : case SVt_PVAV:
205 tgl@sss.pgh.pa.us 204 :GNC 22 : AV_to_JsonbValue((AV *) in, jsonb_state);
205 : 50 : return;
206 : :
3010 peter_e@gmx.net 207 :CBC 28 : case SVt_PVHV:
205 tgl@sss.pgh.pa.us 208 :GNC 28 : HV_to_JsonbValue((HV *) in, jsonb_state);
209 : 28 : return;
210 : :
3009 tgl@sss.pgh.pa.us 211 :CBC 97 : default:
2522 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 : : */
2934 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 : : {
3009 235 : 10 : IV ival = SvIV(in);
236 : :
237 : 10 : out.type = jbvNumeric;
2120 peter@eisentraut.org 238 : 10 : out.val.numeric = int64_to_numeric(ival);
239 : : }
3009 tgl@sss.pgh.pa.us 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))
3010 peter_e@gmx.net 251 [ + - ]: 1 : ereport(ERROR,
252 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
253 : : errmsg("cannot convert infinity to jsonb")));
2983 254 [ - + ]: 52 : if (isnan(nval))
2983 peter_e@gmx.net 255 [ # # ]:UBC 0 : ereport(ERROR,
256 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
257 : : errmsg("cannot convert NaN to jsonb")));
258 : :
3010 peter_e@gmx.net 259 :CBC 52 : out.type = jbvNumeric;
3009 tgl@sss.pgh.pa.us 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 : : */
3009 tgl@sss.pgh.pa.us 276 [ # # ]:UBC 0 : ereport(ERROR,
277 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
278 : : errmsg("cannot transform this Perl type to jsonb")));
279 : : }
280 : : }
281 : :
205 tgl@sss.pgh.pa.us 282 [ + + ]:GNC 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 : :
3010 peter_e@gmx.net 300 :CBC 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 : :
2934 tgl@sss.pgh.pa.us 309 : 51 : return PointerGetDatum(sv);
310 : : }
311 : :
312 : :
3010 peter_e@gmx.net 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);
205 tgl@sss.pgh.pa.us 320 :GNC 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 : : }
|