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