1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
|
/*-------------------------------------------------------------------------
*
* json_io.c
* Primary input/output and conversion procedures
* for JSON data type.
*
* Copyright (c) 2010, PostgreSQL Global Development Group
* Written by Joey Adams <[email protected]>.
*
*-------------------------------------------------------------------------
*/
#include "json.h"
#include "util.h"
#include "utils/array.h"
#include "mb/pg_wchar.h"
PG_MODULE_MAGIC;
static json_type decide_json_type(Oid type, char category);
static const char *datum_to_json(Datum datum, TypeInfo *typeInfo,
json_type target_type);
static const char *array_to_json(Datum datum);
static const char *build_array_string(const char **values,
const int *dim, int ndim);
static void build_array_string_recurse(StringInfo string, const char ***values,
const int *dim, int ndim);
void
report_corrupt_json(void)
{
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("corrupt JSON content")));
}
PG_FUNCTION_INFO_V1(json_in);
Datum json_in(PG_FUNCTION_ARGS);
Datum
json_in(PG_FUNCTION_ARGS)
{
char *string = PG_GETARG_CSTRING(0);
jsontype *vardata;
if (!json_validate_server_encoded(string))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("could not parse JSON value")));
vardata = cstring_to_text(string);
PG_RETURN_JSON_P(vardata);
}
PG_FUNCTION_INFO_V1(json_out);
Datum json_out(PG_FUNCTION_ARGS);
Datum
json_out(PG_FUNCTION_ARGS)
{
jsontype *vardata = PG_GETARG_JSON_P(0);
char *string = text_to_cstring((text *) vardata);
Assert(json_validate_server_encoded(string));
PG_RETURN_CSTRING(string);
}
/*
* Performs JSON value extraction in UTF-8 C-String land.
*
* If the input was JSON-encoded NULL, this function returns NULL
* (indicating that we want from_json to yield actual SQL NULL).
*/
static const char *
from_json_cstring(const char *input)
{
size_t len;
JSON *json = json_decode(input);
const char *cstring_out = NULL;
if (json == NULL)
report_corrupt_json();
switch (json->type)
{
case JSON_NULL:
cstring_out = NULL;
break;
case JSON_STRING:
cstring_out = json_get_string(json, &len);
if (strlen(cstring_out) != len)
ereport(ERROR,
(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
errmsg("could not convert JSON-encoded string to TEXT: contains \\u0000"),
errdetail("the string was: %s", utf8_to_server(input, strlen(input)))));
/*
* todo: suggest using from_json_as_bytea instead when that is
* available.
*/
break;
case JSON_NUMBER:
cstring_out = json_get_number(json);
break;
case JSON_BOOL:
cstring_out = json_get_bool(json) ? "true" : "false";
break;
case JSON_OBJECT:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("from_json cannot be applied to JSON objects")));
break;
case JSON_ARRAY:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("from_json cannot be applied to JSON arrays")));
break;
default:
Assert(false);
}
return cstring_out;
}
PG_FUNCTION_INFO_V1(from_json);
Datum from_json(PG_FUNCTION_ARGS);
Datum
from_json(PG_FUNCTION_ARGS)
{
char *cstring_in = text_to_utf8_cstring(PG_GETARG_JSON_P(0));
const char *cstring_out = from_json_cstring(cstring_in);
text *vardata_out;
pfree(cstring_in);
if (cstring_out != NULL)
{
vardata_out = utf8_cstring_to_text(cstring_out);
PG_RETURN_TEXT_P(vardata_out);
}
else
{
PG_RETURN_NULL();
}
}
PG_FUNCTION_INFO_V1(to_json);
Datum to_json(PG_FUNCTION_ARGS);
Datum
to_json(PG_FUNCTION_ARGS)
{
Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
TypeInfo *typeInfo;
json_type target_type;
typeInfo = fcinfo->flinfo->fn_extra;
if (typeInfo == NULL)
{
typeInfo = fcinfo->flinfo->fn_extra =
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(TypeInfo));
getTypeInfo(typeInfo, argtype, IOFunc_output, fcinfo->flinfo->fn_mcxt);
}
else if (typeInfo->type != argtype)
{
getTypeInfo(typeInfo, argtype, IOFunc_output, fcinfo->flinfo->fn_mcxt);
}
target_type = decide_json_type(argtype, typeInfo->typcategory);
/*
* If NULL argument is given, return the string "null", not actual NULL.
* The null check is done after the type check so users can identify type
* bugs earlier.
*/
if (PG_ARGISNULL(0))
PG_RETURN_JSON_P(cstring_to_text("null"));
PG_RETURN_JSON_P(utf8_cstring_to_text(
datum_to_json(PG_GETARG_DATUM(0), typeInfo, target_type)));
}
/*
* decide_json_type
*
* Given a type and its corresponding typcategory, determine what
* JSON type to encode it to. If it's not possible to encode the type
* (or if support for the type isn't implemented yet), report an error.
*/
static json_type
decide_json_type(Oid type, char category)
{
switch (category)
{
case 'S':
return JSON_STRING;
case 'P':
if (type == CSTRINGOID)
return JSON_STRING;
goto invalid;
case 'N':
if (type == CASHOID)
goto invalid;
return JSON_NUMBER;
case 'B':
if (type == BOOLOID)
return JSON_BOOL;
goto invalid;
case 'A':
return JSON_ARRAY;
default:
goto invalid;
}
invalid:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("to_json cannot convert %s to JSON", format_type_be(type))));
return JSON_INVALID;
}
/*
* datum_to_json
* Converts a datum to a UTF-8-encoded JSON string.
*
* typeInfo comes from the getTypeInfo function, and
* target_type comes from decide_json_type .
*
* See to_json and array_to_json for examples of
* how to invoke this function.
*/
static const char *
datum_to_json(Datum datum, TypeInfo *typeInfo, json_type target_type)
{
char *cstring;
char *cstring_utf8;
JSON *node;
char *encoded_utf8;
switch (target_type)
{
case JSON_STRING:
case JSON_NUMBER:
cstring = OutputFunctionCall(&typeInfo->proc, datum);
cstring_utf8 = server_to_utf8(cstring, strlen(cstring));
if (cstring != cstring_utf8)
pfree(cstring);
if (target_type == JSON_STRING)
node = json_mkstring(cstring_utf8, strlen(cstring_utf8));
else
node = json_mknumber(cstring_utf8, strlen(cstring_utf8));
encoded_utf8 = json_encode(node, 0);
if (encoded_utf8 == NULL)
{
/*
* Currently, we assume that output conversion for all types
* in category 'N' (excluding CASH) will, by coincidence,
* always give a valid JSON-encoded number.
*
* If this error message appears, it means the assumption is
* incorrect, and we need to either blacklist the type in
* decide_json_type (like we did with CASH), or add code that
* can parse the number type.
*/
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("to_json failed to convert %s \"%s\" to a valid JSON %s.\n",
format_type_be(typeInfo->type),
cstring,
target_type == JSON_STRING ? "string" : "number")));
}
return encoded_utf8;
case JSON_BOOL:
return DatumGetBool(datum) ? "true" : "false";
case JSON_ARRAY:
return array_to_json(datum);
default:
Assert(false);
return NULL;
}
}
/*
* array_to_json
* Converts a PostgreSQL array datum to a UTF-8-encoded JSON string.
*
* Note: We assume that any type with typcategory = 'A'
* is compatible with array_out.
*/
static const char *
array_to_json(Datum datum)
{
ArrayType *v = DatumGetArrayTypeP(datum);
Oid element_type = ARR_ELEMTYPE(v);
TypeInfo element_typeinfo;
json_type target_type;
int *dim;
int ndim;
int nitems;
int i;
char *s;
bits8 *bitmap;
int bitmask;
Datum elt;
const char **values;
const char *ret;
/*
* todo: Consider caching the TypeInfo of array items (which we're
* computing now) in the fcinfo->flinfo->fn_mcxt of to_json like we do
* with the TypeInfo of the array itself.
*/
getTypeInfo(&element_typeinfo, element_type, IOFunc_output, CurrentMemoryContext);
target_type = decide_json_type(element_type, element_typeinfo.typcategory);
ndim = ARR_NDIM(v);
dim = ARR_DIMS(v);
nitems = ArrayGetNItems(ndim, dim);
/*
* Unfortunately, we can't make SELECT
* to_json(ARRAY[[],[],[]]::integer[][]); yield '[[],[],[]]' because ndim
* is set to 0 when the array has no items.
*/
if (nitems <= 0)
return "[]";
values = palloc(nitems * sizeof(const char *));
s = ARR_DATA_PTR(v);
bitmap = ARR_NULLBITMAP(v);
bitmask = 1;
for (i = 0; i < nitems; i++)
{
if (bitmap != NULL && (*bitmap & bitmask) == 0)
{
values[i] = NULL;
}
else
{
elt = fetch_att(s, element_typeinfo.typbyval, element_typeinfo.typlen);
s = att_addlength_datum(s, element_typeinfo.typlen, elt);
s = (char *) att_align_nominal(s, element_typeinfo.typalign);
values[i] = datum_to_json(elt, &element_typeinfo, target_type);
}
if (bitmap != NULL)
{
bitmask <<= 1;
if (bitmask == 0x100)
{
bitmap++;
bitmask = 1;
}
}
}
ret = build_array_string(values, dim, ndim);
pfree(values);
return ret;
}
/*
* build_array_string
* Takes a multidimensional array of (JSON-formatted) strings and
* converts it to JSON by wrapping the strings in array brackets and commas.
*/
static const char *
build_array_string(const char **values, const int *dim, int ndim)
{
StringInfoData string[1];
initStringInfo(string);
build_array_string_recurse(string, &values, dim, ndim);
return string->data;
}
static void
build_array_string_recurse(StringInfo string, const char ***values,
const int *dim, int ndim)
{
int i,
count = dim[0];
appendStringInfoChar(string, '[');
for (i = 0; i < count; i++)
{
if (ndim > 1)
{
build_array_string_recurse(string, values, dim + 1, ndim - 1);
}
else
{
const char *value = *(*values)++;
if (value != NULL)
appendStringInfoString(string, value);
else
appendStringInfoString(string, "null");
}
if (i + 1 < count)
appendStringInfoChar(string, ',');
}
appendStringInfoChar(string, ']');
}
|