Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * json.c
4 : : * JSON data type support.
5 : : *
6 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/utils/adt/json.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : : #include "postgres.h"
15 : :
16 : : #include "catalog/pg_proc.h"
17 : : #include "catalog/pg_type.h"
18 : : #include "common/hashfn.h"
19 : : #include "funcapi.h"
20 : : #include "libpq/pqformat.h"
21 : : #include "miscadmin.h"
22 : : #include "utils/array.h"
23 : : #include "utils/builtins.h"
24 : : #include "utils/date.h"
25 : : #include "utils/datetime.h"
26 : : #include "utils/json.h"
27 : : #include "utils/jsonfuncs.h"
28 : : #include "utils/lsyscache.h"
29 : : #include "utils/typcache.h"
30 : :
31 : :
32 : : /*
33 : : * Support for fast key uniqueness checking.
34 : : *
35 : : * We maintain a hash table of used keys in JSON objects for fast detection
36 : : * of duplicates.
37 : : */
38 : : /* Common context for key uniqueness check */
39 : : typedef struct HTAB *JsonUniqueCheckState; /* hash table for key names */
40 : :
41 : : /* Hash entry for JsonUniqueCheckState */
42 : : typedef struct JsonUniqueHashEntry
43 : : {
44 : : const char *key;
45 : : int key_len;
46 : : int object_id;
47 : : } JsonUniqueHashEntry;
48 : :
49 : : /* Stack element for key uniqueness check during JSON parsing */
50 : : typedef struct JsonUniqueStackEntry
51 : : {
52 : : struct JsonUniqueStackEntry *parent;
53 : : int object_id;
54 : : } JsonUniqueStackEntry;
55 : :
56 : : /* Context struct for key uniqueness check during JSON parsing */
57 : : typedef struct JsonUniqueParsingState
58 : : {
59 : : JsonLexContext *lex;
60 : : JsonUniqueCheckState check;
61 : : JsonUniqueStackEntry *stack;
62 : : int id_counter;
63 : : bool unique;
64 : : } JsonUniqueParsingState;
65 : :
66 : : /* Context struct for key uniqueness check during JSON building */
67 : : typedef struct JsonUniqueBuilderState
68 : : {
69 : : JsonUniqueCheckState check; /* unique check */
70 : : StringInfoData skipped_keys; /* skipped keys with NULL values */
71 : : MemoryContext mcxt; /* context for saving skipped keys */
72 : : } JsonUniqueBuilderState;
73 : :
74 : :
75 : : /* State struct for JSON aggregation */
76 : : typedef struct JsonAggState
77 : : {
78 : : StringInfo str;
79 : : JsonTypeCategory key_category;
80 : : Oid key_output_func;
81 : : JsonTypeCategory val_category;
82 : : Oid val_output_func;
83 : : JsonUniqueBuilderState unique_check;
84 : : } JsonAggState;
85 : :
86 : : static void composite_to_json(Datum composite, StringInfo result,
87 : : bool use_line_feeds);
88 : : static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
89 : : Datum *vals, bool *nulls, int *valcount,
90 : : JsonTypeCategory tcategory, Oid outfuncoid,
91 : : bool use_line_feeds);
92 : : static void array_to_json_internal(Datum array, StringInfo result,
93 : : bool use_line_feeds);
94 : : static void datum_to_json_internal(Datum val, bool is_null, StringInfo result,
95 : : JsonTypeCategory tcategory, Oid outfuncoid,
96 : : bool key_scalar);
97 : : static void add_json(Datum val, bool is_null, StringInfo result,
98 : : Oid val_type, bool key_scalar);
99 : : static text *catenate_stringinfo_string(StringInfo buffer, const char *addon);
100 : :
101 : : /*
102 : : * Input.
103 : : */
104 : : Datum
4457 rhaas@postgresql.org 105 :CBC 2810 : json_in(PG_FUNCTION_ARGS)
106 : : {
4034 andrew@dunslane.net 107 : 2810 : char *json = PG_GETARG_CSTRING(0);
108 : 2810 : text *result = cstring_to_text(json);
109 : : JsonLexContext lex;
110 : :
111 : : /* validate it */
192 alvherre@alvh.no-ip. 112 :GNC 2810 : makeJsonLexContext(&lex, result, false);
113 [ + + ]: 2810 : if (!pg_parse_json_or_errsave(&lex, &nullSemAction, fcinfo->context))
490 tgl@sss.pgh.pa.us 114 :CBC 6 : PG_RETURN_NULL();
115 : :
116 : : /* Internal representation is the same as text */
4034 andrew@dunslane.net 117 : 2708 : PG_RETURN_TEXT_P(result);
118 : : }
119 : :
120 : : /*
121 : : * Output.
122 : : */
123 : : Datum
4457 rhaas@postgresql.org 124 : 2299 : json_out(PG_FUNCTION_ARGS)
125 : : {
126 : : /* we needn't detoast because text_to_cstring will handle that */
4326 bruce@momjian.us 127 : 2299 : Datum txt = PG_GETARG_DATUM(0);
128 : :
4457 rhaas@postgresql.org 129 : 2299 : PG_RETURN_CSTRING(TextDatumGetCString(txt));
130 : : }
131 : :
132 : : /*
133 : : * Binary send.
134 : : */
135 : : Datum
4457 rhaas@postgresql.org 136 :UBC 0 : json_send(PG_FUNCTION_ARGS)
137 : : {
4326 bruce@momjian.us 138 : 0 : text *t = PG_GETARG_TEXT_PP(0);
139 : : StringInfoData buf;
140 : :
4457 rhaas@postgresql.org 141 : 0 : pq_begintypsend(&buf);
142 [ # # # # : 0 : pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
# # # # #
# # # ]
143 : 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
144 : : }
145 : :
146 : : /*
147 : : * Binary receive.
148 : : */
149 : : Datum
150 : 0 : json_recv(PG_FUNCTION_ARGS)
151 : : {
152 : 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
153 : : char *str;
154 : : int nbytes;
155 : : JsonLexContext lex;
156 : :
157 : 0 : str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
158 : :
159 : : /* Validate it. */
192 alvherre@alvh.no-ip. 160 :UNC 0 : makeJsonLexContextCstringLen(&lex, str, nbytes, GetDatabaseEncoding(),
161 : : false);
162 : 0 : pg_parse_json_or_ereport(&lex, &nullSemAction);
163 : :
3675 andrew@dunslane.net 164 :UBC 0 : PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes));
165 : : }
166 : :
167 : : /*
168 : : * Turn a Datum into JSON text, appending the string to "result".
169 : : *
170 : : * tcategory and outfuncoid are from a previous call to json_categorize_type,
171 : : * except that if is_null is true then they can be invalid.
172 : : *
173 : : * If key_scalar is true, the value is being printed as a key, so insist
174 : : * it's of an acceptable type, and force it to be quoted.
175 : : */
176 : : static void
268 amitlan@postgresql.o 177 :GNC 2775 : datum_to_json_internal(Datum val, bool is_null, StringInfo result,
178 : : JsonTypeCategory tcategory, Oid outfuncoid,
179 : : bool key_scalar)
180 : : {
181 : : char *outputstr;
182 : : text *jsontext;
183 : :
3114 noah@leadboat.com 184 :CBC 2775 : check_stack_depth();
185 : :
186 : : /* callers are expected to ensure that null keys are not passed in */
3421 tgl@sss.pgh.pa.us 187 [ + + - + ]: 2775 : Assert(!(key_scalar && is_null));
188 : :
4434 andrew@dunslane.net 189 [ + + ]: 2775 : if (is_null)
190 : : {
118 nathan@postgresql.or 191 :GNC 156 : appendBinaryStringInfo(result, "null", strlen("null"));
4454 andrew@dunslane.net 192 :CBC 156 : return;
193 : : }
194 : :
3628 tgl@sss.pgh.pa.us 195 [ + + + + ]: 2619 : if (key_scalar &&
196 [ + + ]: 340 : (tcategory == JSONTYPE_ARRAY ||
197 [ + + ]: 337 : tcategory == JSONTYPE_COMPOSITE ||
198 [ - + ]: 331 : tcategory == JSONTYPE_JSON ||
199 : : tcategory == JSONTYPE_CAST))
200 [ + - ]: 18 : ereport(ERROR,
201 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
202 : : errmsg("key value must be scalar, not array, composite, or json")));
203 : :
4454 andrew@dunslane.net 204 [ + + + + : 2601 : switch (tcategory)
+ + + + +
+ ]
205 : : {
3628 tgl@sss.pgh.pa.us 206 : 162 : case JSONTYPE_ARRAY:
4454 andrew@dunslane.net 207 : 162 : array_to_json_internal(val, result, false);
208 : 162 : break;
3628 tgl@sss.pgh.pa.us 209 : 250 : case JSONTYPE_COMPOSITE:
3485 sfrost@snowman.net 210 : 250 : composite_to_json(val, result, false);
4454 andrew@dunslane.net 211 : 250 : break;
3628 tgl@sss.pgh.pa.us 212 : 16 : case JSONTYPE_BOOL:
213 [ - + ]: 16 : if (key_scalar)
118 nathan@postgresql.or 214 :UNC 0 : appendStringInfoChar(result, '"');
118 nathan@postgresql.or 215 [ + + ]:GNC 16 : if (DatumGetBool(val))
216 : 10 : appendBinaryStringInfo(result, "true", strlen("true"));
217 : : else
218 : 6 : appendBinaryStringInfo(result, "false", strlen("false"));
219 [ - + ]: 16 : if (key_scalar)
118 nathan@postgresql.or 220 :UNC 0 : appendStringInfoChar(result, '"');
4454 andrew@dunslane.net 221 :CBC 16 : break;
3628 tgl@sss.pgh.pa.us 222 : 1590 : case JSONTYPE_NUMERIC:
223 : 1590 : outputstr = OidOutputFunctionCall(outfuncoid, val);
224 : :
225 : : /*
226 : : * Don't quote a non-key if it's a valid JSON number (i.e., not
227 : : * "Infinity", "-Infinity", or "NaN"). Since we know this is a
228 : : * numeric data type's output, we simplify and open-code the
229 : : * validation for better performance.
230 : : */
128 nathan@postgresql.or 231 [ + + ]:GNC 1590 : if (!key_scalar &&
232 [ + + + + ]: 1455 : ((*outputstr >= '0' && *outputstr <= '9') ||
233 [ + + ]: 33 : (*outputstr == '-' &&
234 [ + - + + ]: 27 : (outputstr[1] >= '0' && outputstr[1] <= '9'))))
3422 andrew@dunslane.net 235 :CBC 1446 : appendStringInfoString(result, outputstr);
236 : : else
237 : : {
128 nathan@postgresql.or 238 :GNC 144 : appendStringInfoChar(result, '"');
239 : 144 : appendStringInfoString(result, outputstr);
240 : 144 : appendStringInfoChar(result, '"');
241 : : }
4437 andrew@dunslane.net 242 :CBC 1590 : pfree(outputstr);
243 : 1590 : break;
3528 tgl@sss.pgh.pa.us 244 : 12 : case JSONTYPE_DATE:
245 : : {
246 : : char buf[MAXDATELEN + 1];
247 : :
1663 akorotkov@postgresql 248 : 12 : JsonEncodeDateTime(buf, val, DATEOID, NULL);
128 nathan@postgresql.or 249 :GNC 12 : appendStringInfoChar(result, '"');
250 : 12 : appendStringInfoString(result, buf);
251 : 12 : appendStringInfoChar(result, '"');
252 : : }
2280 andrew@dunslane.net 253 :CBC 12 : break;
254 : 13 : case JSONTYPE_TIMESTAMP:
255 : : {
256 : : char buf[MAXDATELEN + 1];
257 : :
1663 akorotkov@postgresql 258 : 13 : JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
128 nathan@postgresql.or 259 :GNC 13 : appendStringInfoChar(result, '"');
260 : 13 : appendStringInfoString(result, buf);
261 : 13 : appendStringInfoChar(result, '"');
262 : : }
2280 andrew@dunslane.net 263 :CBC 13 : break;
264 : 12 : case JSONTYPE_TIMESTAMPTZ:
265 : : {
266 : : char buf[MAXDATELEN + 1];
267 : :
1663 akorotkov@postgresql 268 : 12 : JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
128 nathan@postgresql.or 269 :GNC 12 : appendStringInfoChar(result, '"');
270 : 12 : appendStringInfoString(result, buf);
271 : 12 : appendStringInfoChar(result, '"');
272 : : }
2280 andrew@dunslane.net 273 :CBC 12 : break;
274 : 93 : case JSONTYPE_JSON:
275 : : /* JSON and JSONB output will already be escaped */
276 : 93 : outputstr = OidOutputFunctionCall(outfuncoid, val);
277 : 93 : appendStringInfoString(result, outputstr);
278 : 93 : pfree(outputstr);
279 : 93 : break;
280 : 2 : case JSONTYPE_CAST:
281 : : /* outfuncoid refers to a cast function, not an output function */
282 : 2 : jsontext = DatumGetTextPP(OidFunctionCall1(outfuncoid, val));
118 nathan@postgresql.or 283 [ - + ]:GNC 2 : appendBinaryStringInfo(result, VARDATA_ANY(jsontext),
284 [ - + - - : 2 : VARSIZE_ANY_EXHDR(jsontext));
- - - - -
+ ]
2280 andrew@dunslane.net 285 :CBC 2 : pfree(jsontext);
286 : 2 : break;
287 : 451 : default:
288 : 451 : outputstr = OidOutputFunctionCall(outfuncoid, val);
289 : 451 : escape_json(result, outputstr);
290 : 451 : pfree(outputstr);
291 : 451 : break;
292 : : }
293 : : }
294 : :
295 : : /*
296 : : * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
297 : : * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone
298 : : * offset (in seconds) in which we want to show timestamptz.
299 : : */
300 : : char *
1663 akorotkov@postgresql 301 : 835 : JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
302 : : {
2280 andrew@dunslane.net 303 [ + + ]: 835 : if (!buf)
304 : 30 : buf = palloc(MAXDATELEN + 1);
305 : :
306 [ + + + + : 835 : switch (typid)
+ - ]
307 : : {
308 : 117 : case DATEOID:
309 : : {
310 : : DateADT date;
311 : : struct pg_tm tm;
312 : :
313 : 117 : date = DatumGetDateADT(value);
314 : :
315 : : /* Same as date_out(), but forcing DateStyle */
3528 tgl@sss.pgh.pa.us 316 [ + + + + ]: 117 : if (DATE_NOT_FINITE(date))
3099 317 : 12 : EncodeSpecialDate(date, buf);
318 : : else
319 : : {
3528 320 : 105 : j2date(date + POSTGRES_EPOCH_JDATE,
321 : : &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
322 : 105 : EncodeDateOnly(&tm, USE_XSD_DATES, buf);
323 : : }
324 : : }
325 : 117 : break;
2280 andrew@dunslane.net 326 : 132 : case TIMEOID:
327 : : {
328 : 132 : TimeADT time = DatumGetTimeADT(value);
329 : : struct pg_tm tt,
330 : 132 : *tm = &tt;
331 : : fsec_t fsec;
332 : :
333 : : /* Same as time_out(), but forcing DateStyle */
334 : 132 : time2tm(time, tm, &fsec);
335 : 132 : EncodeTimeOnly(tm, fsec, false, 0, USE_XSD_DATES, buf);
336 : : }
337 : 132 : break;
338 : 165 : case TIMETZOID:
339 : : {
340 : 165 : TimeTzADT *time = DatumGetTimeTzADTP(value);
341 : : struct pg_tm tt,
342 : 165 : *tm = &tt;
343 : : fsec_t fsec;
344 : : int tz;
345 : :
346 : : /* Same as timetz_out(), but forcing DateStyle */
347 : 165 : timetz2tm(time, tm, &fsec, &tz);
348 : 165 : EncodeTimeOnly(tm, fsec, true, tz, USE_XSD_DATES, buf);
349 : : }
350 : 165 : break;
351 : 175 : case TIMESTAMPOID:
352 : : {
353 : : Timestamp timestamp;
354 : : struct pg_tm tm;
355 : : fsec_t fsec;
356 : :
357 : 175 : timestamp = DatumGetTimestamp(value);
358 : : /* Same as timestamp_out(), but forcing DateStyle */
3603 359 [ + + + + ]: 175 : if (TIMESTAMP_NOT_FINITE(timestamp))
3099 tgl@sss.pgh.pa.us 360 : 12 : EncodeSpecialTimestamp(timestamp, buf);
3603 andrew@dunslane.net 361 [ + - ]: 163 : else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0)
362 : 163 : EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf);
363 : : else
3603 andrew@dunslane.net 364 [ # # ]:UBC 0 : ereport(ERROR,
365 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
366 : : errmsg("timestamp out of range")));
367 : : }
3603 andrew@dunslane.net 368 :CBC 175 : break;
2280 369 : 246 : case TIMESTAMPTZOID:
370 : : {
371 : : TimestampTz timestamp;
372 : : struct pg_tm tm;
373 : : int tz;
374 : : fsec_t fsec;
3603 375 : 246 : const char *tzn = NULL;
376 : :
2280 377 : 246 : timestamp = DatumGetTimestampTz(value);
378 : :
379 : : /*
380 : : * If a time zone is specified, we apply the time-zone shift,
381 : : * convert timestamptz to pg_tm as if it were without a time
382 : : * zone, and then use the specified time zone for converting
383 : : * the timestamp into a string.
384 : : */
1663 akorotkov@postgresql 385 [ + + ]: 246 : if (tzp)
386 : : {
387 : 222 : tz = *tzp;
388 : 222 : timestamp -= (TimestampTz) tz * USECS_PER_SEC;
389 : : }
390 : :
391 : : /* Same as timestamptz_out(), but forcing DateStyle */
3603 andrew@dunslane.net 392 [ + + + + ]: 246 : if (TIMESTAMP_NOT_FINITE(timestamp))
3099 tgl@sss.pgh.pa.us 393 : 12 : EncodeSpecialTimestamp(timestamp, buf);
1663 akorotkov@postgresql 394 [ + + + + : 234 : else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+ - ]
395 : : tzp ? NULL : &tzn, NULL) == 0)
396 : : {
397 [ + + ]: 234 : if (tzp)
398 : 222 : tm.tm_isdst = 1; /* set time-zone presence flag */
399 : :
3603 andrew@dunslane.net 400 : 234 : EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
401 : : }
402 : : else
3603 andrew@dunslane.net 403 [ # # ]:UBC 0 : ereport(ERROR,
404 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
405 : : errmsg("timestamp out of range")));
406 : : }
3603 andrew@dunslane.net 407 :CBC 246 : break;
4454 andrew@dunslane.net 408 :UBC 0 : default:
1355 michael@paquier.xyz 409 [ # # ]: 0 : elog(ERROR, "unknown jsonb value datetime type oid %u", typid);
410 : : return NULL;
411 : : }
412 : :
2280 andrew@dunslane.net 413 :CBC 835 : return buf;
414 : : }
415 : :
416 : : /*
417 : : * Process a single dimension of an array.
418 : : * If it's the innermost dimension, output the values, otherwise call
419 : : * ourselves recursively to process the next dimension.
420 : : */
421 : : static void
4326 bruce@momjian.us 422 : 189 : array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals,
423 : : bool *nulls, int *valcount, JsonTypeCategory tcategory,
424 : : Oid outfuncoid, bool use_line_feeds)
425 : : {
426 : : int i;
427 : : const char *sep;
428 : :
4454 andrew@dunslane.net 429 [ - + ]: 189 : Assert(dim < ndims);
430 : :
431 [ + + ]: 189 : sep = use_line_feeds ? ",\n " : ",";
432 : :
433 : 189 : appendStringInfoChar(result, '[');
434 : :
435 [ + + ]: 705 : for (i = 1; i <= dims[dim]; i++)
436 : : {
437 [ + + ]: 516 : if (i > 1)
4326 bruce@momjian.us 438 : 327 : appendStringInfoString(result, sep);
439 : :
4454 andrew@dunslane.net 440 [ + + ]: 516 : if (dim + 1 == ndims)
441 : : {
268 amitlan@postgresql.o 442 :GNC 510 : datum_to_json_internal(vals[*valcount], nulls[*valcount],
443 : : result, tcategory,
444 : : outfuncoid, false);
4454 andrew@dunslane.net 445 :CBC 510 : (*valcount)++;
446 : : }
447 : : else
448 : : {
449 : : /*
450 : : * Do we want line feeds on inner dimensions of arrays? For now
451 : : * we'll say no.
452 : : */
4326 bruce@momjian.us 453 : 6 : array_dim_to_json(result, dim + 1, ndims, dims, vals, nulls,
454 : : valcount, tcategory, outfuncoid, false);
455 : : }
456 : : }
457 : :
4454 andrew@dunslane.net 458 : 189 : appendStringInfoChar(result, ']');
459 : 189 : }
460 : :
461 : : /*
462 : : * Turn an array into JSON.
463 : : */
464 : : static void
465 : 183 : array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
466 : : {
467 : 183 : ArrayType *v = DatumGetArrayTypeP(array);
468 : 183 : Oid element_type = ARR_ELEMTYPE(v);
469 : : int *dim;
470 : : int ndim;
471 : : int nitems;
4326 bruce@momjian.us 472 : 183 : int count = 0;
473 : : Datum *elements;
474 : : bool *nulls;
475 : : int16 typlen;
476 : : bool typbyval;
477 : : char typalign;
478 : : JsonTypeCategory tcategory;
479 : : Oid outfuncoid;
480 : :
4454 andrew@dunslane.net 481 : 183 : ndim = ARR_NDIM(v);
482 : 183 : dim = ARR_DIMS(v);
483 : 183 : nitems = ArrayGetNItems(ndim, dim);
484 : :
485 [ - + ]: 183 : if (nitems <= 0)
486 : : {
4326 bruce@momjian.us 487 :UBC 0 : appendStringInfoString(result, "[]");
4454 andrew@dunslane.net 488 : 0 : return;
489 : : }
490 : :
3628 tgl@sss.pgh.pa.us 491 :CBC 183 : get_typlenbyvalalign(element_type,
492 : : &typlen, &typbyval, &typalign);
493 : :
269 amitlan@postgresql.o 494 :GNC 183 : json_categorize_type(element_type, false,
495 : : &tcategory, &outfuncoid);
496 : :
4454 andrew@dunslane.net 497 :CBC 183 : deconstruct_array(v, element_type, typlen, typbyval,
498 : : typalign, &elements, &nulls,
499 : : &nitems);
500 : :
4434 501 : 183 : array_dim_to_json(result, 0, ndim, dim, elements, nulls, &count, tcategory,
502 : : outfuncoid, use_line_feeds);
503 : :
4454 504 : 183 : pfree(elements);
505 : 183 : pfree(nulls);
506 : : }
507 : :
508 : : /*
509 : : * Turn a composite / record into JSON.
510 : : */
511 : : static void
3485 sfrost@snowman.net 512 : 514 : composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
513 : : {
514 : : HeapTupleHeader td;
515 : : Oid tupType;
516 : : int32 tupTypmod;
517 : : TupleDesc tupdesc;
518 : : HeapTupleData tmptup,
519 : : *tuple;
520 : : int i;
4326 bruce@momjian.us 521 : 514 : bool needsep = false;
522 : : const char *sep;
523 : : int seplen;
524 : :
525 : : /*
526 : : * We can avoid expensive strlen() calls by precalculating the separator
527 : : * length.
528 : : */
4454 andrew@dunslane.net 529 [ + + ]: 514 : sep = use_line_feeds ? ",\n " : ",";
128 nathan@postgresql.or 530 [ + + ]:GNC 514 : seplen = use_line_feeds ? strlen(",\n ") : strlen(",");
531 : :
4326 bruce@momjian.us 532 :CBC 514 : td = DatumGetHeapTupleHeader(composite);
533 : :
534 : : /* Extract rowtype info and find a tupdesc */
535 : 514 : tupType = HeapTupleHeaderGetTypeId(td);
536 : 514 : tupTypmod = HeapTupleHeaderGetTypMod(td);
537 : 514 : tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
538 : :
539 : : /* Build a temporary HeapTuple control structure */
540 : 514 : tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
541 : 514 : tmptup.t_data = td;
4454 andrew@dunslane.net 542 : 514 : tuple = &tmptup;
543 : :
4326 bruce@momjian.us 544 : 514 : appendStringInfoChar(result, '{');
545 : :
546 [ + + ]: 1626 : for (i = 0; i < tupdesc->natts; i++)
547 : : {
548 : : Datum val;
549 : : bool isnull;
550 : : char *attname;
551 : : JsonTypeCategory tcategory;
552 : : Oid outfuncoid;
2429 andres@anarazel.de 553 : 1112 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
554 : :
555 [ - + ]: 1112 : if (att->attisdropped)
4326 bruce@momjian.us 556 :UBC 0 : continue;
557 : :
4454 andrew@dunslane.net 558 [ + + ]:CBC 1112 : if (needsep)
128 nathan@postgresql.or 559 :GNC 598 : appendBinaryStringInfo(result, sep, seplen);
4454 andrew@dunslane.net 560 :CBC 1112 : needsep = true;
561 : :
2429 andres@anarazel.de 562 : 1112 : attname = NameStr(att->attname);
4326 bruce@momjian.us 563 : 1112 : escape_json(result, attname);
564 : 1112 : appendStringInfoChar(result, ':');
565 : :
3485 sfrost@snowman.net 566 : 1112 : val = heap_getattr(tuple, i + 1, tupdesc, &isnull);
567 : :
3628 tgl@sss.pgh.pa.us 568 [ + + ]: 1112 : if (isnull)
569 : : {
570 : 90 : tcategory = JSONTYPE_NULL;
571 : 90 : outfuncoid = InvalidOid;
572 : : }
573 : : else
269 amitlan@postgresql.o 574 :GNC 1022 : json_categorize_type(att->atttypid, false, &tcategory,
575 : : &outfuncoid);
576 : :
268 577 : 1112 : datum_to_json_internal(val, isnull, result, tcategory, outfuncoid,
578 : : false);
579 : : }
580 : :
4326 bruce@momjian.us 581 :CBC 514 : appendStringInfoChar(result, '}');
582 [ + - ]: 514 : ReleaseTupleDesc(tupdesc);
4454 andrew@dunslane.net 583 : 514 : }
584 : :
585 : : /*
586 : : * Append JSON text for "val" to "result".
587 : : *
588 : : * This is just a thin wrapper around datum_to_json. If the same type will be
589 : : * printed many times, avoid using this; better to do the json_categorize_type
590 : : * lookups only once.
591 : : */
592 : : static void
3628 tgl@sss.pgh.pa.us 593 : 583 : add_json(Datum val, bool is_null, StringInfo result,
594 : : Oid val_type, bool key_scalar)
595 : : {
596 : : JsonTypeCategory tcategory;
597 : : Oid outfuncoid;
598 : :
3729 andrew@dunslane.net 599 [ - + ]: 583 : if (val_type == InvalidOid)
3729 andrew@dunslane.net 600 [ # # ]:UBC 0 : ereport(ERROR,
601 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
602 : : errmsg("could not determine input data type")));
603 : :
3628 tgl@sss.pgh.pa.us 604 [ + + ]:CBC 583 : if (is_null)
605 : : {
606 : 33 : tcategory = JSONTYPE_NULL;
607 : 33 : outfuncoid = InvalidOid;
608 : : }
609 : : else
269 amitlan@postgresql.o 610 :GNC 550 : json_categorize_type(val_type, false,
611 : : &tcategory, &outfuncoid);
612 : :
268 613 : 583 : datum_to_json_internal(val, is_null, result, tcategory, outfuncoid,
614 : : key_scalar);
3729 andrew@dunslane.net 615 :CBC 565 : }
616 : :
617 : : /*
618 : : * SQL function array_to_json(row)
619 : : */
620 : : Datum
3503 sfrost@snowman.net 621 : 9 : array_to_json(PG_FUNCTION_ARGS)
622 : : {
3485 623 : 9 : Datum array = PG_GETARG_DATUM(0);
624 : : StringInfo result;
625 : :
626 : 9 : result = makeStringInfo();
627 : :
628 : 9 : array_to_json_internal(array, result, false);
629 : :
630 : 9 : PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
631 : : }
632 : :
633 : : /*
634 : : * SQL function array_to_json(row, prettybool)
635 : : */
636 : : Datum
637 : 12 : array_to_json_pretty(PG_FUNCTION_ARGS)
638 : : {
4326 bruce@momjian.us 639 : 12 : Datum array = PG_GETARG_DATUM(0);
640 : 12 : bool use_line_feeds = PG_GETARG_BOOL(1);
641 : : StringInfo result;
642 : :
4454 andrew@dunslane.net 643 : 12 : result = makeStringInfo();
644 : :
645 : 12 : array_to_json_internal(array, result, use_line_feeds);
646 : :
3800 rhaas@postgresql.org 647 : 12 : PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
648 : : }
649 : :
650 : : /*
651 : : * SQL function row_to_json(row)
652 : : */
653 : : Datum
4454 andrew@dunslane.net 654 : 240 : row_to_json(PG_FUNCTION_ARGS)
655 : : {
3485 sfrost@snowman.net 656 : 240 : Datum array = PG_GETARG_DATUM(0);
657 : : StringInfo result;
658 : :
659 : 240 : result = makeStringInfo();
660 : :
661 : 240 : composite_to_json(array, result, false);
662 : :
663 : 240 : PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
664 : : }
665 : :
666 : : /*
667 : : * SQL function row_to_json(row, prettybool)
668 : : */
669 : : Datum
670 : 24 : row_to_json_pretty(PG_FUNCTION_ARGS)
671 : : {
4326 bruce@momjian.us 672 : 24 : Datum array = PG_GETARG_DATUM(0);
673 : 24 : bool use_line_feeds = PG_GETARG_BOOL(1);
674 : : StringInfo result;
675 : :
4454 andrew@dunslane.net 676 : 24 : result = makeStringInfo();
677 : :
3485 sfrost@snowman.net 678 : 24 : composite_to_json(array, result, use_line_feeds);
679 : :
3800 rhaas@postgresql.org 680 : 24 : PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
681 : : }
682 : :
683 : : /*
684 : : * Is the given type immutable when coming out of a JSON context?
685 : : *
686 : : * At present, datetimes are all considered mutable, because they
687 : : * depend on timezone. XXX we should also drill down into objects
688 : : * and arrays, but do not.
689 : : */
690 : : bool
382 alvherre@alvh.no-ip. 691 :UBC 0 : to_json_is_immutable(Oid typoid)
692 : : {
693 : : JsonTypeCategory tcategory;
694 : : Oid outfuncoid;
695 : :
269 amitlan@postgresql.o 696 :UNC 0 : json_categorize_type(typoid, false, &tcategory, &outfuncoid);
697 : :
382 alvherre@alvh.no-ip. 698 [ # # # # :UBC 0 : switch (tcategory)
# # ]
699 : : {
700 : 0 : case JSONTYPE_BOOL:
701 : : case JSONTYPE_JSON:
702 : : case JSONTYPE_JSONB:
703 : : case JSONTYPE_NULL:
704 : 0 : return true;
705 : :
706 : 0 : case JSONTYPE_DATE:
707 : : case JSONTYPE_TIMESTAMP:
708 : : case JSONTYPE_TIMESTAMPTZ:
709 : 0 : return false;
710 : :
711 : 0 : case JSONTYPE_ARRAY:
712 : 0 : return false; /* TODO recurse into elements */
713 : :
714 : 0 : case JSONTYPE_COMPOSITE:
715 : 0 : return false; /* TODO recurse into fields */
716 : :
717 : 0 : case JSONTYPE_NUMERIC:
718 : : case JSONTYPE_CAST:
719 : : case JSONTYPE_OTHER:
720 : 0 : return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
721 : : }
722 : :
723 : 0 : return false; /* not reached */
724 : : }
725 : :
726 : : /*
727 : : * SQL function to_json(anyvalue)
728 : : */
729 : : Datum
4053 andrew@dunslane.net 730 :CBC 90 : to_json(PG_FUNCTION_ARGS)
731 : : {
3815 tgl@sss.pgh.pa.us 732 : 90 : Datum val = PG_GETARG_DATUM(0);
4053 andrew@dunslane.net 733 : 90 : Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
734 : : JsonTypeCategory tcategory;
735 : : Oid outfuncoid;
736 : :
737 [ - + ]: 90 : if (val_type == InvalidOid)
4053 andrew@dunslane.net 738 [ # # ]:UBC 0 : ereport(ERROR,
739 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
740 : : errmsg("could not determine input data type")));
741 : :
269 amitlan@postgresql.o 742 :GNC 90 : json_categorize_type(val_type, false,
743 : : &tcategory, &outfuncoid);
744 : :
268 745 : 90 : PG_RETURN_DATUM(datum_to_json(val, tcategory, outfuncoid));
746 : : }
747 : :
748 : : /*
749 : : * Turn a Datum into JSON text.
750 : : *
751 : : * tcategory and outfuncoid are from a previous call to json_categorize_type.
752 : : */
753 : : Datum
754 : 125 : datum_to_json(Datum val, JsonTypeCategory tcategory, Oid outfuncoid)
755 : : {
756 : 125 : StringInfo result = makeStringInfo();
757 : :
758 : 125 : datum_to_json_internal(val, false, result, tcategory, outfuncoid,
759 : : false);
760 : :
761 : 125 : return PointerGetDatum(cstring_to_text_with_len(result->data, result->len));
762 : : }
763 : :
764 : : /*
765 : : * json_agg transition function
766 : : *
767 : : * aggregate input column as a json array value.
768 : : */
769 : : static Datum
382 alvherre@alvh.no-ip. 770 :CBC 271 : json_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null)
771 : : {
772 : : MemoryContext aggcontext,
773 : : oldcontext;
774 : : JsonAggState *state;
775 : : Datum val;
776 : :
4053 andrew@dunslane.net 777 [ - + ]: 271 : if (!AggCheckCallContext(fcinfo, &aggcontext))
778 : : {
779 : : /* cannot be called directly because of internal-type argument */
4053 andrew@dunslane.net 780 [ # # ]:UBC 0 : elog(ERROR, "json_agg_transfn called in non-aggregate context");
781 : : }
782 : :
4053 andrew@dunslane.net 783 [ + + ]:CBC 271 : if (PG_ARGISNULL(0))
784 : : {
3099 tgl@sss.pgh.pa.us 785 : 56 : Oid arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
786 : :
3131 andrew@dunslane.net 787 [ - + ]: 56 : if (arg_type == InvalidOid)
3131 andrew@dunslane.net 788 [ # # ]:UBC 0 : ereport(ERROR,
789 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
790 : : errmsg("could not determine input data type")));
791 : :
792 : : /*
793 : : * Make this state object in a context where it will persist for the
794 : : * duration of the aggregate call. MemoryContextSwitchTo is only
795 : : * needed the first time, as the StringInfo routines make sure they
796 : : * use the right context to enlarge the object if necessary.
797 : : */
4053 andrew@dunslane.net 798 :CBC 56 : oldcontext = MemoryContextSwitchTo(aggcontext);
3131 799 : 56 : state = (JsonAggState *) palloc(sizeof(JsonAggState));
800 : 56 : state->str = makeStringInfo();
4053 801 : 56 : MemoryContextSwitchTo(oldcontext);
802 : :
3131 803 : 56 : appendStringInfoChar(state->str, '[');
269 amitlan@postgresql.o 804 :GNC 56 : json_categorize_type(arg_type, false, &state->val_category,
805 : : &state->val_output_func);
806 : : }
807 : : else
808 : : {
3131 andrew@dunslane.net 809 :CBC 215 : state = (JsonAggState *) PG_GETARG_POINTER(0);
810 : : }
811 : :
382 alvherre@alvh.no-ip. 812 [ + + + + ]: 271 : if (absent_on_null && PG_ARGISNULL(1))
813 : 45 : PG_RETURN_POINTER(state);
814 : :
815 [ + + ]: 226 : if (state->str->len > 1)
816 : 173 : appendStringInfoString(state->str, ", ");
817 : :
818 : : /* fast path for NULLs */
4053 andrew@dunslane.net 819 [ + + ]: 226 : if (PG_ARGISNULL(1))
820 : : {
268 amitlan@postgresql.o 821 :GNC 27 : datum_to_json_internal((Datum) 0, true, state->str, JSONTYPE_NULL,
822 : : InvalidOid, false);
4053 andrew@dunslane.net 823 :CBC 27 : PG_RETURN_POINTER(state);
824 : : }
825 : :
3815 tgl@sss.pgh.pa.us 826 : 199 : val = PG_GETARG_DATUM(1);
827 : :
828 : : /* add some whitespace if structured type and not first item */
382 alvherre@alvh.no-ip. 829 [ + + + + ]: 199 : if (!PG_ARGISNULL(0) && state->str->len > 1 &&
3131 andrew@dunslane.net 830 [ + + ]: 149 : (state->val_category == JSONTYPE_ARRAY ||
831 [ + + ]: 146 : state->val_category == JSONTYPE_COMPOSITE))
832 : : {
833 : 56 : appendStringInfoString(state->str, "\n ");
834 : : }
835 : :
268 amitlan@postgresql.o 836 :GNC 199 : datum_to_json_internal(val, false, state->str, state->val_category,
837 : : state->val_output_func, false);
838 : :
839 : : /*
840 : : * The transition type for json_agg() is declared to be "internal", which
841 : : * is a pass-by-value type the same size as a pointer. So we can safely
842 : : * pass the JsonAggState pointer through nodeAgg.c's machinations.
843 : : */
4053 andrew@dunslane.net 844 :CBC 199 : PG_RETURN_POINTER(state);
845 : : }
846 : :
847 : :
848 : : /*
849 : : * json_agg aggregate function
850 : : */
851 : : Datum
382 alvherre@alvh.no-ip. 852 : 76 : json_agg_transfn(PG_FUNCTION_ARGS)
853 : : {
854 : 76 : return json_agg_transfn_worker(fcinfo, false);
855 : : }
856 : :
857 : : /*
858 : : * json_agg_strict aggregate function
859 : : */
860 : : Datum
861 : 195 : json_agg_strict_transfn(PG_FUNCTION_ARGS)
862 : : {
863 : 195 : return json_agg_transfn_worker(fcinfo, true);
864 : : }
865 : :
866 : : /*
867 : : * json_agg final function
868 : : */
869 : : Datum
4053 andrew@dunslane.net 870 : 62 : json_agg_finalfn(PG_FUNCTION_ARGS)
871 : : {
872 : : JsonAggState *state;
873 : :
874 : : /* cannot be called directly because of internal-type argument */
875 [ - + ]: 62 : Assert(AggCheckCallContext(fcinfo, NULL));
876 : :
3131 877 : 124 : state = PG_ARGISNULL(0) ?
878 [ + + ]: 62 : NULL :
879 : 56 : (JsonAggState *) PG_GETARG_POINTER(0);
880 : :
881 : : /* NULL result for no rows in, as is standard with aggregates */
4053 882 [ + + ]: 62 : if (state == NULL)
883 : 6 : PG_RETURN_NULL();
884 : :
885 : : /* Else return state with appropriate array terminator added */
3131 886 : 56 : PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, "]"));
887 : : }
888 : :
889 : : /* Functions implementing hash table for key uniqueness check */
890 : : static uint32
382 alvherre@alvh.no-ip. 891 : 207 : json_unique_hash(const void *key, Size keysize)
892 : : {
893 : 207 : const JsonUniqueHashEntry *entry = (JsonUniqueHashEntry *) key;
894 : 207 : uint32 hash = hash_bytes_uint32(entry->object_id);
895 : :
896 : 207 : hash ^= hash_bytes((const unsigned char *) entry->key, entry->key_len);
897 : :
898 : 207 : return DatumGetUInt32(hash);
899 : : }
900 : :
901 : : static int
902 : 48 : json_unique_hash_match(const void *key1, const void *key2, Size keysize)
903 : : {
904 : 48 : const JsonUniqueHashEntry *entry1 = (const JsonUniqueHashEntry *) key1;
905 : 48 : const JsonUniqueHashEntry *entry2 = (const JsonUniqueHashEntry *) key2;
906 : :
907 [ - + ]: 48 : if (entry1->object_id != entry2->object_id)
382 alvherre@alvh.no-ip. 908 [ # # ]:UBC 0 : return entry1->object_id > entry2->object_id ? 1 : -1;
909 : :
382 alvherre@alvh.no-ip. 910 [ - + ]:CBC 48 : if (entry1->key_len != entry2->key_len)
382 alvherre@alvh.no-ip. 911 [ # # ]:UBC 0 : return entry1->key_len > entry2->key_len ? 1 : -1;
912 : :
382 alvherre@alvh.no-ip. 913 :CBC 48 : return strncmp(entry1->key, entry2->key, entry1->key_len);
914 : : }
915 : :
916 : : /*
917 : : * Uniqueness detection support.
918 : : *
919 : : * In order to detect uniqueness during building or parsing of a JSON
920 : : * object, we maintain a hash table of key names already seen.
921 : : */
922 : : static void
923 : 147 : json_unique_check_init(JsonUniqueCheckState *cxt)
924 : : {
925 : : HASHCTL ctl;
926 : :
927 : 147 : memset(&ctl, 0, sizeof(ctl));
928 : 147 : ctl.keysize = sizeof(JsonUniqueHashEntry);
929 : 147 : ctl.entrysize = sizeof(JsonUniqueHashEntry);
930 : 147 : ctl.hcxt = CurrentMemoryContext;
931 : 147 : ctl.hash = json_unique_hash;
932 : 147 : ctl.match = json_unique_hash_match;
933 : :
934 : 147 : *cxt = hash_create("json object hashtable",
935 : : 32,
936 : : &ctl,
937 : : HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE);
938 : 147 : }
939 : :
940 : : static void
941 : 31 : json_unique_builder_init(JsonUniqueBuilderState *cxt)
942 : : {
943 : 31 : json_unique_check_init(&cxt->check);
944 : 31 : cxt->mcxt = CurrentMemoryContext;
945 : 31 : cxt->skipped_keys.data = NULL;
946 : 31 : }
947 : :
948 : : static bool
949 : 207 : json_unique_check_key(JsonUniqueCheckState *cxt, const char *key, int object_id)
950 : : {
951 : : JsonUniqueHashEntry entry;
952 : : bool found;
953 : :
954 : 207 : entry.key = key;
955 : 207 : entry.key_len = strlen(key);
956 : 207 : entry.object_id = object_id;
957 : :
958 : 207 : (void) hash_search(*cxt, &entry, HASH_ENTER, &found);
959 : :
960 : 207 : return !found;
961 : : }
962 : :
963 : : /*
964 : : * On-demand initialization of a throwaway StringInfo. This is used to
965 : : * read a key name that we don't need to store in the output object, for
966 : : * duplicate key detection when the value is NULL.
967 : : */
968 : : static StringInfo
969 : 21 : json_unique_builder_get_throwawaybuf(JsonUniqueBuilderState *cxt)
970 : : {
971 : 21 : StringInfo out = &cxt->skipped_keys;
972 : :
973 [ + + ]: 21 : if (!out->data)
974 : : {
975 : 15 : MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt);
976 : :
977 : 15 : initStringInfo(out);
978 : 15 : MemoryContextSwitchTo(oldcxt);
979 : : }
980 : : else
981 : : /* Just reset the string to empty */
982 : 6 : out->len = 0;
983 : :
984 : 21 : return out;
985 : : }
986 : :
987 : : /*
988 : : * json_object_agg transition function.
989 : : *
990 : : * aggregate two input columns as a single json object value.
991 : : */
992 : : static Datum
993 : 138 : json_object_agg_transfn_worker(FunctionCallInfo fcinfo,
994 : : bool absent_on_null, bool unique_keys)
995 : : {
996 : : MemoryContext aggcontext,
997 : : oldcontext;
998 : : JsonAggState *state;
999 : : StringInfo out;
1000 : : Datum arg;
1001 : : bool skip;
1002 : : int key_offset;
1003 : :
3729 andrew@dunslane.net 1004 [ - + ]: 138 : if (!AggCheckCallContext(fcinfo, &aggcontext))
1005 : : {
1006 : : /* cannot be called directly because of internal-type argument */
3421 tgl@sss.pgh.pa.us 1007 [ # # ]:UBC 0 : elog(ERROR, "json_object_agg_transfn called in non-aggregate context");
1008 : : }
1009 : :
3729 andrew@dunslane.net 1010 [ + + ]:CBC 138 : if (PG_ARGISNULL(0))
1011 : : {
1012 : : Oid arg_type;
1013 : :
1014 : : /*
1015 : : * Make the StringInfo in a context where it will persist for the
1016 : : * duration of the aggregate call. Switching context is only needed
1017 : : * for this initial step, as the StringInfo and dynahash routines make
1018 : : * sure they use the right context to enlarge the object if necessary.
1019 : : */
1020 : 46 : oldcontext = MemoryContextSwitchTo(aggcontext);
3131 1021 : 46 : state = (JsonAggState *) palloc(sizeof(JsonAggState));
1022 : 46 : state->str = makeStringInfo();
382 alvherre@alvh.no-ip. 1023 [ + + ]: 46 : if (unique_keys)
1024 : 18 : json_unique_builder_init(&state->unique_check);
1025 : : else
1026 : 28 : memset(&state->unique_check, 0, sizeof(state->unique_check));
3729 andrew@dunslane.net 1027 : 46 : MemoryContextSwitchTo(oldcontext);
1028 : :
3131 1029 : 46 : arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
1030 : :
1031 [ - + ]: 46 : if (arg_type == InvalidOid)
3131 andrew@dunslane.net 1032 [ # # ]:UBC 0 : ereport(ERROR,
1033 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1034 : : errmsg("could not determine data type for argument %d", 1)));
1035 : :
269 amitlan@postgresql.o 1036 :GNC 46 : json_categorize_type(arg_type, false, &state->key_category,
1037 : : &state->key_output_func);
1038 : :
3131 andrew@dunslane.net 1039 :CBC 46 : arg_type = get_fn_expr_argtype(fcinfo->flinfo, 2);
1040 : :
1041 [ - + ]: 46 : if (arg_type == InvalidOid)
3131 andrew@dunslane.net 1042 [ # # ]:UBC 0 : ereport(ERROR,
1043 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1044 : : errmsg("could not determine data type for argument %d", 2)));
1045 : :
269 amitlan@postgresql.o 1046 :GNC 46 : json_categorize_type(arg_type, false, &state->val_category,
1047 : : &state->val_output_func);
1048 : :
3131 andrew@dunslane.net 1049 :CBC 46 : appendStringInfoString(state->str, "{ ");
1050 : : }
1051 : : else
1052 : : {
1053 : 92 : state = (JsonAggState *) PG_GETARG_POINTER(0);
1054 : : }
1055 : :
1056 : : /*
1057 : : * Note: since json_object_agg() is declared as taking type "any", the
1058 : : * parser will not do any type conversion on unknown-type literals (that
1059 : : * is, undecorated strings or NULLs). Such values will arrive here as
1060 : : * type UNKNOWN, which fortunately does not matter to us, since
1061 : : * unknownout() works fine.
1062 : : */
1063 : :
3536 tgl@sss.pgh.pa.us 1064 [ + + ]: 138 : if (PG_ARGISNULL(1))
1065 [ + - ]: 6 : ereport(ERROR,
1066 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
1067 : : errmsg("null value not allowed for object key")));
1068 : :
1069 : : /* Skip null values if absent_on_null */
382 alvherre@alvh.no-ip. 1070 [ + + + + ]: 132 : skip = absent_on_null && PG_ARGISNULL(2);
1071 : :
1072 [ + + ]: 132 : if (skip)
1073 : : {
1074 : : /*
1075 : : * We got a NULL value and we're not storing those; if we're not
1076 : : * testing key uniqueness, we're done. If we are, use the throwaway
1077 : : * buffer to store the key name so that we can check it.
1078 : : */
1079 [ + + ]: 27 : if (!unique_keys)
1080 : 12 : PG_RETURN_POINTER(state);
1081 : :
1082 : 15 : out = json_unique_builder_get_throwawaybuf(&state->unique_check);
1083 : : }
1084 : : else
1085 : : {
1086 : 105 : out = state->str;
1087 : :
1088 : : /*
1089 : : * Append comma delimiter only if we have already output some fields
1090 : : * after the initial string "{ ".
1091 : : */
1092 [ + + ]: 105 : if (out->len > 2)
1093 : 62 : appendStringInfoString(out, ", ");
1094 : : }
1095 : :
3536 tgl@sss.pgh.pa.us 1096 : 120 : arg = PG_GETARG_DATUM(1);
1097 : :
382 alvherre@alvh.no-ip. 1098 : 120 : key_offset = out->len;
1099 : :
268 amitlan@postgresql.o 1100 :GNC 120 : datum_to_json_internal(arg, false, out, state->key_category,
1101 : : state->key_output_func, true);
1102 : :
382 alvherre@alvh.no-ip. 1103 [ + + ]:CBC 120 : if (unique_keys)
1104 : : {
1105 : 45 : const char *key = &out->data[key_offset];
1106 : :
1107 [ + + ]: 45 : if (!json_unique_check_key(&state->unique_check.check, key, 0))
1108 [ + - ]: 12 : ereport(ERROR,
1109 : : errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
1110 : : errmsg("duplicate JSON object key value: %s", key));
1111 : :
1112 [ + + ]: 33 : if (skip)
1113 : 9 : PG_RETURN_POINTER(state);
1114 : : }
1115 : :
3131 andrew@dunslane.net 1116 : 99 : appendStringInfoString(state->str, " : ");
1117 : :
3536 tgl@sss.pgh.pa.us 1118 [ + + ]: 99 : if (PG_ARGISNULL(2))
1119 : 6 : arg = (Datum) 0;
1120 : : else
1121 : 93 : arg = PG_GETARG_DATUM(2);
1122 : :
268 amitlan@postgresql.o 1123 :GNC 99 : datum_to_json_internal(arg, PG_ARGISNULL(2), state->str,
1124 : : state->val_category,
1125 : : state->val_output_func, false);
1126 : :
3729 andrew@dunslane.net 1127 :CBC 99 : PG_RETURN_POINTER(state);
1128 : : }
1129 : :
1130 : : /*
1131 : : * json_object_agg aggregate function
1132 : : */
1133 : : Datum
382 alvherre@alvh.no-ip. 1134 : 63 : json_object_agg_transfn(PG_FUNCTION_ARGS)
1135 : : {
1136 : 63 : return json_object_agg_transfn_worker(fcinfo, false, false);
1137 : : }
1138 : :
1139 : : /*
1140 : : * json_object_agg_strict aggregate function
1141 : : */
1142 : : Datum
1143 : 30 : json_object_agg_strict_transfn(PG_FUNCTION_ARGS)
1144 : : {
1145 : 30 : return json_object_agg_transfn_worker(fcinfo, true, false);
1146 : : }
1147 : :
1148 : : /*
1149 : : * json_object_agg_unique aggregate function
1150 : : */
1151 : : Datum
1152 : 18 : json_object_agg_unique_transfn(PG_FUNCTION_ARGS)
1153 : : {
1154 : 18 : return json_object_agg_transfn_worker(fcinfo, false, true);
1155 : : }
1156 : :
1157 : : /*
1158 : : * json_object_agg_unique_strict aggregate function
1159 : : */
1160 : : Datum
1161 : 27 : json_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS)
1162 : : {
1163 : 27 : return json_object_agg_transfn_worker(fcinfo, true, true);
1164 : : }
1165 : :
1166 : : /*
1167 : : * json_object_agg final function.
1168 : : */
1169 : : Datum
3729 andrew@dunslane.net 1170 : 37 : json_object_agg_finalfn(PG_FUNCTION_ARGS)
1171 : : {
1172 : : JsonAggState *state;
1173 : :
1174 : : /* cannot be called directly because of internal-type argument */
1175 [ - + ]: 37 : Assert(AggCheckCallContext(fcinfo, NULL));
1176 : :
3131 1177 [ + + ]: 37 : state = PG_ARGISNULL(0) ? NULL : (JsonAggState *) PG_GETARG_POINTER(0);
1178 : :
1179 : : /* NULL result for no rows in, as is standard with aggregates */
3729 1180 [ + + ]: 37 : if (state == NULL)
3489 1181 : 3 : PG_RETURN_NULL();
1182 : :
1183 : : /* Else return state with appropriate object terminator added */
3131 1184 : 34 : PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }"));
1185 : : }
1186 : :
1187 : : /*
1188 : : * Helper function for aggregates: return given StringInfo's contents plus
1189 : : * specified trailing string, as a text datum. We need this because aggregate
1190 : : * final functions are not allowed to modify the aggregate state.
1191 : : */
1192 : : static text *
3421 tgl@sss.pgh.pa.us 1193 : 90 : catenate_stringinfo_string(StringInfo buffer, const char *addon)
1194 : : {
1195 : : /* custom version of cstring_to_text_with_len */
1196 : 90 : int buflen = buffer->len;
1197 : 90 : int addlen = strlen(addon);
1198 : 90 : text *result = (text *) palloc(buflen + addlen + VARHDRSZ);
1199 : :
1200 : 90 : SET_VARSIZE(result, buflen + addlen + VARHDRSZ);
1201 : 90 : memcpy(VARDATA(result), buffer->data, buflen);
1202 : 90 : memcpy(VARDATA(result) + buflen, addon, addlen);
1203 : :
1204 : 90 : return result;
1205 : : }
1206 : :
1207 : : Datum
187 peter@eisentraut.org 1208 :GNC 196 : json_build_object_worker(int nargs, const Datum *args, const bool *nulls, const Oid *types,
1209 : : bool absent_on_null, bool unique_keys)
1210 : : {
1211 : : int i;
3536 tgl@sss.pgh.pa.us 1212 :CBC 196 : const char *sep = "";
1213 : : StringInfo result;
1214 : : JsonUniqueBuilderState unique_check;
1215 : :
3729 andrew@dunslane.net 1216 [ + + ]: 196 : if (nargs % 2 != 0)
1217 [ + - ]: 9 : ereport(ERROR,
1218 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1219 : : errmsg("argument list must have even number of elements"),
1220 : : /* translator: %s is a SQL function name */
1221 : : errhint("The arguments of %s must consist of alternating keys and values.",
1222 : : "json_build_object()")));
1223 : :
1224 : 187 : result = makeStringInfo();
1225 : :
1226 : 187 : appendStringInfoChar(result, '{');
1227 : :
382 alvherre@alvh.no-ip. 1228 [ + + ]: 187 : if (unique_keys)
1229 : 13 : json_unique_builder_init(&unique_check);
1230 : :
3729 andrew@dunslane.net 1231 [ + + ]: 392 : for (i = 0; i < nargs; i += 2)
1232 : : {
1233 : : StringInfo out;
1234 : : bool skip;
1235 : : int key_offset;
1236 : :
1237 : : /* Skip null values if absent_on_null */
382 alvherre@alvh.no-ip. 1238 [ + + + + ]: 248 : skip = absent_on_null && nulls[i + 1];
1239 : :
1240 [ + + ]: 248 : if (skip)
1241 : : {
1242 : : /* If key uniqueness check is needed we must save skipped keys */
1243 [ + + ]: 13 : if (!unique_keys)
1244 : 7 : continue;
1245 : :
1246 : 6 : out = json_unique_builder_get_throwawaybuf(&unique_check);
1247 : : }
1248 : : else
1249 : : {
1250 : 235 : appendStringInfoString(result, sep);
1251 : 235 : sep = ", ";
1252 : 235 : out = result;
1253 : : }
1254 : :
1255 : : /* process key */
2363 andrew@dunslane.net 1256 [ + + ]: 241 : if (nulls[i])
3729 1257 [ + - ]: 12 : ereport(ERROR,
1258 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
1259 : : errmsg("null value not allowed for object key")));
1260 : :
1261 : : /* save key offset before appending it */
382 alvherre@alvh.no-ip. 1262 : 229 : key_offset = out->len;
1263 : :
1264 : 229 : add_json(args[i], false, out, types[i], true);
1265 : :
1266 [ + + ]: 211 : if (unique_keys)
1267 : : {
1268 : : /* check key uniqueness after key appending */
1269 : 32 : const char *key = &out->data[key_offset];
1270 : :
1271 [ + + ]: 32 : if (!json_unique_check_key(&unique_check.check, key, 0))
1272 [ + - ]: 13 : ereport(ERROR,
1273 : : errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
1274 : : errmsg("duplicate JSON object key value: %s", key));
1275 : :
1276 [ + + ]: 19 : if (skip)
1277 : 3 : continue;
1278 : : }
1279 : :
3729 andrew@dunslane.net 1280 : 195 : appendStringInfoString(result, " : ");
1281 : :
1282 : : /* process value */
2363 1283 : 195 : add_json(args[i + 1], nulls[i + 1], result, types[i + 1], false);
1284 : : }
1285 : :
3729 1286 : 144 : appendStringInfoChar(result, '}');
1287 : :
382 alvherre@alvh.no-ip. 1288 : 144 : return PointerGetDatum(cstring_to_text_with_len(result->data, result->len));
1289 : : }
1290 : :
1291 : : /*
1292 : : * SQL function json_build_object(variadic "any")
1293 : : */
1294 : : Datum
1295 : 78 : json_build_object(PG_FUNCTION_ARGS)
1296 : : {
1297 : : Datum *args;
1298 : : bool *nulls;
1299 : : Oid *types;
1300 : :
1301 : : /* build argument values to build the object */
1302 : 78 : int nargs = extract_variadic_args(fcinfo, 0, true,
1303 : : &args, &types, &nulls);
1304 : :
1305 [ + + ]: 78 : if (nargs < 0)
1306 : 3 : PG_RETURN_NULL();
1307 : :
1308 : 75 : PG_RETURN_DATUM(json_build_object_worker(nargs, args, nulls, types, false, false));
1309 : : }
1310 : :
1311 : : /*
1312 : : * degenerate case of json_build_object where it gets 0 arguments.
1313 : : */
1314 : : Datum
3729 andrew@dunslane.net 1315 : 3 : json_build_object_noargs(PG_FUNCTION_ARGS)
1316 : : {
1317 : 3 : PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
1318 : : }
1319 : :
1320 : : Datum
187 peter@eisentraut.org 1321 :GNC 94 : json_build_array_worker(int nargs, const Datum *args, const bool *nulls, const Oid *types,
1322 : : bool absent_on_null)
1323 : : {
1324 : : int i;
3536 tgl@sss.pgh.pa.us 1325 :CBC 94 : const char *sep = "";
1326 : : StringInfo result;
1327 : :
3729 andrew@dunslane.net 1328 : 94 : result = makeStringInfo();
1329 : :
1330 : 94 : appendStringInfoChar(result, '[');
1331 : :
1332 [ + + ]: 262 : for (i = 0; i < nargs; i++)
1333 : : {
382 alvherre@alvh.no-ip. 1334 [ + + + + ]: 168 : if (absent_on_null && nulls[i])
1335 : 9 : continue;
1336 : :
3536 tgl@sss.pgh.pa.us 1337 : 159 : appendStringInfoString(result, sep);
1338 : 159 : sep = ", ";
2363 andrew@dunslane.net 1339 : 159 : add_json(args[i], nulls[i], result, types[i], false);
1340 : : }
1341 : :
3729 1342 : 94 : appendStringInfoChar(result, ']');
1343 : :
382 alvherre@alvh.no-ip. 1344 : 94 : return PointerGetDatum(cstring_to_text_with_len(result->data, result->len));
1345 : : }
1346 : :
1347 : : /*
1348 : : * SQL function json_build_array(variadic "any")
1349 : : */
1350 : : Datum
1351 : 27 : json_build_array(PG_FUNCTION_ARGS)
1352 : : {
1353 : : Datum *args;
1354 : : bool *nulls;
1355 : : Oid *types;
1356 : :
1357 : : /* build argument values to build the object */
1358 : 27 : int nargs = extract_variadic_args(fcinfo, 0, true,
1359 : : &args, &types, &nulls);
1360 : :
1361 [ + + ]: 27 : if (nargs < 0)
1362 : 3 : PG_RETURN_NULL();
1363 : :
1364 : 24 : PG_RETURN_DATUM(json_build_array_worker(nargs, args, nulls, types, false));
1365 : : }
1366 : :
1367 : : /*
1368 : : * degenerate case of json_build_array where it gets 0 arguments.
1369 : : */
1370 : : Datum
3729 andrew@dunslane.net 1371 : 3 : json_build_array_noargs(PG_FUNCTION_ARGS)
1372 : : {
1373 : 3 : PG_RETURN_TEXT_P(cstring_to_text_with_len("[]", 2));
1374 : : }
1375 : :
1376 : : /*
1377 : : * SQL function json_object(text[])
1378 : : *
1379 : : * take a one or two dimensional array of text as key/value pairs
1380 : : * for a json object.
1381 : : */
1382 : : Datum
1383 : 24 : json_object(PG_FUNCTION_ARGS)
1384 : : {
1385 : 24 : ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0);
1386 : 24 : int ndims = ARR_NDIM(in_array);
1387 : : StringInfoData result;
1388 : : Datum *in_datums;
1389 : : bool *in_nulls;
1390 : : int in_count,
1391 : : count,
1392 : : i;
1393 : : text *rval;
1394 : : char *v;
1395 : :
1396 [ + + + + ]: 24 : switch (ndims)
1397 : : {
1398 : 3 : case 0:
1399 : 3 : PG_RETURN_DATUM(CStringGetTextDatum("{}"));
1400 : : break;
1401 : :
1402 : 9 : case 1:
1403 [ + + ]: 9 : if ((ARR_DIMS(in_array)[0]) % 2)
1404 [ + - ]: 3 : ereport(ERROR,
1405 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1406 : : errmsg("array must have even number of elements")));
1407 : 6 : break;
1408 : :
1409 : 9 : case 2:
1410 [ + + ]: 9 : if ((ARR_DIMS(in_array)[1]) != 2)
1411 [ + - ]: 6 : ereport(ERROR,
1412 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1413 : : errmsg("array must have two columns")));
1414 : 3 : break;
1415 : :
1416 : 3 : default:
1417 [ + - ]: 3 : ereport(ERROR,
1418 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1419 : : errmsg("wrong number of array subscripts")));
1420 : : }
1421 : :
653 peter@eisentraut.org 1422 : 9 : deconstruct_array_builtin(in_array, TEXTOID, &in_datums, &in_nulls, &in_count);
1423 : :
3729 andrew@dunslane.net 1424 : 9 : count = in_count / 2;
1425 : :
1426 : 9 : initStringInfo(&result);
1427 : :
1428 : 9 : appendStringInfoChar(&result, '{');
1429 : :
1430 [ + + ]: 933 : for (i = 0; i < count; ++i)
1431 : : {
1432 [ - + ]: 924 : if (in_nulls[i * 2])
3729 andrew@dunslane.net 1433 [ # # ]:UBC 0 : ereport(ERROR,
1434 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
1435 : : errmsg("null value not allowed for object key")));
1436 : :
3729 andrew@dunslane.net 1437 :CBC 924 : v = TextDatumGetCString(in_datums[i * 2]);
1438 [ + + ]: 924 : if (i > 0)
1439 : 915 : appendStringInfoString(&result, ", ");
1440 : 924 : escape_json(&result, v);
1441 : 924 : appendStringInfoString(&result, " : ");
1442 : 924 : pfree(v);
1443 [ + + ]: 924 : if (in_nulls[i * 2 + 1])
1444 : 6 : appendStringInfoString(&result, "null");
1445 : : else
1446 : : {
1447 : 918 : v = TextDatumGetCString(in_datums[i * 2 + 1]);
1448 : 918 : escape_json(&result, v);
1449 : 918 : pfree(v);
1450 : : }
1451 : : }
1452 : :
1453 : 9 : appendStringInfoChar(&result, '}');
1454 : :
1455 : 9 : pfree(in_datums);
1456 : 9 : pfree(in_nulls);
1457 : :
1458 : 9 : rval = cstring_to_text_with_len(result.data, result.len);
1459 : 9 : pfree(result.data);
1460 : :
1461 : 9 : PG_RETURN_TEXT_P(rval);
1462 : : }
1463 : :
1464 : : /*
1465 : : * SQL function json_object(text[], text[])
1466 : : *
1467 : : * take separate key and value arrays of text to construct a json object
1468 : : * pairwise.
1469 : : */
1470 : : Datum
1471 : 21 : json_object_two_arg(PG_FUNCTION_ARGS)
1472 : : {
1473 : 21 : ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(0);
1474 : 21 : ArrayType *val_array = PG_GETARG_ARRAYTYPE_P(1);
1475 : 21 : int nkdims = ARR_NDIM(key_array);
1476 : 21 : int nvdims = ARR_NDIM(val_array);
1477 : : StringInfoData result;
1478 : : Datum *key_datums,
1479 : : *val_datums;
1480 : : bool *key_nulls,
1481 : : *val_nulls;
1482 : : int key_count,
1483 : : val_count,
1484 : : i;
1485 : : text *rval;
1486 : : char *v;
1487 : :
1488 [ + + - + ]: 21 : if (nkdims > 1 || nkdims != nvdims)
1489 [ + - ]: 3 : ereport(ERROR,
1490 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1491 : : errmsg("wrong number of array subscripts")));
1492 : :
1493 [ + + ]: 18 : if (nkdims == 0)
1494 : 3 : PG_RETURN_DATUM(CStringGetTextDatum("{}"));
1495 : :
653 peter@eisentraut.org 1496 : 15 : deconstruct_array_builtin(key_array, TEXTOID, &key_datums, &key_nulls, &key_count);
1497 : 15 : deconstruct_array_builtin(val_array, TEXTOID, &val_datums, &val_nulls, &val_count);
1498 : :
3729 andrew@dunslane.net 1499 [ + + ]: 15 : if (key_count != val_count)
1500 [ + - ]: 6 : ereport(ERROR,
1501 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1502 : : errmsg("mismatched array dimensions")));
1503 : :
1504 : 9 : initStringInfo(&result);
1505 : :
1506 : 9 : appendStringInfoChar(&result, '{');
1507 : :
1508 [ + + ]: 39 : for (i = 0; i < key_count; ++i)
1509 : : {
1510 [ + + ]: 33 : if (key_nulls[i])
1511 [ + - ]: 3 : ereport(ERROR,
1512 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
1513 : : errmsg("null value not allowed for object key")));
1514 : :
1515 : 30 : v = TextDatumGetCString(key_datums[i]);
1516 [ + + ]: 30 : if (i > 0)
1517 : 21 : appendStringInfoString(&result, ", ");
1518 : 30 : escape_json(&result, v);
1519 : 30 : appendStringInfoString(&result, " : ");
1520 : 30 : pfree(v);
1521 [ - + ]: 30 : if (val_nulls[i])
3729 andrew@dunslane.net 1522 :UBC 0 : appendStringInfoString(&result, "null");
1523 : : else
1524 : : {
3729 andrew@dunslane.net 1525 :CBC 30 : v = TextDatumGetCString(val_datums[i]);
1526 : 30 : escape_json(&result, v);
1527 : 30 : pfree(v);
1528 : : }
1529 : : }
1530 : :
1531 : 6 : appendStringInfoChar(&result, '}');
1532 : :
1533 : 6 : pfree(key_datums);
1534 : 6 : pfree(key_nulls);
1535 : 6 : pfree(val_datums);
1536 : 6 : pfree(val_nulls);
1537 : :
1538 : 6 : rval = cstring_to_text_with_len(result.data, result.len);
1539 : 6 : pfree(result.data);
1540 : :
1541 : 6 : PG_RETURN_TEXT_P(rval);
1542 : : }
1543 : :
1544 : :
1545 : : /*
1546 : : * Produce a JSON string literal, properly escaping characters in the text.
1547 : : */
1548 : : void
4454 1549 : 194654 : escape_json(StringInfo buf, const char *str)
1550 : : {
1551 : : const char *p;
1552 : :
3036 peter_e@gmx.net 1553 [ + + ]: 194654 : appendStringInfoCharMacro(buf, '"');
4454 andrew@dunslane.net 1554 [ + + ]: 2313437 : for (p = str; *p; p++)
1555 : : {
1556 [ + + + + : 2118783 : switch (*p)
+ + + + ]
1557 : : {
1558 : 9 : case '\b':
1559 : 9 : appendStringInfoString(buf, "\\b");
1560 : 9 : break;
1561 : 3 : case '\f':
1562 : 3 : appendStringInfoString(buf, "\\f");
1563 : 3 : break;
1564 : 12 : case '\n':
1565 : 12 : appendStringInfoString(buf, "\\n");
1566 : 12 : break;
1567 : 3 : case '\r':
1568 : 3 : appendStringInfoString(buf, "\\r");
1569 : 3 : break;
1570 : 6 : case '\t':
1571 : 6 : appendStringInfoString(buf, "\\t");
1572 : 6 : break;
1573 : 219 : case '"':
1574 : 219 : appendStringInfoString(buf, "\\\"");
1575 : 219 : break;
1576 : 45 : case '\\':
3362 tgl@sss.pgh.pa.us 1577 : 45 : appendStringInfoString(buf, "\\\\");
4454 andrew@dunslane.net 1578 : 45 : break;
1579 : 2118486 : default:
1580 [ + + ]: 2118486 : if ((unsigned char) *p < ' ')
1581 : 3 : appendStringInfo(buf, "\\u%04x", (int) *p);
1582 : : else
1583 [ + + ]: 2118483 : appendStringInfoCharMacro(buf, *p);
1584 : 2118486 : break;
1585 : : }
1586 : : }
3036 peter_e@gmx.net 1587 [ + + ]: 194654 : appendStringInfoCharMacro(buf, '"');
4454 andrew@dunslane.net 1588 : 194654 : }
1589 : :
1590 : : /* Semantic actions for key uniqueness check */
1591 : : static JsonParseErrorType
380 alvherre@alvh.no-ip. 1592 : 90 : json_unique_object_start(void *_state)
1593 : : {
1594 : 90 : JsonUniqueParsingState *state = _state;
1595 : : JsonUniqueStackEntry *entry;
1596 : :
1597 [ - + ]: 90 : if (!state->unique)
380 alvherre@alvh.no-ip. 1598 :UBC 0 : return JSON_SUCCESS;
1599 : :
1600 : : /* push object entry to stack */
380 alvherre@alvh.no-ip. 1601 :CBC 90 : entry = palloc(sizeof(*entry));
1602 : 90 : entry->object_id = state->id_counter++;
1603 : 90 : entry->parent = state->stack;
1604 : 90 : state->stack = entry;
1605 : :
1606 : 90 : return JSON_SUCCESS;
1607 : : }
1608 : :
1609 : : static JsonParseErrorType
1610 : 87 : json_unique_object_end(void *_state)
1611 : : {
1612 : 87 : JsonUniqueParsingState *state = _state;
1613 : : JsonUniqueStackEntry *entry;
1614 : :
1615 [ + + ]: 87 : if (!state->unique)
1616 : 33 : return JSON_SUCCESS;
1617 : :
1618 : 54 : entry = state->stack;
1619 : 54 : state->stack = entry->parent; /* pop object from stack */
1620 : 54 : pfree(entry);
1621 : 54 : return JSON_SUCCESS;
1622 : : }
1623 : :
1624 : : static JsonParseErrorType
1625 : 130 : json_unique_object_field_start(void *_state, char *field, bool isnull)
1626 : : {
1627 : 130 : JsonUniqueParsingState *state = _state;
1628 : : JsonUniqueStackEntry *entry;
1629 : :
1630 [ - + ]: 130 : if (!state->unique)
380 alvherre@alvh.no-ip. 1631 :UBC 0 : return JSON_SUCCESS;
1632 : :
1633 : : /* find key collision in the current object */
380 alvherre@alvh.no-ip. 1634 [ + + ]:CBC 130 : if (json_unique_check_key(&state->check, field, state->stack->object_id))
1635 : 107 : return JSON_SUCCESS;
1636 : :
1637 : 23 : state->unique = false;
1638 : :
1639 : : /* pop all objects entries */
1640 [ + + ]: 56 : while ((entry = state->stack))
1641 : : {
1642 : 33 : state->stack = entry->parent;
1643 : 33 : pfree(entry);
1644 : : }
1645 : 23 : return JSON_SUCCESS;
1646 : : }
1647 : :
1648 : : /* Validate JSON text and additionally check key uniqueness */
1649 : : bool
1650 : 664 : json_validate(text *json, bool check_unique_keys, bool throw_error)
1651 : : {
1652 : : JsonLexContext lex;
1653 : 664 : JsonSemAction uniqueSemAction = {0};
1654 : : JsonUniqueParsingState state;
1655 : : JsonParseErrorType result;
1656 : :
192 alvherre@alvh.no-ip. 1657 :GNC 664 : makeJsonLexContext(&lex, json, check_unique_keys);
1658 : :
380 alvherre@alvh.no-ip. 1659 [ + + ]:CBC 664 : if (check_unique_keys)
1660 : : {
192 alvherre@alvh.no-ip. 1661 :GNC 116 : state.lex = &lex;
380 alvherre@alvh.no-ip. 1662 :CBC 116 : state.stack = NULL;
1663 : 116 : state.id_counter = 0;
1664 : 116 : state.unique = true;
1665 : 116 : json_unique_check_init(&state.check);
1666 : :
1667 : 116 : uniqueSemAction.semstate = &state;
1668 : 116 : uniqueSemAction.object_start = json_unique_object_start;
1669 : 116 : uniqueSemAction.object_field_start = json_unique_object_field_start;
1670 : 116 : uniqueSemAction.object_end = json_unique_object_end;
1671 : : }
1672 : :
192 alvherre@alvh.no-ip. 1673 [ + + ]:GNC 664 : result = pg_parse_json(&lex, check_unique_keys ? &uniqueSemAction : &nullSemAction);
1674 : :
380 alvherre@alvh.no-ip. 1675 [ + + ]:CBC 664 : if (result != JSON_SUCCESS)
1676 : : {
1677 [ - + ]: 108 : if (throw_error)
192 alvherre@alvh.no-ip. 1678 :UNC 0 : json_errsave_error(result, &lex, NULL);
1679 : :
380 alvherre@alvh.no-ip. 1680 :CBC 108 : return false; /* invalid json */
1681 : : }
1682 : :
1683 [ + + + + ]: 556 : if (check_unique_keys && !state.unique)
1684 : : {
1685 [ + + ]: 23 : if (throw_error)
380 alvherre@alvh.no-ip. 1686 [ + - ]:GBC 4 : ereport(ERROR,
1687 : : (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
1688 : : errmsg("duplicate JSON object key value")));
1689 : :
380 alvherre@alvh.no-ip. 1690 :CBC 19 : return false; /* not unique keys */
1691 : : }
1692 : :
192 alvherre@alvh.no-ip. 1693 [ + + ]:GNC 533 : if (check_unique_keys)
1694 : 81 : freeJsonLexContext(&lex);
1695 : :
380 alvherre@alvh.no-ip. 1696 :CBC 533 : return true; /* ok */
1697 : : }
1698 : :
1699 : : /*
1700 : : * SQL function json_typeof(json) -> text
1701 : : *
1702 : : * Returns the type of the outermost JSON value as TEXT. Possible types are
1703 : : * "object", "array", "string", "number", "boolean", and "null".
1704 : : *
1705 : : * Performs a single call to json_lex() to get the first token of the supplied
1706 : : * value. This initial token uniquely determines the value's type. As our
1707 : : * input must already have been validated by json_in() or json_recv(), the
1708 : : * initial token should never be JSON_TOKEN_OBJECT_END, JSON_TOKEN_ARRAY_END,
1709 : : * JSON_TOKEN_COLON, JSON_TOKEN_COMMA, or JSON_TOKEN_END.
1710 : : */
1711 : : Datum
3839 andrew@dunslane.net 1712 : 30 : json_typeof(PG_FUNCTION_ARGS)
1713 : : {
380 alvherre@alvh.no-ip. 1714 : 30 : text *json = PG_GETARG_TEXT_PP(0);
1715 : : JsonLexContext lex;
1716 : : char *type;
1717 : : JsonParseErrorType result;
1718 : :
1719 : : /* Lex exactly one token from the input and check its type. */
192 alvherre@alvh.no-ip. 1720 :GNC 30 : makeJsonLexContext(&lex, json, false);
1721 : 30 : result = json_lex(&lex);
591 andrew@dunslane.net 1722 [ - + ]:CBC 30 : if (result != JSON_SUCCESS)
192 alvherre@alvh.no-ip. 1723 :UNC 0 : json_errsave_error(result, &lex, NULL);
1724 : :
192 alvherre@alvh.no-ip. 1725 [ + + + + :GNC 30 : switch (lex.token_type)
+ + - ]
1726 : : {
3735 andrew@dunslane.net 1727 :CBC 6 : case JSON_TOKEN_OBJECT_START:
1728 : 6 : type = "object";
1729 : 6 : break;
1730 : 6 : case JSON_TOKEN_ARRAY_START:
1731 : 6 : type = "array";
1732 : 6 : break;
1733 : 3 : case JSON_TOKEN_STRING:
1734 : 3 : type = "string";
1735 : 3 : break;
1736 : 6 : case JSON_TOKEN_NUMBER:
1737 : 6 : type = "number";
1738 : 6 : break;
1739 : 6 : case JSON_TOKEN_TRUE:
1740 : : case JSON_TOKEN_FALSE:
1741 : 6 : type = "boolean";
1742 : 6 : break;
1743 : 3 : case JSON_TOKEN_NULL:
1744 : 3 : type = "null";
1745 : 3 : break;
3735 andrew@dunslane.net 1746 :UBC 0 : default:
192 alvherre@alvh.no-ip. 1747 [ # # ]:UNC 0 : elog(ERROR, "unexpected json token: %d", lex.token_type);
1748 : : }
1749 : :
3808 peter_e@gmx.net 1750 :CBC 30 : PG_RETURN_TEXT_P(cstring_to_text(type));
1751 : : }
|