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 : :
164 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 JsonbValue *SV_to_JsonbValue(SV *obj, JsonbParseState **ps, bool is_elem);
17 : :
18 : :
19 : : static SV *
2713 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:
2637 tgl@sss.pgh.pa.us 27 : 6 : return Jsonb_to_SV(jbv->val.binary.data);
28 : :
2713 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 : :
2713 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 *
2713 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)
2713 peter_e@gmx.net 82 [ # # ]:UBC 0 : elog(ERROR, "unexpected jsonb token: %d", r);
83 : :
2637 tgl@sss.pgh.pa.us 84 :CBC 19 : return JsonbValue_to_SV(&v);
85 : : }
86 : : else
87 : : {
2713 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 : :
2637 tgl@sss.pgh.pa.us 96 : 20 : return newRV((SV *) av);
97 : : }
98 : :
2713 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 : :
2637 tgl@sss.pgh.pa.us 121 : 18 : return newRV((SV *) hv);
122 : : }
123 : :
2713 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 JsonbValue *
2713 peter_e@gmx.net 131 :CBC 22 : AV_to_JsonbValue(AV *in, JsonbParseState **jsonb_state)
132 : : {
133 : 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)
144 : 48 : (void) SV_to_JsonbValue(*value, jsonb_state, true);
145 : : }
146 : :
147 : 22 : return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
148 : : }
149 : :
150 : : static JsonbValue *
151 : 28 : HV_to_JsonbValue(HV *obj, JsonbParseState **jsonb_state)
152 : : {
153 : 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);
170 : 36 : (void) SV_to_JsonbValue(val, jsonb_state, false);
171 : : }
172 : :
173 : 28 : return pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
174 : : }
175 : :
176 : : static JsonbValue *
177 : 147 : SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem)
178 : : {
179 : 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:
189 : 22 : return AV_to_JsonbValue((AV *) in, jsonb_state);
190 : :
191 : 28 : case SVt_PVHV:
192 : 28 : return HV_to_JsonbValue((HV *) in, jsonb_state);
193 : :
2712 tgl@sss.pgh.pa.us 194 : 97 : default:
2225 195 [ + + ]: 97 : if (!SvOK(in))
196 : : {
197 : 12 : out.type = jbvNull;
198 : : }
199 [ + + ]: 85 : else if (SvUOK(in))
200 : : {
201 : : /*
202 : : * If UV is >=64 bits, we have no better way to make this
203 : : * happen than converting to text and back. Given the low
204 : : * usage of UV in Perl code, it's not clear it's worth working
205 : : * hard to provide alternate code paths.
206 : : */
2637 207 : 2 : const char *strval = SvPV_nolen(in);
208 : :
209 : 2 : out.type = jbvNumeric;
210 : 2 : out.val.numeric =
211 : 2 : DatumGetNumeric(DirectFunctionCall3(numeric_in,
212 : : CStringGetDatum(strval),
213 : : ObjectIdGetDatum(InvalidOid),
214 : : Int32GetDatum(-1)));
215 : : }
216 [ + + ]: 83 : else if (SvIOK(in))
217 : : {
2712 218 : 10 : IV ival = SvIV(in);
219 : :
220 : 10 : out.type = jbvNumeric;
1823 peter@eisentraut.org 221 : 10 : out.val.numeric = int64_to_numeric(ival);
222 : : }
2712 tgl@sss.pgh.pa.us 223 [ + + ]: 73 : else if (SvNOK(in))
224 : : {
225 : 53 : double nval = SvNV(in);
226 : :
227 : : /*
228 : : * jsonb doesn't allow infinity or NaN (per JSON
229 : : * specification), but the numeric type that is used for the
230 : : * storage accepts those, so we have to reject them here
231 : : * explicitly.
232 : : */
233 [ + + ]: 53 : if (isinf(nval))
2713 peter_e@gmx.net 234 [ + - ]: 1 : ereport(ERROR,
235 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
236 : : errmsg("cannot convert infinity to jsonb")));
2686 237 [ - + ]: 52 : if (isnan(nval))
2686 peter_e@gmx.net 238 [ # # ]:UBC 0 : ereport(ERROR,
239 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
240 : : errmsg("cannot convert NaN to jsonb")));
241 : :
2713 peter_e@gmx.net 242 :CBC 52 : out.type = jbvNumeric;
2712 tgl@sss.pgh.pa.us 243 : 52 : out.val.numeric =
244 : 52 : DatumGetNumeric(DirectFunctionCall1(float8_numeric,
245 : : Float8GetDatum(nval)));
246 : : }
247 [ + - ]: 20 : else if (SvPOK(in))
248 : : {
249 : 20 : out.type = jbvString;
250 : 20 : out.val.string.val = sv2cstr(in);
251 : 20 : out.val.string.len = strlen(out.val.string.val);
252 : : }
253 : : else
254 : : {
255 : : /*
256 : : * XXX It might be nice if we could include the Perl type in
257 : : * the error message.
258 : : */
2712 tgl@sss.pgh.pa.us 259 [ # # ]:UBC 0 : ereport(ERROR,
260 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
261 : : errmsg("cannot transform this Perl type to jsonb")));
262 : : return NULL;
263 : : }
264 : : }
265 : :
266 : : /* Push result into 'jsonb_state' unless it is a raw scalar. */
2713 peter_e@gmx.net 267 :CBC 96 : return *jsonb_state
268 [ + + ]: 74 : ? pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, &out)
269 [ + + ]: 170 : : memcpy(palloc(sizeof(JsonbValue)), &out, sizeof(JsonbValue));
270 : : }
271 : :
272 : :
273 : 4 : PG_FUNCTION_INFO_V1(jsonb_to_plperl);
274 : :
275 : : Datum
276 : 51 : jsonb_to_plperl(PG_FUNCTION_ARGS)
277 : : {
278 : 51 : dTHX;
279 : 51 : Jsonb *in = PG_GETARG_JSONB_P(0);
280 : 51 : SV *sv = Jsonb_to_SV(&in->root);
281 : :
2637 tgl@sss.pgh.pa.us 282 : 51 : return PointerGetDatum(sv);
283 : : }
284 : :
285 : :
2713 peter_e@gmx.net 286 : 4 : PG_FUNCTION_INFO_V1(plperl_to_jsonb);
287 : :
288 : : Datum
289 : 63 : plperl_to_jsonb(PG_FUNCTION_ARGS)
290 : : {
291 : 63 : dTHX;
292 : 63 : JsonbParseState *jsonb_state = NULL;
293 : 63 : SV *in = (SV *) PG_GETARG_POINTER(0);
294 : 63 : JsonbValue *out = SV_to_JsonbValue(in, &jsonb_state, true);
295 : 62 : Jsonb *result = JsonbValueToJsonb(out);
296 : :
297 : 62 : PG_RETURN_JSONB_P(result);
298 : : }
|