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