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