Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * jsonfuncs.c
4 : : * Functions to process JSON data types.
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/utils/adt/jsonfuncs.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres.h"
16 : :
17 : : #include <limits.h>
18 : :
19 : : #include "access/htup_details.h"
20 : : #include "access/tupdesc.h"
21 : : #include "catalog/pg_proc.h"
22 : : #include "catalog/pg_type.h"
23 : : #include "common/int.h"
24 : : #include "common/jsonapi.h"
25 : : #include "common/string.h"
26 : : #include "fmgr.h"
27 : : #include "funcapi.h"
28 : : #include "lib/stringinfo.h"
29 : : #include "mb/pg_wchar.h"
30 : : #include "miscadmin.h"
31 : : #include "nodes/miscnodes.h"
32 : : #include "parser/parse_coerce.h"
33 : : #include "utils/array.h"
34 : : #include "utils/builtins.h"
35 : : #include "utils/fmgroids.h"
36 : : #include "utils/hsearch.h"
37 : : #include "utils/json.h"
38 : : #include "utils/jsonb.h"
39 : : #include "utils/jsonfuncs.h"
40 : : #include "utils/lsyscache.h"
41 : : #include "utils/memutils.h"
42 : : #include "utils/syscache.h"
43 : : #include "utils/tuplestore.h"
44 : : #include "utils/typcache.h"
45 : :
46 : : /* Operations available for setPath */
47 : : #define JB_PATH_CREATE 0x0001
48 : : #define JB_PATH_DELETE 0x0002
49 : : #define JB_PATH_REPLACE 0x0004
50 : : #define JB_PATH_INSERT_BEFORE 0x0008
51 : : #define JB_PATH_INSERT_AFTER 0x0010
52 : : #define JB_PATH_CREATE_OR_INSERT \
53 : : (JB_PATH_INSERT_BEFORE | JB_PATH_INSERT_AFTER | JB_PATH_CREATE)
54 : : #define JB_PATH_FILL_GAPS 0x0020
55 : : #define JB_PATH_CONSISTENT_POSITION 0x0040
56 : :
57 : : /* state for json_object_keys */
58 : : typedef struct OkeysState
59 : : {
60 : : JsonLexContext *lex;
61 : : char **result;
62 : : int result_size;
63 : : int result_count;
64 : : int sent_count;
65 : : } OkeysState;
66 : :
67 : : /* state for iterate_json_values function */
68 : : typedef struct IterateJsonStringValuesState
69 : : {
70 : : JsonLexContext *lex;
71 : : JsonIterateStringValuesAction action; /* an action that will be applied
72 : : * to each json value */
73 : : void *action_state; /* any necessary context for iteration */
74 : : uint32 flags; /* what kind of elements from a json we want
75 : : * to iterate */
76 : : } IterateJsonStringValuesState;
77 : :
78 : : /* state for transform_json_string_values function */
79 : : typedef struct TransformJsonStringValuesState
80 : : {
81 : : JsonLexContext *lex;
82 : : StringInfo strval; /* resulting json */
83 : : JsonTransformStringValuesAction action; /* an action that will be applied
84 : : * to each json value */
85 : : void *action_state; /* any necessary context for transformation */
86 : : } TransformJsonStringValuesState;
87 : :
88 : : /* state for json_get* functions */
89 : : typedef struct GetState
90 : : {
91 : : JsonLexContext *lex;
92 : : text *tresult;
93 : : const char *result_start;
94 : : bool normalize_results;
95 : : bool next_scalar;
96 : : int npath; /* length of each path-related array */
97 : : char **path_names; /* field name(s) being sought */
98 : : int *path_indexes; /* array index(es) being sought */
99 : : bool *pathok; /* is path matched to current depth? */
100 : : int *array_cur_index; /* current element index at each path
101 : : * level */
102 : : } GetState;
103 : :
104 : : /* state for json_array_length */
105 : : typedef struct AlenState
106 : : {
107 : : JsonLexContext *lex;
108 : : int count;
109 : : } AlenState;
110 : :
111 : : /* state for json_each */
112 : : typedef struct EachState
113 : : {
114 : : JsonLexContext *lex;
115 : : Tuplestorestate *tuple_store;
116 : : TupleDesc ret_tdesc;
117 : : MemoryContext tmp_cxt;
118 : : const char *result_start;
119 : : bool normalize_results;
120 : : bool next_scalar;
121 : : char *normalized_scalar;
122 : : } EachState;
123 : :
124 : : /* state for json_array_elements */
125 : : typedef struct ElementsState
126 : : {
127 : : JsonLexContext *lex;
128 : : const char *function_name;
129 : : Tuplestorestate *tuple_store;
130 : : TupleDesc ret_tdesc;
131 : : MemoryContext tmp_cxt;
132 : : const char *result_start;
133 : : bool normalize_results;
134 : : bool next_scalar;
135 : : char *normalized_scalar;
136 : : } ElementsState;
137 : :
138 : : /* state for get_json_object_as_hash */
139 : : typedef struct JHashState
140 : : {
141 : : JsonLexContext *lex;
142 : : const char *function_name;
143 : : HTAB *hash;
144 : : char *saved_scalar;
145 : : const char *save_json_start;
146 : : JsonTokenType saved_token_type;
147 : : } JHashState;
148 : :
149 : : /* hashtable element */
150 : : typedef struct JsonHashEntry
151 : : {
152 : : char fname[NAMEDATALEN]; /* hash key (MUST BE FIRST) */
153 : : char *val;
154 : : JsonTokenType type;
155 : : } JsonHashEntry;
156 : :
157 : : /* structure to cache type I/O metadata needed for populate_scalar() */
158 : : typedef struct ScalarIOData
159 : : {
160 : : Oid typioparam;
161 : : FmgrInfo typiofunc;
162 : : } ScalarIOData;
163 : :
164 : : /* these two structures are used recursively */
165 : : typedef struct ColumnIOData ColumnIOData;
166 : : typedef struct RecordIOData RecordIOData;
167 : :
168 : : /* structure to cache metadata needed for populate_array() */
169 : : typedef struct ArrayIOData
170 : : {
171 : : ColumnIOData *element_info; /* metadata cache */
172 : : Oid element_type; /* array element type id */
173 : : int32 element_typmod; /* array element type modifier */
174 : : } ArrayIOData;
175 : :
176 : : /* structure to cache metadata needed for populate_composite() */
177 : : typedef struct CompositeIOData
178 : : {
179 : : /*
180 : : * We use pointer to a RecordIOData here because variable-length struct
181 : : * RecordIOData can't be used directly in ColumnIOData.io union
182 : : */
183 : : RecordIOData *record_io; /* metadata cache for populate_record() */
184 : : TupleDesc tupdesc; /* cached tuple descriptor */
185 : : /* these fields differ from target type only if domain over composite: */
186 : : Oid base_typid; /* base type id */
187 : : int32 base_typmod; /* base type modifier */
188 : : /* this field is used only if target type is domain over composite: */
189 : : void *domain_info; /* opaque cache for domain checks */
190 : : } CompositeIOData;
191 : :
192 : : /* structure to cache metadata needed for populate_domain() */
193 : : typedef struct DomainIOData
194 : : {
195 : : ColumnIOData *base_io; /* metadata cache */
196 : : Oid base_typid; /* base type id */
197 : : int32 base_typmod; /* base type modifier */
198 : : void *domain_info; /* opaque cache for domain checks */
199 : : } DomainIOData;
200 : :
201 : : /* enumeration type categories */
202 : : typedef enum TypeCat
203 : : {
204 : : TYPECAT_SCALAR = 's',
205 : : TYPECAT_ARRAY = 'a',
206 : : TYPECAT_COMPOSITE = 'c',
207 : : TYPECAT_COMPOSITE_DOMAIN = 'C',
208 : : TYPECAT_DOMAIN = 'd',
209 : : } TypeCat;
210 : :
211 : : /* these two are stolen from hstore / record_out, used in populate_record* */
212 : :
213 : : /* structure to cache record metadata needed for populate_record_field() */
214 : : struct ColumnIOData
215 : : {
216 : : Oid typid; /* column type id */
217 : : int32 typmod; /* column type modifier */
218 : : TypeCat typcat; /* column type category */
219 : : ScalarIOData scalar_io; /* metadata cache for direct conversion
220 : : * through input function */
221 : : union
222 : : {
223 : : ArrayIOData array;
224 : : CompositeIOData composite;
225 : : DomainIOData domain;
226 : : } io; /* metadata cache for various column type
227 : : * categories */
228 : : };
229 : :
230 : : /* structure to cache record metadata needed for populate_record() */
231 : : struct RecordIOData
232 : : {
233 : : Oid record_type;
234 : : int32 record_typmod;
235 : : int ncolumns;
236 : : ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
237 : : };
238 : :
239 : : /* per-query cache for populate_record_worker and populate_recordset_worker */
240 : : typedef struct PopulateRecordCache
241 : : {
242 : : Oid argtype; /* declared type of the record argument */
243 : : ColumnIOData c; /* metadata cache for populate_composite() */
244 : : MemoryContext fn_mcxt; /* where this is stored */
245 : : } PopulateRecordCache;
246 : :
247 : : /* per-call state for populate_recordset */
248 : : typedef struct PopulateRecordsetState
249 : : {
250 : : JsonLexContext *lex;
251 : : const char *function_name;
252 : : HTAB *json_hash;
253 : : char *saved_scalar;
254 : : const char *save_json_start;
255 : : JsonTokenType saved_token_type;
256 : : Tuplestorestate *tuple_store;
257 : : HeapTupleHeader rec;
258 : : PopulateRecordCache *cache;
259 : : } PopulateRecordsetState;
260 : :
261 : : /* common data for populate_array_json() and populate_array_dim_jsonb() */
262 : : typedef struct PopulateArrayContext
263 : : {
264 : : ArrayBuildState *astate; /* array build state */
265 : : ArrayIOData *aio; /* metadata cache */
266 : : MemoryContext acxt; /* array build memory context */
267 : : MemoryContext mcxt; /* cache memory context */
268 : : const char *colname; /* for diagnostics only */
269 : : int *dims; /* dimensions */
270 : : int *sizes; /* current dimension counters */
271 : : int ndims; /* number of dimensions */
272 : : Node *escontext; /* For soft-error handling */
273 : : } PopulateArrayContext;
274 : :
275 : : /* state for populate_array_json() */
276 : : typedef struct PopulateArrayState
277 : : {
278 : : JsonLexContext *lex; /* json lexer */
279 : : PopulateArrayContext *ctx; /* context */
280 : : const char *element_start; /* start of the current array element */
281 : : char *element_scalar; /* current array element token if it is a
282 : : * scalar */
283 : : JsonTokenType element_type; /* current array element type */
284 : : } PopulateArrayState;
285 : :
286 : : /* state for json_strip_nulls */
287 : : typedef struct StripnullState
288 : : {
289 : : JsonLexContext *lex;
290 : : StringInfo strval;
291 : : bool skip_next_null;
292 : : bool strip_in_arrays;
293 : : } StripnullState;
294 : :
295 : : /* structure for generalized json/jsonb value passing */
296 : : typedef struct JsValue
297 : : {
298 : : bool is_json; /* json/jsonb */
299 : : union
300 : : {
301 : : struct
302 : : {
303 : : const char *str; /* json string */
304 : : int len; /* json string length or -1 if null-terminated */
305 : : JsonTokenType type; /* json type */
306 : : } json; /* json value */
307 : :
308 : : JsonbValue *jsonb; /* jsonb value */
309 : : } val;
310 : : } JsValue;
311 : :
312 : : typedef struct JsObject
313 : : {
314 : : bool is_json; /* json/jsonb */
315 : : union
316 : : {
317 : : HTAB *json_hash;
318 : : JsonbContainer *jsonb_cont;
319 : : } val;
320 : : } JsObject;
321 : :
322 : : /* useful macros for testing JsValue properties */
323 : : #define JsValueIsNull(jsv) \
324 : : ((jsv)->is_json ? \
325 : : (!(jsv)->val.json.str || (jsv)->val.json.type == JSON_TOKEN_NULL) : \
326 : : (!(jsv)->val.jsonb || (jsv)->val.jsonb->type == jbvNull))
327 : :
328 : : #define JsValueIsString(jsv) \
329 : : ((jsv)->is_json ? (jsv)->val.json.type == JSON_TOKEN_STRING \
330 : : : ((jsv)->val.jsonb && (jsv)->val.jsonb->type == jbvString))
331 : :
332 : : #define JsObjectIsEmpty(jso) \
333 : : ((jso)->is_json \
334 : : ? hash_get_num_entries((jso)->val.json_hash) == 0 \
335 : : : ((jso)->val.jsonb_cont == NULL || \
336 : : JsonContainerSize((jso)->val.jsonb_cont) == 0))
337 : :
338 : : #define JsObjectFree(jso) \
339 : : do { \
340 : : if ((jso)->is_json) \
341 : : hash_destroy((jso)->val.json_hash); \
342 : : } while (0)
343 : :
344 : : static int report_json_context(JsonLexContext *lex);
345 : :
346 : : /* semantic action functions for json_object_keys */
347 : : static JsonParseErrorType okeys_object_field_start(void *state, char *fname, bool isnull);
348 : : static JsonParseErrorType okeys_array_start(void *state);
349 : : static JsonParseErrorType okeys_scalar(void *state, char *token, JsonTokenType tokentype);
350 : :
351 : : /* semantic action functions for json_get* functions */
352 : : static JsonParseErrorType get_object_start(void *state);
353 : : static JsonParseErrorType get_object_end(void *state);
354 : : static JsonParseErrorType get_object_field_start(void *state, char *fname, bool isnull);
355 : : static JsonParseErrorType get_object_field_end(void *state, char *fname, bool isnull);
356 : : static JsonParseErrorType get_array_start(void *state);
357 : : static JsonParseErrorType get_array_end(void *state);
358 : : static JsonParseErrorType get_array_element_start(void *state, bool isnull);
359 : : static JsonParseErrorType get_array_element_end(void *state, bool isnull);
360 : : static JsonParseErrorType get_scalar(void *state, char *token, JsonTokenType tokentype);
361 : :
362 : : /* common worker function for json getter functions */
363 : : static Datum get_path_all(FunctionCallInfo fcinfo, bool as_text);
364 : : static text *get_worker(text *json, char **tpath, int *ipath, int npath,
365 : : bool normalize_results);
366 : : static Datum get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text);
367 : : static text *JsonbValueAsText(JsonbValue *v);
368 : :
369 : : /* semantic action functions for json_array_length */
370 : : static JsonParseErrorType alen_object_start(void *state);
371 : : static JsonParseErrorType alen_scalar(void *state, char *token, JsonTokenType tokentype);
372 : : static JsonParseErrorType alen_array_element_start(void *state, bool isnull);
373 : :
374 : : /* common workers for json{b}_each* functions */
375 : : static Datum each_worker(FunctionCallInfo fcinfo, bool as_text);
376 : : static Datum each_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname,
377 : : bool as_text);
378 : :
379 : : /* semantic action functions for json_each */
380 : : static JsonParseErrorType each_object_field_start(void *state, char *fname, bool isnull);
381 : : static JsonParseErrorType each_object_field_end(void *state, char *fname, bool isnull);
382 : : static JsonParseErrorType each_array_start(void *state);
383 : : static JsonParseErrorType each_scalar(void *state, char *token, JsonTokenType tokentype);
384 : :
385 : : /* common workers for json{b}_array_elements_* functions */
386 : : static Datum elements_worker(FunctionCallInfo fcinfo, const char *funcname,
387 : : bool as_text);
388 : : static Datum elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname,
389 : : bool as_text);
390 : :
391 : : /* semantic action functions for json_array_elements */
392 : : static JsonParseErrorType elements_object_start(void *state);
393 : : static JsonParseErrorType elements_array_element_start(void *state, bool isnull);
394 : : static JsonParseErrorType elements_array_element_end(void *state, bool isnull);
395 : : static JsonParseErrorType elements_scalar(void *state, char *token, JsonTokenType tokentype);
396 : :
397 : : /* turn a json object into a hash table */
398 : : static HTAB *get_json_object_as_hash(const char *json, int len, const char *funcname,
399 : : Node *escontext);
400 : :
401 : : /* semantic actions for populate_array_json */
402 : : static JsonParseErrorType populate_array_object_start(void *_state);
403 : : static JsonParseErrorType populate_array_array_end(void *_state);
404 : : static JsonParseErrorType populate_array_element_start(void *_state, bool isnull);
405 : : static JsonParseErrorType populate_array_element_end(void *_state, bool isnull);
406 : : static JsonParseErrorType populate_array_scalar(void *_state, char *token, JsonTokenType tokentype);
407 : :
408 : : /* semantic action functions for get_json_object_as_hash */
409 : : static JsonParseErrorType hash_object_field_start(void *state, char *fname, bool isnull);
410 : : static JsonParseErrorType hash_object_field_end(void *state, char *fname, bool isnull);
411 : : static JsonParseErrorType hash_array_start(void *state);
412 : : static JsonParseErrorType hash_scalar(void *state, char *token, JsonTokenType tokentype);
413 : :
414 : : /* semantic action functions for populate_recordset */
415 : : static JsonParseErrorType populate_recordset_object_field_start(void *state, char *fname, bool isnull);
416 : : static JsonParseErrorType populate_recordset_object_field_end(void *state, char *fname, bool isnull);
417 : : static JsonParseErrorType populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype);
418 : : static JsonParseErrorType populate_recordset_object_start(void *state);
419 : : static JsonParseErrorType populate_recordset_object_end(void *state);
420 : : static JsonParseErrorType populate_recordset_array_start(void *state);
421 : : static JsonParseErrorType populate_recordset_array_element_start(void *state, bool isnull);
422 : :
423 : : /* semantic action functions for json_strip_nulls */
424 : : static JsonParseErrorType sn_object_start(void *state);
425 : : static JsonParseErrorType sn_object_end(void *state);
426 : : static JsonParseErrorType sn_array_start(void *state);
427 : : static JsonParseErrorType sn_array_end(void *state);
428 : : static JsonParseErrorType sn_object_field_start(void *state, char *fname, bool isnull);
429 : : static JsonParseErrorType sn_array_element_start(void *state, bool isnull);
430 : : static JsonParseErrorType sn_scalar(void *state, char *token, JsonTokenType tokentype);
431 : :
432 : : /* worker functions for populate_record, to_record, populate_recordset and to_recordset */
433 : : static Datum populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
434 : : bool is_json, bool have_record_arg);
435 : : static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
436 : : bool is_json, bool have_record_arg,
437 : : Node *escontext);
438 : :
439 : : /* helper functions for populate_record[set] */
440 : : static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p,
441 : : HeapTupleHeader defaultval, MemoryContext mcxt,
442 : : JsObject *obj, Node *escontext);
443 : : static void get_record_type_from_argument(FunctionCallInfo fcinfo,
444 : : const char *funcname,
445 : : PopulateRecordCache *cache);
446 : : static void get_record_type_from_query(FunctionCallInfo fcinfo,
447 : : const char *funcname,
448 : : PopulateRecordCache *cache);
449 : : static bool JsValueToJsObject(JsValue *jsv, JsObject *jso, Node *escontext);
450 : : static Datum populate_composite(CompositeIOData *io, Oid typid,
451 : : const char *colname, MemoryContext mcxt,
452 : : HeapTupleHeader defaultval, JsValue *jsv, bool *isnull,
453 : : Node *escontext);
454 : : static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv,
455 : : bool *isnull, Node *escontext, bool omit_quotes);
456 : : static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod,
457 : : MemoryContext mcxt, bool need_scalar);
458 : : static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod,
459 : : const char *colname, MemoryContext mcxt, Datum defaultval,
460 : : JsValue *jsv, bool *isnull, Node *escontext,
461 : : bool omit_scalar_quotes);
462 : : static RecordIOData *allocate_record_info(MemoryContext mcxt, int ncolumns);
463 : : static bool JsObjectGetField(JsObject *obj, char *field, JsValue *jsv);
464 : : static void populate_recordset_record(PopulateRecordsetState *state, JsObject *obj);
465 : : static bool populate_array_json(PopulateArrayContext *ctx, const char *json, int len);
466 : : static bool populate_array_dim_jsonb(PopulateArrayContext *ctx, JsonbValue *jbv,
467 : : int ndim);
468 : : static void populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim);
469 : : static bool populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims);
470 : : static bool populate_array_check_dimension(PopulateArrayContext *ctx, int ndim);
471 : : static bool populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv);
472 : : static Datum populate_array(ArrayIOData *aio, const char *colname,
473 : : MemoryContext mcxt, JsValue *jsv,
474 : : bool *isnull,
475 : : Node *escontext);
476 : : static Datum populate_domain(DomainIOData *io, Oid typid, const char *colname,
477 : : MemoryContext mcxt, JsValue *jsv, bool *isnull,
478 : : Node *escontext, bool omit_quotes);
479 : :
480 : : /* functions supporting jsonb_delete, jsonb_set and jsonb_concat */
481 : : static void IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
482 : : JsonbInState *state);
483 : : static void setPath(JsonbIterator **it, const Datum *path_elems,
484 : : const bool *path_nulls, int path_len,
485 : : JsonbInState *st, int level, JsonbValue *newval,
486 : : int op_type);
487 : : static void setPathObject(JsonbIterator **it, const Datum *path_elems,
488 : : const bool *path_nulls, int path_len, JsonbInState *st,
489 : : int level,
490 : : JsonbValue *newval, uint32 npairs, int op_type);
491 : : static void setPathArray(JsonbIterator **it, const Datum *path_elems,
492 : : const bool *path_nulls, int path_len, JsonbInState *st,
493 : : int level,
494 : : JsonbValue *newval, uint32 nelems, int op_type);
495 : :
496 : : /* function supporting iterate_json_values */
497 : : static JsonParseErrorType iterate_values_scalar(void *state, char *token, JsonTokenType tokentype);
498 : : static JsonParseErrorType iterate_values_object_field_start(void *state, char *fname, bool isnull);
499 : :
500 : : /* functions supporting transform_json_string_values */
501 : : static JsonParseErrorType transform_string_values_object_start(void *state);
502 : : static JsonParseErrorType transform_string_values_object_end(void *state);
503 : : static JsonParseErrorType transform_string_values_array_start(void *state);
504 : : static JsonParseErrorType transform_string_values_array_end(void *state);
505 : : static JsonParseErrorType transform_string_values_object_field_start(void *state, char *fname, bool isnull);
506 : : static JsonParseErrorType transform_string_values_array_element_start(void *state, bool isnull);
507 : : static JsonParseErrorType transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype);
508 : :
509 : :
510 : : /*
511 : : * pg_parse_json_or_errsave
512 : : *
513 : : * This function is like pg_parse_json, except that it does not return a
514 : : * JsonParseErrorType. Instead, in case of any failure, this function will
515 : : * save error data into *escontext if that's an ErrorSaveContext, otherwise
516 : : * ereport(ERROR).
517 : : *
518 : : * Returns a boolean indicating success or failure (failure will only be
519 : : * returned when escontext is an ErrorSaveContext).
520 : : */
521 : : bool
637 heikki.linnakangas@i 522 :CBC 25400 : pg_parse_json_or_errsave(JsonLexContext *lex, const JsonSemAction *sem,
523 : : Node *escontext)
524 : : {
525 : : JsonParseErrorType result;
526 : :
2290 rhaas@postgresql.org 527 : 25400 : result = pg_parse_json(lex, sem);
528 [ + + ]: 25272 : if (result != JSON_SUCCESS)
529 : : {
1241 tgl@sss.pgh.pa.us 530 : 331 : json_errsave_error(result, lex, escontext);
531 : 36 : return false;
532 : : }
533 : 24941 : return true;
534 : : }
535 : :
536 : : /*
537 : : * makeJsonLexContext
538 : : *
539 : : * This is like makeJsonLexContextCstringLen, but it accepts a text value
540 : : * directly.
541 : : */
542 : : JsonLexContext *
943 alvherre@alvh.no-ip. 543 : 8177 : makeJsonLexContext(JsonLexContext *lex, text *json, bool need_escapes)
544 : : {
545 : : /*
546 : : * Most callers pass a detoasted datum, but it's not clear that they all
547 : : * do. pg_detoast_datum_packed() is cheap insurance.
548 : : */
1240 tgl@sss.pgh.pa.us 549 : 8177 : json = pg_detoast_datum_packed(json);
550 : :
943 alvherre@alvh.no-ip. 551 : 16354 : return makeJsonLexContextCstringLen(lex,
552 [ + + ]: 8177 : VARDATA_ANY(json),
2290 rhaas@postgresql.org 553 [ - + - - :ECB (5997) : VARSIZE_ANY_EXHDR(json),
- - - - +
+ ]
554 : : GetDatabaseEncoding(),
555 : : need_escapes);
556 : : }
557 : :
558 : : /*
559 : : * SQL function json_object_keys
560 : : *
561 : : * Returns the set of keys for the object argument.
562 : : *
563 : : * This SRF operates in value-per-call mode. It processes the
564 : : * object during the first call, and the keys are simply stashed
565 : : * in an array, whose size is expanded as necessary. This is probably
566 : : * safe enough for a list of keys of a single object, since they are
567 : : * limited in size to NAMEDATALEN and the number of keys is unlikely to
568 : : * be so huge that it has major memory implications.
569 : : */
570 : : Datum
4426 andrew@dunslane.net 571 :CBC 60 : jsonb_object_keys(PG_FUNCTION_ARGS)
572 : : {
573 : : FuncCallContext *funcctx;
574 : : OkeysState *state;
575 : :
576 [ + + ]: 60 : if (SRF_IS_FIRSTCALL())
577 : : {
578 : : MemoryContext oldcontext;
3151 tgl@sss.pgh.pa.us 579 : 20 : Jsonb *jb = PG_GETARG_JSONB_P(0);
4426 andrew@dunslane.net 580 : 20 : bool skipNested = false;
581 : : JsonbIterator *it;
582 : : JsonbValue v;
583 : : JsonbIteratorToken r;
584 : :
585 [ + + ]: 20 : if (JB_ROOT_IS_SCALAR(jb))
586 [ + - ]: 4 : ereport(ERROR,
587 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
588 : : errmsg("cannot call %s on a scalar",
589 : : "jsonb_object_keys")));
590 [ + + ]: 16 : else if (JB_ROOT_IS_ARRAY(jb))
591 [ + - ]: 4 : ereport(ERROR,
592 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
593 : : errmsg("cannot call %s on an array",
594 : : "jsonb_object_keys")));
595 : :
596 : 12 : funcctx = SRF_FIRSTCALL_INIT();
597 : 12 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
598 : :
146 michael@paquier.xyz 599 :GNC 12 : state = palloc_object(OkeysState);
600 : :
4426 andrew@dunslane.net 601 :CBC 12 : state->result_size = JB_ROOT_COUNT(jb);
602 : 12 : state->result_count = 0;
603 : 12 : state->sent_count = 0;
146 michael@paquier.xyz 604 :GNC 12 : state->result = palloc_array(char *, state->result_size);
605 : :
4381 heikki.linnakangas@i 606 :CBC 12 : it = JsonbIteratorInit(&jb->root);
607 : :
4426 andrew@dunslane.net 608 [ + + ]: 116 : while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
609 : : {
610 : 104 : skipNested = true;
611 : :
612 [ + + ]: 104 : if (r == WJB_KEY)
613 : : {
614 : : char *cstr;
615 : :
4416 tgl@sss.pgh.pa.us 616 : 40 : cstr = palloc(v.val.string.len + 1 * sizeof(char));
617 : 40 : memcpy(cstr, v.val.string.val, v.val.string.len);
618 : 40 : cstr[v.val.string.len] = '\0';
4426 andrew@dunslane.net 619 : 40 : state->result[state->result_count++] = cstr;
620 : : }
621 : : }
622 : :
623 : 12 : MemoryContextSwitchTo(oldcontext);
523 peter@eisentraut.org 624 : 12 : funcctx->user_fctx = state;
625 : : }
626 : :
4426 andrew@dunslane.net 627 : 52 : funcctx = SRF_PERCALL_SETUP();
628 : 52 : state = (OkeysState *) funcctx->user_fctx;
629 : :
630 [ + + ]: 52 : if (state->sent_count < state->result_count)
631 : : {
632 : 40 : char *nxt = state->result[state->sent_count++];
633 : :
634 : 40 : SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(nxt));
635 : : }
636 : :
637 : 12 : SRF_RETURN_DONE(funcctx);
638 : : }
639 : :
640 : : /*
641 : : * Report a JSON error.
642 : : */
643 : : void
1241 tgl@sss.pgh.pa.us 644 : 331 : json_errsave_error(JsonParseErrorType error, JsonLexContext *lex,
645 : : Node *escontext)
646 : : {
2290 rhaas@postgresql.org 647 [ + - + - ]: 331 : if (error == JSON_UNICODE_HIGH_ESCAPE ||
1241 tgl@sss.pgh.pa.us 648 [ + + ]: 331 : error == JSON_UNICODE_UNTRANSLATABLE ||
649 : : error == JSON_UNICODE_CODE_POINT_ZERO)
650 [ + - ]: 16 : errsave(escontext,
651 : : (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
652 : : errmsg("unsupported Unicode escape sequence"),
653 : : errdetail_internal("%s", json_errdetail(error, lex)),
654 : : report_json_context(lex)));
655 [ + + ]: 315 : else if (error == JSON_SEM_ACTION_FAILED)
656 : : {
657 : : /* semantic action function had better have reported something */
658 [ + - + - : 4 : if (!SOFT_ERROR_OCCURRED(escontext))
- + ]
1241 tgl@sss.pgh.pa.us 659 [ # # ]:UBC 0 : elog(ERROR, "JSON semantic action function did not provide error information");
660 : : }
661 : : else
1241 tgl@sss.pgh.pa.us 662 [ + + ]:CBC 311 : errsave(escontext,
663 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
664 : : errmsg("invalid input syntax for type %s", "json"),
665 : : errdetail_internal("%s", json_errdetail(error, lex)),
666 : : report_json_context(lex)));
2290 rhaas@postgresql.org 667 : 36 : }
668 : :
669 : : /*
670 : : * Report a CONTEXT line for bogus JSON input.
671 : : *
672 : : * lex->token_terminator must be set to identify the spot where we detected
673 : : * the error. Note that lex->token_start might be NULL, in case we recognized
674 : : * error at EOF.
675 : : *
676 : : * The return value isn't meaningful, but we make it non-void so that this
677 : : * can be invoked inside ereport().
678 : : */
679 : : static int
680 : 303 : report_json_context(JsonLexContext *lex)
681 : : {
682 : : const char *context_start;
683 : : const char *context_end;
684 : : const char *line_start;
685 : : char *ctxt;
686 : : int ctxtlen;
687 : : const char *prefix;
688 : : const char *suffix;
689 : :
690 : : /* Choose boundaries for the part of the input we will display */
1891 tgl@sss.pgh.pa.us 691 : 303 : line_start = lex->line_start;
692 : 303 : context_start = line_start;
2290 rhaas@postgresql.org 693 : 303 : context_end = lex->token_terminator;
1149 tgl@sss.pgh.pa.us 694 [ - + ]: 303 : Assert(context_end >= context_start);
695 : :
696 : : /* Advance until we are close enough to context_end */
1714 697 [ + + ]: 415 : while (context_end - context_start >= 50)
698 : : {
699 : : /* Advance to next multibyte character */
2290 rhaas@postgresql.org 700 [ + + ]: 112 : if (IS_HIGHBIT_SET(*context_start))
118 tmunro@postgresql.or 701 : 24 : context_start += pg_mblen_range(context_start, context_end);
702 : : else
2290 rhaas@postgresql.org 703 : 88 : context_start++;
704 : : }
705 : :
706 : : /*
707 : : * We add "..." to indicate that the excerpt doesn't start at the
708 : : * beginning of the line ... but if we're within 3 characters of the
709 : : * beginning of the line, we might as well just show the whole line.
710 : : */
711 [ + + ]: 303 : if (context_start - line_start <= 3)
712 : 291 : context_start = line_start;
713 : :
714 : : /* Get a null-terminated copy of the data to present */
715 : 303 : ctxtlen = context_end - context_start;
716 : 303 : ctxt = palloc(ctxtlen + 1);
717 : 303 : memcpy(ctxt, context_start, ctxtlen);
718 : 303 : ctxt[ctxtlen] = '\0';
719 : :
720 : : /*
721 : : * Show the context, prefixing "..." if not starting at start of line, and
722 : : * suffixing "..." if not ending at end of line.
723 : : */
724 [ + + ]: 303 : prefix = (context_start > line_start) ? "..." : "";
1714 tgl@sss.pgh.pa.us 725 : 869 : suffix = (lex->token_type != JSON_TOKEN_END &&
726 [ + + ]: 263 : context_end - lex->input < lex->input_length &&
727 [ + + + + : 566 : *context_end != '\n' && *context_end != '\r') ? "..." : "";
+ - ]
728 : :
2232 729 : 303 : return errcontext("JSON data, line %d: %s%s%s",
730 : : lex->line_number, prefix, ctxt, suffix);
731 : : }
732 : :
733 : :
734 : : Datum
4785 andrew@dunslane.net 735 : 1240 : json_object_keys(PG_FUNCTION_ARGS)
736 : : {
737 : : FuncCallContext *funcctx;
738 : : OkeysState *state;
739 : :
740 [ + + ]: 1240 : if (SRF_IS_FIRSTCALL())
741 : : {
3341 noah@leadboat.com 742 : 16 : text *json = PG_GETARG_TEXT_PP(0);
743 : : JsonLexContext lex;
744 : : JsonSemAction *sem;
745 : : MemoryContext oldcontext;
746 : :
4785 andrew@dunslane.net 747 : 16 : funcctx = SRF_FIRSTCALL_INIT();
748 : 16 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
749 : :
146 michael@paquier.xyz 750 :GNC 16 : state = palloc_object(OkeysState);
751 : 16 : sem = palloc0_object(JsonSemAction);
752 : :
943 alvherre@alvh.no-ip. 753 :CBC 16 : state->lex = makeJsonLexContext(&lex, json, true);
4785 andrew@dunslane.net 754 : 16 : state->result_size = 256;
755 : 16 : state->result_count = 0;
756 : 16 : state->sent_count = 0;
146 michael@paquier.xyz 757 :GNC 16 : state->result = palloc_array(char *, 256);
758 : :
523 peter@eisentraut.org 759 :CBC 16 : sem->semstate = state;
4785 andrew@dunslane.net 760 : 16 : sem->array_start = okeys_array_start;
761 : 16 : sem->scalar = okeys_scalar;
762 : 16 : sem->object_field_start = okeys_object_field_start;
763 : : /* remainder are all NULL, courtesy of palloc0 above */
764 : :
943 alvherre@alvh.no-ip. 765 : 16 : pg_parse_json_or_ereport(&lex, sem);
766 : : /* keys are now in state->result */
767 : :
768 : 8 : freeJsonLexContext(&lex);
4785 andrew@dunslane.net 769 : 8 : pfree(sem);
770 : :
771 : 8 : MemoryContextSwitchTo(oldcontext);
523 peter@eisentraut.org 772 : 8 : funcctx->user_fctx = state;
773 : : }
774 : :
4785 andrew@dunslane.net 775 : 1232 : funcctx = SRF_PERCALL_SETUP();
4672 peter_e@gmx.net 776 : 1232 : state = (OkeysState *) funcctx->user_fctx;
777 : :
4785 andrew@dunslane.net 778 [ + + ]: 1232 : if (state->sent_count < state->result_count)
779 : : {
780 : 1224 : char *nxt = state->result[state->sent_count++];
781 : :
782 : 1224 : SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(nxt));
783 : : }
784 : :
785 : 8 : SRF_RETURN_DONE(funcctx);
786 : : }
787 : :
788 : : static JsonParseErrorType
789 : 1228 : okeys_object_field_start(void *state, char *fname, bool isnull)
790 : : {
4672 peter_e@gmx.net 791 : 1228 : OkeysState *_state = (OkeysState *) state;
792 : :
793 : : /* only collecting keys for the top level object */
4785 andrew@dunslane.net 794 [ + + ]: 1228 : if (_state->lex->lex_level != 1)
1241 tgl@sss.pgh.pa.us 795 : 4 : return JSON_SUCCESS;
796 : :
797 : : /* enlarge result array if necessary */
4785 andrew@dunslane.net 798 [ + + ]: 1224 : if (_state->result_count >= _state->result_size)
799 : : {
800 : 4 : _state->result_size *= 2;
4332 tgl@sss.pgh.pa.us 801 : 4 : _state->result = (char **)
4785 andrew@dunslane.net 802 : 4 : repalloc(_state->result, sizeof(char *) * _state->result_size);
803 : : }
804 : :
805 : : /* save a copy of the field name */
806 : 1224 : _state->result[_state->result_count++] = pstrdup(fname);
807 : :
1241 tgl@sss.pgh.pa.us 808 : 1224 : return JSON_SUCCESS;
809 : : }
810 : :
811 : : static JsonParseErrorType
4785 andrew@dunslane.net 812 : 8 : okeys_array_start(void *state)
813 : : {
4672 peter_e@gmx.net 814 : 8 : OkeysState *_state = (OkeysState *) state;
815 : :
816 : : /* top level must be a json object */
4785 andrew@dunslane.net 817 [ + + ]: 8 : if (_state->lex->lex_level == 0)
818 [ + - ]: 4 : ereport(ERROR,
819 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
820 : : errmsg("cannot call %s on an array",
821 : : "json_object_keys")));
822 : :
1241 tgl@sss.pgh.pa.us 823 : 4 : return JSON_SUCCESS;
824 : : }
825 : :
826 : : static JsonParseErrorType
4785 andrew@dunslane.net 827 : 1236 : okeys_scalar(void *state, char *token, JsonTokenType tokentype)
828 : : {
4672 peter_e@gmx.net 829 : 1236 : OkeysState *_state = (OkeysState *) state;
830 : :
831 : : /* top level must be a json object */
4785 andrew@dunslane.net 832 [ + + ]: 1236 : if (_state->lex->lex_level == 0)
833 [ + - ]: 4 : ereport(ERROR,
834 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
835 : : errmsg("cannot call %s on a scalar",
836 : : "json_object_keys")));
837 : :
1241 tgl@sss.pgh.pa.us 838 : 1232 : return JSON_SUCCESS;
839 : : }
840 : :
841 : : /*
842 : : * json and jsonb getter functions
843 : : * these implement the -> ->> #> and #>> operators
844 : : * and the json{b?}_extract_path*(json, text, ...) functions
845 : : */
846 : :
847 : :
848 : : Datum
4785 andrew@dunslane.net 849 : 651 : json_object_field(PG_FUNCTION_ARGS)
850 : : {
3341 noah@leadboat.com 851 : 651 : text *json = PG_GETARG_TEXT_PP(0);
4274 tgl@sss.pgh.pa.us 852 : 651 : text *fname = PG_GETARG_TEXT_PP(1);
4785 andrew@dunslane.net 853 : 651 : char *fnamestr = text_to_cstring(fname);
854 : : text *result;
855 : :
4274 tgl@sss.pgh.pa.us 856 : 651 : result = get_worker(json, &fnamestr, NULL, 1, false);
857 : :
4785 andrew@dunslane.net 858 [ + + ]: 635 : if (result != NULL)
859 : 514 : PG_RETURN_TEXT_P(result);
860 : : else
861 : 121 : PG_RETURN_NULL();
862 : : }
863 : :
864 : : Datum
4426 865 : 16482 : jsonb_object_field(PG_FUNCTION_ARGS)
866 : : {
3151 tgl@sss.pgh.pa.us 867 : 16482 : Jsonb *jb = PG_GETARG_JSONB_P(0);
4356 andrew@dunslane.net 868 : 16482 : text *key = PG_GETARG_TEXT_PP(1);
869 : : JsonbValue *v;
870 : : JsonbValue vbuf;
871 : :
4274 tgl@sss.pgh.pa.us 872 [ + + ]: 16482 : if (!JB_ROOT_IS_OBJECT(jb))
873 : 18 : PG_RETURN_NULL();
874 : :
2419 alvherre@alvh.no-ip. 875 : 16464 : v = getKeyJsonValueFromContainer(&jb->root,
876 [ + + ]: 16464 : VARDATA_ANY(key),
877 [ - + - - : 16464 : VARSIZE_ANY_EXHDR(key),
- - - - +
+ ]
878 : : &vbuf);
879 : :
4356 andrew@dunslane.net 880 [ + + ]: 16464 : if (v != NULL)
3151 tgl@sss.pgh.pa.us 881 : 305 : PG_RETURN_JSONB_P(JsonbValueToJsonb(v));
882 : :
4426 andrew@dunslane.net 883 : 16159 : PG_RETURN_NULL();
884 : : }
885 : :
886 : : Datum
4785 887 : 621 : json_object_field_text(PG_FUNCTION_ARGS)
888 : : {
3341 noah@leadboat.com 889 : 621 : text *json = PG_GETARG_TEXT_PP(0);
4274 tgl@sss.pgh.pa.us 890 : 621 : text *fname = PG_GETARG_TEXT_PP(1);
4785 andrew@dunslane.net 891 : 621 : char *fnamestr = text_to_cstring(fname);
892 : : text *result;
893 : :
4274 tgl@sss.pgh.pa.us 894 : 621 : result = get_worker(json, &fnamestr, NULL, 1, true);
895 : :
4785 andrew@dunslane.net 896 [ + + ]: 617 : if (result != NULL)
897 : 588 : PG_RETURN_TEXT_P(result);
898 : : else
899 : 29 : PG_RETURN_NULL();
900 : : }
901 : :
902 : : Datum
4426 903 : 142 : jsonb_object_field_text(PG_FUNCTION_ARGS)
904 : : {
3151 tgl@sss.pgh.pa.us 905 : 142 : Jsonb *jb = PG_GETARG_JSONB_P(0);
4356 andrew@dunslane.net 906 : 142 : text *key = PG_GETARG_TEXT_PP(1);
907 : : JsonbValue *v;
908 : : JsonbValue vbuf;
909 : :
4274 tgl@sss.pgh.pa.us 910 [ + + ]: 142 : if (!JB_ROOT_IS_OBJECT(jb))
911 : 18 : PG_RETURN_NULL();
912 : :
2419 alvherre@alvh.no-ip. 913 : 124 : v = getKeyJsonValueFromContainer(&jb->root,
914 [ - + ]: 124 : VARDATA_ANY(key),
915 [ - + - - : 124 : VARSIZE_ANY_EXHDR(key),
- - - - -
+ ]
916 : : &vbuf);
917 : :
918 [ + + + + ]: 124 : if (v != NULL && v->type != jbvNull)
919 : 100 : PG_RETURN_TEXT_P(JsonbValueAsText(v));
920 : :
4426 andrew@dunslane.net 921 : 24 : PG_RETURN_NULL();
922 : : }
923 : :
924 : : Datum
4785 925 : 185 : json_array_element(PG_FUNCTION_ARGS)
926 : : {
3341 noah@leadboat.com 927 : 185 : text *json = PG_GETARG_TEXT_PP(0);
4785 andrew@dunslane.net 928 : 185 : int element = PG_GETARG_INT32(1);
929 : : text *result;
930 : :
4274 tgl@sss.pgh.pa.us 931 : 185 : result = get_worker(json, NULL, &element, 1, false);
932 : :
4785 andrew@dunslane.net 933 [ + + ]: 185 : if (result != NULL)
934 : 157 : PG_RETURN_TEXT_P(result);
935 : : else
936 : 28 : PG_RETURN_NULL();
937 : : }
938 : :
939 : : Datum
4426 940 : 233 : jsonb_array_element(PG_FUNCTION_ARGS)
941 : : {
3151 tgl@sss.pgh.pa.us 942 : 233 : Jsonb *jb = PG_GETARG_JSONB_P(0);
4426 andrew@dunslane.net 943 : 233 : int element = PG_GETARG_INT32(1);
944 : : JsonbValue *v;
945 : :
4274 tgl@sss.pgh.pa.us 946 [ + + ]: 233 : if (!JB_ROOT_IS_ARRAY(jb))
947 : 14 : PG_RETURN_NULL();
948 : :
949 : : /* Handle negative subscript */
3945 andrew@dunslane.net 950 [ + + ]: 219 : if (element < 0)
951 : : {
3617 rhaas@postgresql.org 952 : 20 : uint32 nelements = JB_ROOT_COUNT(jb);
953 : :
627 nathan@postgresql.or 954 [ + + ]: 20 : if (pg_abs_s32(element) > nelements)
3945 andrew@dunslane.net 955 : 10 : PG_RETURN_NULL();
956 : : else
957 : 10 : element += nelements;
958 : : }
959 : :
4356 960 : 209 : v = getIthJsonbValueFromContainer(&jb->root, element);
961 [ + + ]: 209 : if (v != NULL)
3151 tgl@sss.pgh.pa.us 962 : 186 : PG_RETURN_JSONB_P(JsonbValueToJsonb(v));
963 : :
4426 andrew@dunslane.net 964 : 23 : PG_RETURN_NULL();
965 : : }
966 : :
967 : : Datum
4785 968 : 36 : json_array_element_text(PG_FUNCTION_ARGS)
969 : : {
3341 noah@leadboat.com 970 : 36 : text *json = PG_GETARG_TEXT_PP(0);
4785 andrew@dunslane.net 971 : 36 : int element = PG_GETARG_INT32(1);
972 : : text *result;
973 : :
4274 tgl@sss.pgh.pa.us 974 : 36 : result = get_worker(json, NULL, &element, 1, true);
975 : :
4785 andrew@dunslane.net 976 [ + + ]: 36 : if (result != NULL)
977 : 17 : PG_RETURN_TEXT_P(result);
978 : : else
979 : 19 : PG_RETURN_NULL();
980 : : }
981 : :
982 : : Datum
4426 983 : 49 : jsonb_array_element_text(PG_FUNCTION_ARGS)
984 : : {
3151 tgl@sss.pgh.pa.us 985 : 49 : Jsonb *jb = PG_GETARG_JSONB_P(0);
4426 andrew@dunslane.net 986 : 49 : int element = PG_GETARG_INT32(1);
987 : : JsonbValue *v;
988 : :
4274 tgl@sss.pgh.pa.us 989 [ + + ]: 49 : if (!JB_ROOT_IS_ARRAY(jb))
990 : 9 : PG_RETURN_NULL();
991 : :
992 : : /* Handle negative subscript */
3945 andrew@dunslane.net 993 [ + + ]: 40 : if (element < 0)
994 : : {
3617 rhaas@postgresql.org 995 : 5 : uint32 nelements = JB_ROOT_COUNT(jb);
996 : :
627 nathan@postgresql.or 997 [ + - ]: 5 : if (pg_abs_s32(element) > nelements)
3945 andrew@dunslane.net 998 : 5 : PG_RETURN_NULL();
999 : : else
3945 andrew@dunslane.net 1000 :UBC 0 : element += nelements;
1001 : : }
1002 : :
4356 andrew@dunslane.net 1003 :CBC 35 : v = getIthJsonbValueFromContainer(&jb->root, element);
1004 : :
2419 alvherre@alvh.no-ip. 1005 [ + + + + ]: 35 : if (v != NULL && v->type != jbvNull)
1006 : 17 : PG_RETURN_TEXT_P(JsonbValueAsText(v));
1007 : :
4426 andrew@dunslane.net 1008 : 18 : PG_RETURN_NULL();
1009 : : }
1010 : :
1011 : : Datum
4785 1012 : 222 : json_extract_path(PG_FUNCTION_ARGS)
1013 : : {
4274 tgl@sss.pgh.pa.us 1014 : 222 : return get_path_all(fcinfo, false);
1015 : : }
1016 : :
1017 : : Datum
4785 andrew@dunslane.net 1018 : 150 : json_extract_path_text(PG_FUNCTION_ARGS)
1019 : : {
4274 tgl@sss.pgh.pa.us 1020 : 150 : return get_path_all(fcinfo, true);
1021 : : }
1022 : :
1023 : : /*
1024 : : * common routine for extract_path functions
1025 : : */
1026 : : static Datum
1027 : 372 : get_path_all(FunctionCallInfo fcinfo, bool as_text)
1028 : : {
3341 noah@leadboat.com 1029 : 372 : text *json = PG_GETARG_TEXT_PP(0);
4785 andrew@dunslane.net 1030 : 372 : ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
1031 : : text *result;
1032 : : Datum *pathtext;
1033 : : bool *pathnulls;
1034 : : int npath;
1035 : : char **tpath;
1036 : : int *ipath;
1037 : : int i;
1038 : :
1039 : : /*
1040 : : * If the array contains any null elements, return NULL, on the grounds
1041 : : * that you'd have gotten NULL if any RHS value were NULL in a nested
1042 : : * series of applications of the -> operator. (Note: because we also
1043 : : * return NULL for error cases such as no-such-field, this is true
1044 : : * regardless of the contents of the rest of the array.)
1045 : : */
1046 [ + + ]: 372 : if (array_contains_nulls(path))
4274 tgl@sss.pgh.pa.us 1047 : 10 : PG_RETURN_NULL();
1048 : :
1404 peter@eisentraut.org 1049 : 362 : deconstruct_array_builtin(path, TEXTOID, &pathtext, &pathnulls, &npath);
1050 : :
146 michael@paquier.xyz 1051 :GNC 362 : tpath = palloc_array(char *, npath);
1052 : 362 : ipath = palloc_array(int, npath);
1053 : :
4785 andrew@dunslane.net 1054 [ + + ]:CBC 986 : for (i = 0; i < npath; i++)
1055 : : {
4274 tgl@sss.pgh.pa.us 1056 [ - + ]: 624 : Assert(!pathnulls[i]);
4785 andrew@dunslane.net 1057 : 624 : tpath[i] = TextDatumGetCString(pathtext[i]);
1058 : :
1059 : : /*
1060 : : * we have no idea at this stage what structure the document is so
1061 : : * just convert anything in the path that we can to an integer and set
1062 : : * all the other integers to INT_MIN which will never match.
1063 : : */
4274 tgl@sss.pgh.pa.us 1064 [ + + ]: 624 : if (*tpath[i] != '\0')
1065 : : {
1066 : : int ind;
1067 : : char *endptr;
1068 : :
1069 : 614 : errno = 0;
1909 1070 : 614 : ind = strtoint(tpath[i], &endptr, 10);
1071 [ + + + - : 614 : if (endptr == tpath[i] || *endptr != '\0' || errno != 0)
- + ]
3945 andrew@dunslane.net 1072 : 452 : ipath[i] = INT_MIN;
1073 : : else
1909 tgl@sss.pgh.pa.us 1074 : 162 : ipath[i] = ind;
1075 : : }
1076 : : else
3945 andrew@dunslane.net 1077 : 10 : ipath[i] = INT_MIN;
1078 : : }
1079 : :
4274 tgl@sss.pgh.pa.us 1080 : 362 : result = get_worker(json, tpath, ipath, npath, as_text);
1081 : :
4785 andrew@dunslane.net 1082 [ + + ]: 362 : if (result != NULL)
4382 bruce@momjian.us 1083 : 262 : PG_RETURN_TEXT_P(result);
1084 : : else
4785 andrew@dunslane.net 1085 : 100 : PG_RETURN_NULL();
1086 : : }
1087 : :
1088 : : /*
1089 : : * get_worker
1090 : : *
1091 : : * common worker for all the json getter functions
1092 : : *
1093 : : * json: JSON object (in text form)
1094 : : * tpath[]: field name(s) to extract
1095 : : * ipath[]: array index(es) (zero-based) to extract, accepts negatives
1096 : : * npath: length of tpath[] and/or ipath[]
1097 : : * normalize_results: true to de-escape string and null scalars
1098 : : *
1099 : : * tpath can be NULL, or any one tpath[] entry can be NULL, if an object
1100 : : * field is not to be matched at that nesting level. Similarly, ipath can
1101 : : * be NULL, or any one ipath[] entry can be INT_MIN if an array element is
1102 : : * not to be matched at that nesting level (a json datum should never be
1103 : : * large enough to have -INT_MIN elements due to MaxAllocSize restriction).
1104 : : */
1105 : : static text *
1106 : 1855 : get_worker(text *json,
1107 : : char **tpath,
1108 : : int *ipath,
1109 : : int npath,
1110 : : bool normalize_results)
1111 : : {
146 michael@paquier.xyz 1112 :GNC 1855 : JsonSemAction *sem = palloc0_object(JsonSemAction);
1113 : 1855 : GetState *state = palloc0_object(GetState);
1114 : :
4274 tgl@sss.pgh.pa.us 1115 [ - + ]:CBC 1855 : Assert(npath >= 0);
1116 : :
943 alvherre@alvh.no-ip. 1117 : 1855 : state->lex = makeJsonLexContext(NULL, json, true);
1118 : :
1119 : : /* is it "_as_text" variant? */
4785 andrew@dunslane.net 1120 : 1855 : state->normalize_results = normalize_results;
4274 tgl@sss.pgh.pa.us 1121 : 1855 : state->npath = npath;
1122 : 1855 : state->path_names = tpath;
1123 : 1855 : state->path_indexes = ipath;
146 michael@paquier.xyz 1124 :GNC 1855 : state->pathok = palloc0_array(bool, npath);
1125 : 1855 : state->array_cur_index = palloc_array(int, npath);
1126 : :
4274 tgl@sss.pgh.pa.us 1127 [ + + ]:CBC 1855 : if (npath > 0)
4785 andrew@dunslane.net 1128 : 1805 : state->pathok[0] = true;
1129 : :
523 peter@eisentraut.org 1130 : 1855 : sem->semstate = state;
1131 : :
1132 : : /*
1133 : : * Not all variants need all the semantic routines. Only set the ones that
1134 : : * are actually needed for maximum efficiency.
1135 : : */
4785 andrew@dunslane.net 1136 : 1855 : sem->scalar = get_scalar;
4274 tgl@sss.pgh.pa.us 1137 [ + + ]: 1855 : if (npath == 0)
1138 : : {
1139 : 50 : sem->object_start = get_object_start;
1140 : 50 : sem->object_end = get_object_end;
1141 : 50 : sem->array_start = get_array_start;
1142 : 50 : sem->array_end = get_array_end;
1143 : : }
1144 [ + + ]: 1855 : if (tpath != NULL)
1145 : : {
4785 andrew@dunslane.net 1146 : 1634 : sem->object_field_start = get_object_field_start;
1147 : 1634 : sem->object_field_end = get_object_field_end;
1148 : : }
4274 tgl@sss.pgh.pa.us 1149 [ + + ]: 1855 : if (ipath != NULL)
1150 : : {
1151 : 583 : sem->array_start = get_array_start;
4785 andrew@dunslane.net 1152 : 583 : sem->array_element_start = get_array_element_start;
1153 : 583 : sem->array_element_end = get_array_element_end;
1154 : : }
1155 : :
943 alvherre@alvh.no-ip. 1156 : 1855 : pg_parse_json_or_ereport(state->lex, sem);
1157 : 1835 : freeJsonLexContext(state->lex);
1158 : :
4785 andrew@dunslane.net 1159 : 1835 : return state->tresult;
1160 : : }
1161 : :
1162 : : static JsonParseErrorType
1163 : 30 : get_object_start(void *state)
1164 : : {
4672 peter_e@gmx.net 1165 : 30 : GetState *_state = (GetState *) state;
4274 tgl@sss.pgh.pa.us 1166 : 30 : int lex_level = _state->lex->lex_level;
1167 : :
1168 [ + + + - ]: 30 : if (lex_level == 0 && _state->npath == 0)
1169 : : {
1170 : : /*
1171 : : * Special case: we should match the entire object. We only need this
1172 : : * at outermost level because at nested levels the match will have
1173 : : * been started by the outer field or array element callback.
1174 : : */
1175 : 10 : _state->result_start = _state->lex->token_start;
1176 : : }
1177 : :
1241 1178 : 30 : return JSON_SUCCESS;
1179 : : }
1180 : :
1181 : : static JsonParseErrorType
4274 1182 : 30 : get_object_end(void *state)
1183 : : {
4672 peter_e@gmx.net 1184 : 30 : GetState *_state = (GetState *) state;
4785 andrew@dunslane.net 1185 : 30 : int lex_level = _state->lex->lex_level;
1186 : :
4274 tgl@sss.pgh.pa.us 1187 [ + + + - ]: 30 : if (lex_level == 0 && _state->npath == 0)
1188 : : {
1189 : : /* Special case: return the entire object */
683 peter@eisentraut.org 1190 : 10 : const char *start = _state->result_start;
4274 tgl@sss.pgh.pa.us 1191 : 10 : int len = _state->lex->prev_token_terminator - start;
1192 : :
1193 : 10 : _state->tresult = cstring_to_text_with_len(start, len);
1194 : : }
1195 : :
1241 1196 : 30 : return JSON_SUCCESS;
1197 : : }
1198 : :
1199 : : static JsonParseErrorType
4274 1200 : 118166 : get_object_field_start(void *state, char *fname, bool isnull)
1201 : : {
1202 : 118166 : GetState *_state = (GetState *) state;
1203 : 118166 : bool get_next = false;
1204 : 118166 : int lex_level = _state->lex->lex_level;
1205 : :
1206 [ + + ]: 118166 : if (lex_level <= _state->npath &&
1207 [ + + ]: 30006 : _state->pathok[lex_level - 1] &&
1208 [ + - ]: 29806 : _state->path_names != NULL &&
1209 [ + - ]: 29806 : _state->path_names[lex_level - 1] != NULL &&
1210 [ + + ]: 29806 : strcmp(fname, _state->path_names[lex_level - 1]) == 0)
1211 : : {
4785 andrew@dunslane.net 1212 [ + + ]: 1477 : if (lex_level < _state->npath)
1213 : : {
1214 : : /* if not at end of path just mark path ok */
1215 : 180 : _state->pathok[lex_level] = true;
1216 : : }
1217 : : else
1218 : : {
1219 : : /* end of path, so we want this value */
1220 : 1297 : get_next = true;
1221 : : }
1222 : : }
1223 : :
1224 [ + + ]: 118166 : if (get_next)
1225 : : {
1226 : : /* this object overrides any previous matching object */
4274 tgl@sss.pgh.pa.us 1227 : 1297 : _state->tresult = NULL;
1228 : 1297 : _state->result_start = NULL;
1229 : :
4785 andrew@dunslane.net 1230 [ + + ]: 1297 : if (_state->normalize_results &&
1231 [ + + ]: 652 : _state->lex->token_type == JSON_TOKEN_STRING)
1232 : : {
1233 : : /* for as_text variants, tell get_scalar to set it for us */
1234 : 457 : _state->next_scalar = true;
1235 : : }
1236 : : else
1237 : : {
1238 : : /* for non-as_text variants, just note the json starting point */
1239 : 840 : _state->result_start = _state->lex->token_start;
1240 : : }
1241 : : }
1242 : :
1241 tgl@sss.pgh.pa.us 1243 : 118166 : return JSON_SUCCESS;
1244 : : }
1245 : :
1246 : : static JsonParseErrorType
4785 andrew@dunslane.net 1247 : 118166 : get_object_field_end(void *state, char *fname, bool isnull)
1248 : : {
4672 peter_e@gmx.net 1249 : 118166 : GetState *_state = (GetState *) state;
4785 andrew@dunslane.net 1250 : 118166 : bool get_last = false;
1251 : 118166 : int lex_level = _state->lex->lex_level;
1252 : :
1253 : : /* same tests as in get_object_field_start */
4274 tgl@sss.pgh.pa.us 1254 [ + + ]: 118166 : if (lex_level <= _state->npath &&
1255 [ + + ]: 30006 : _state->pathok[lex_level - 1] &&
1256 [ + - ]: 29806 : _state->path_names != NULL &&
1257 [ + - ]: 29806 : _state->path_names[lex_level - 1] != NULL &&
1258 [ + + ]: 29806 : strcmp(fname, _state->path_names[lex_level - 1]) == 0)
1259 : : {
4785 andrew@dunslane.net 1260 [ + + ]: 1477 : if (lex_level < _state->npath)
1261 : : {
1262 : : /* done with this field so reset pathok */
1263 : 180 : _state->pathok[lex_level] = false;
1264 : : }
1265 : : else
1266 : : {
1267 : : /* end of path, so we want this value */
1268 : 1297 : get_last = true;
1269 : : }
1270 : : }
1271 : :
1272 : : /* for as_text scalar case, our work is already done */
1273 [ + + + + ]: 118166 : if (get_last && _state->result_start != NULL)
1274 : : {
1275 : : /*
1276 : : * make a text object from the string from the previously noted json
1277 : : * start up to the end of the previous token (the lexer is by now
1278 : : * ahead of us on whatever came after what we're interested in).
1279 : : */
1280 [ + + + + ]: 840 : if (isnull && _state->normalize_results)
1281 : 19 : _state->tresult = (text *) NULL;
1282 : : else
1283 : : {
683 peter@eisentraut.org 1284 : 821 : const char *start = _state->result_start;
4274 tgl@sss.pgh.pa.us 1285 : 821 : int len = _state->lex->prev_token_terminator - start;
1286 : :
1287 : 821 : _state->tresult = cstring_to_text_with_len(start, len);
1288 : : }
1289 : :
1290 : : /* this should be unnecessary but let's do it for cleanliness: */
1291 : 840 : _state->result_start = NULL;
1292 : : }
1293 : :
1241 1294 : 118166 : return JSON_SUCCESS;
1295 : : }
1296 : :
1297 : : static JsonParseErrorType
4785 andrew@dunslane.net 1298 : 1245 : get_array_start(void *state)
1299 : : {
4672 peter_e@gmx.net 1300 : 1245 : GetState *_state = (GetState *) state;
4785 andrew@dunslane.net 1301 : 1245 : int lex_level = _state->lex->lex_level;
1302 : :
4274 tgl@sss.pgh.pa.us 1303 [ + + ]: 1245 : if (lex_level < _state->npath)
1304 : : {
1305 : : /* Initialize counting of elements in this array */
1306 : 360 : _state->array_cur_index[lex_level] = -1;
1307 : :
1308 : : /* INT_MIN value is reserved to represent invalid subscript */
3934 andrew@dunslane.net 1309 [ + + ]: 360 : if (_state->path_indexes[lex_level] < 0 &&
1310 [ + + ]: 24 : _state->path_indexes[lex_level] != INT_MIN)
1311 : : {
1312 : : /* Negative subscript -- convert to positive-wise subscript */
1313 : : JsonParseErrorType error;
1314 : : int nelements;
1315 : :
2290 rhaas@postgresql.org 1316 : 4 : error = json_count_array_elements(_state->lex, &nelements);
1317 [ - + ]: 4 : if (error != JSON_SUCCESS)
1241 tgl@sss.pgh.pa.us 1318 :UBC 0 : json_errsave_error(error, _state->lex, NULL);
1319 : :
3934 andrew@dunslane.net 1320 [ + - ]:CBC 4 : if (-_state->path_indexes[lex_level] <= nelements)
1321 : 4 : _state->path_indexes[lex_level] += nelements;
1322 : : }
1323 : : }
4274 tgl@sss.pgh.pa.us 1324 [ + + + - ]: 885 : else if (lex_level == 0 && _state->npath == 0)
1325 : : {
1326 : : /*
1327 : : * Special case: we should match the entire array. We only need this
1328 : : * at the outermost level because at nested levels the match will have
1329 : : * been started by the outer field or array element callback.
1330 : : */
1331 : 10 : _state->result_start = _state->lex->token_start;
1332 : : }
1333 : :
1241 1334 : 1245 : return JSON_SUCCESS;
1335 : : }
1336 : :
1337 : : static JsonParseErrorType
4274 1338 : 10 : get_array_end(void *state)
1339 : : {
1340 : 10 : GetState *_state = (GetState *) state;
1341 : 10 : int lex_level = _state->lex->lex_level;
1342 : :
1343 [ + - + - ]: 10 : if (lex_level == 0 && _state->npath == 0)
1344 : : {
1345 : : /* Special case: return the entire array */
683 peter@eisentraut.org 1346 : 10 : const char *start = _state->result_start;
4274 tgl@sss.pgh.pa.us 1347 : 10 : int len = _state->lex->prev_token_terminator - start;
1348 : :
1349 : 10 : _state->tresult = cstring_to_text_with_len(start, len);
1350 : : }
1351 : :
1241 1352 : 10 : return JSON_SUCCESS;
1353 : : }
1354 : :
1355 : : static JsonParseErrorType
4785 andrew@dunslane.net 1356 : 1340 : get_array_element_start(void *state, bool isnull)
1357 : : {
4672 peter_e@gmx.net 1358 : 1340 : GetState *_state = (GetState *) state;
4785 andrew@dunslane.net 1359 : 1340 : bool get_next = false;
1360 : 1340 : int lex_level = _state->lex->lex_level;
1361 : :
1362 : : /* Update array element counter */
4274 tgl@sss.pgh.pa.us 1363 [ + + ]: 1340 : if (lex_level <= _state->npath)
1364 : 696 : _state->array_cur_index[lex_level - 1]++;
1365 : :
1366 [ + + ]: 1340 : if (lex_level <= _state->npath &&
1367 [ + - ]: 696 : _state->pathok[lex_level - 1] &&
1368 [ + - ]: 696 : _state->path_indexes != NULL &&
1369 [ + + ]: 696 : _state->array_cur_index[lex_level - 1] == _state->path_indexes[lex_level - 1])
1370 : : {
1371 [ + + ]: 330 : if (lex_level < _state->npath)
1372 : : {
1373 : : /* if not at end of path just mark path ok */
1374 : 102 : _state->pathok[lex_level] = true;
1375 : : }
1376 : : else
1377 : : {
1378 : : /* end of path, so we want this value */
1379 : 228 : get_next = true;
1380 : : }
1381 : : }
1382 : :
1383 : : /* same logic as for objects */
4785 andrew@dunslane.net 1384 [ + + ]: 1340 : if (get_next)
1385 : : {
4274 tgl@sss.pgh.pa.us 1386 : 228 : _state->tresult = NULL;
1387 : 228 : _state->result_start = NULL;
1388 : :
4785 andrew@dunslane.net 1389 [ + + ]: 228 : if (_state->normalize_results &&
1390 [ + + ]: 46 : _state->lex->token_type == JSON_TOKEN_STRING)
1391 : : {
1392 : 14 : _state->next_scalar = true;
1393 : : }
1394 : : else
1395 : : {
1396 : 214 : _state->result_start = _state->lex->token_start;
1397 : : }
1398 : : }
1399 : :
1241 tgl@sss.pgh.pa.us 1400 : 1340 : return JSON_SUCCESS;
1401 : : }
1402 : :
1403 : : static JsonParseErrorType
4785 andrew@dunslane.net 1404 : 1340 : get_array_element_end(void *state, bool isnull)
1405 : : {
4672 peter_e@gmx.net 1406 : 1340 : GetState *_state = (GetState *) state;
4785 andrew@dunslane.net 1407 : 1340 : bool get_last = false;
1408 : 1340 : int lex_level = _state->lex->lex_level;
1409 : :
1410 : : /* same tests as in get_array_element_start */
4274 tgl@sss.pgh.pa.us 1411 [ + + ]: 1340 : if (lex_level <= _state->npath &&
1412 [ + - ]: 696 : _state->pathok[lex_level - 1] &&
1413 [ + - ]: 696 : _state->path_indexes != NULL &&
1414 [ + + ]: 696 : _state->array_cur_index[lex_level - 1] == _state->path_indexes[lex_level - 1])
1415 : : {
4785 andrew@dunslane.net 1416 [ + + ]: 330 : if (lex_level < _state->npath)
1417 : : {
1418 : : /* done with this element so reset pathok */
1419 : 102 : _state->pathok[lex_level] = false;
1420 : : }
1421 : : else
1422 : : {
1423 : : /* end of path, so we want this value */
1424 : 228 : get_last = true;
1425 : : }
1426 : : }
1427 : :
1428 : : /* same logic as for objects */
1429 [ + + + + ]: 1340 : if (get_last && _state->result_start != NULL)
1430 : : {
1431 [ + + + + ]: 214 : if (isnull && _state->normalize_results)
1432 : 9 : _state->tresult = (text *) NULL;
1433 : : else
1434 : : {
683 peter@eisentraut.org 1435 : 205 : const char *start = _state->result_start;
4274 tgl@sss.pgh.pa.us 1436 : 205 : int len = _state->lex->prev_token_terminator - start;
1437 : :
1438 : 205 : _state->tresult = cstring_to_text_with_len(start, len);
1439 : : }
1440 : :
1441 : 214 : _state->result_start = NULL;
1442 : : }
1443 : :
1241 1444 : 1340 : return JSON_SUCCESS;
1445 : : }
1446 : :
1447 : : static JsonParseErrorType
4785 andrew@dunslane.net 1448 : 120086 : get_scalar(void *state, char *token, JsonTokenType tokentype)
1449 : : {
4672 peter_e@gmx.net 1450 : 120086 : GetState *_state = (GetState *) state;
4274 tgl@sss.pgh.pa.us 1451 : 120086 : int lex_level = _state->lex->lex_level;
1452 : :
1453 : : /* Check for whole-object match */
1454 [ + + + + ]: 120086 : if (lex_level == 0 && _state->npath == 0)
1455 : : {
1456 [ + + + + ]: 30 : if (_state->normalize_results && tokentype == JSON_TOKEN_STRING)
1457 : : {
1458 : : /* we want the de-escaped string */
1459 : 5 : _state->next_scalar = true;
1460 : : }
1461 [ + + + + ]: 25 : else if (_state->normalize_results && tokentype == JSON_TOKEN_NULL)
1462 : : {
1463 : 5 : _state->tresult = (text *) NULL;
1464 : : }
1465 : : else
1466 : : {
1467 : : /*
1468 : : * This is a bit hokey: we will suppress whitespace after the
1469 : : * scalar token, but not whitespace before it. Probably not worth
1470 : : * doing our own space-skipping to avoid that.
1471 : : */
683 peter@eisentraut.org 1472 : 20 : const char *start = _state->lex->input;
4274 tgl@sss.pgh.pa.us 1473 : 20 : int len = _state->lex->prev_token_terminator - start;
1474 : :
1475 : 20 : _state->tresult = cstring_to_text_with_len(start, len);
1476 : : }
1477 : : }
1478 : :
4785 andrew@dunslane.net 1479 [ + + ]: 120086 : if (_state->next_scalar)
1480 : : {
1481 : : /* a de-escaped text value is wanted, so supply it */
1482 : 476 : _state->tresult = cstring_to_text(token);
1483 : : /* make sure the next call to get_scalar doesn't overwrite it */
1484 : 476 : _state->next_scalar = false;
1485 : : }
1486 : :
1241 tgl@sss.pgh.pa.us 1487 : 120086 : return JSON_SUCCESS;
1488 : : }
1489 : :
1490 : : Datum
4426 andrew@dunslane.net 1491 : 224 : jsonb_extract_path(PG_FUNCTION_ARGS)
1492 : : {
4274 tgl@sss.pgh.pa.us 1493 : 224 : return get_jsonb_path_all(fcinfo, false);
1494 : : }
1495 : :
1496 : : Datum
4426 andrew@dunslane.net 1497 : 150 : jsonb_extract_path_text(PG_FUNCTION_ARGS)
1498 : : {
4274 tgl@sss.pgh.pa.us 1499 : 150 : return get_jsonb_path_all(fcinfo, true);
1500 : : }
1501 : :
1502 : : static Datum
1503 : 374 : get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
1504 : : {
3151 1505 : 374 : Jsonb *jb = PG_GETARG_JSONB_P(0);
4426 andrew@dunslane.net 1506 : 374 : ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
1507 : : Datum *pathtext;
1508 : : bool *pathnulls;
1509 : : bool isnull;
1510 : : int npath;
1511 : : Datum res;
1512 : :
1513 : : /*
1514 : : * If the array contains any null elements, return NULL, on the grounds
1515 : : * that you'd have gotten NULL if any RHS value were NULL in a nested
1516 : : * series of applications of the -> operator. (Note: because we also
1517 : : * return NULL for error cases such as no-such-field, this is true
1518 : : * regardless of the contents of the rest of the array.)
1519 : : */
1520 [ + + ]: 374 : if (array_contains_nulls(path))
4274 tgl@sss.pgh.pa.us 1521 : 10 : PG_RETURN_NULL();
1522 : :
1404 peter@eisentraut.org 1523 : 364 : deconstruct_array_builtin(path, TEXTOID, &pathtext, &pathnulls, &npath);
1524 : :
1920 akorotkov@postgresql 1525 : 364 : res = jsonb_get_element(jb, pathtext, npath, &isnull, as_text);
1526 : :
1527 [ + + ]: 364 : if (isnull)
1528 : 115 : PG_RETURN_NULL();
1529 : : else
1530 : 249 : PG_RETURN_DATUM(res);
1531 : : }
1532 : :
1533 : : Datum
186 peter@eisentraut.org 1534 :GNC 514 : jsonb_get_element(Jsonb *jb, const Datum *path, int npath, bool *isnull, bool as_text)
1535 : : {
1920 akorotkov@postgresql 1536 :CBC 514 : JsonbContainer *container = &jb->root;
1537 : 514 : JsonbValue *jbvp = NULL;
1538 : : int i;
1539 : 514 : bool have_object = false,
1540 : 514 : have_array = false;
1541 : :
1542 : 514 : *isnull = false;
1543 : :
1544 : : /* Identify whether we have object, array, or scalar at top-level */
4426 andrew@dunslane.net 1545 [ + + ]: 514 : if (JB_ROOT_IS_OBJECT(jb))
1546 : 340 : have_object = true;
1547 [ + - + + ]: 174 : else if (JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb))
1548 : 104 : have_array = true;
1549 : : else
1550 : : {
4274 tgl@sss.pgh.pa.us 1551 [ + - - + ]: 70 : Assert(JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb));
1552 : : /* Extract the scalar value, if it is what we'll return */
1553 [ + + ]: 70 : if (npath <= 0)
1554 : 30 : jbvp = getIthJsonbValueFromContainer(container, 0);
1555 : : }
1556 : :
1557 : : /*
1558 : : * If the array is empty, return the entire LHS object, on the grounds
1559 : : * that we should do zero field or element extractions. For the
1560 : : * non-scalar case we can just hand back the object without much work. For
1561 : : * the scalar case, fall through and deal with the value below the loop.
1562 : : * (This inconsistency arises because there's no easy way to generate a
1563 : : * JsonbValue directly for root-level containers.)
1564 : : */
1565 [ + + + + ]: 514 : if (npath <= 0 && jbvp == NULL)
1566 : : {
1567 [ + + ]: 20 : if (as_text)
1568 : : {
1920 akorotkov@postgresql 1569 : 10 : return PointerGetDatum(cstring_to_text(JsonbToCString(NULL,
1570 : : container,
1920 akorotkov@postgresql 1571 :ECB (6) : VARSIZE(jb))));
1572 : : }
1573 : : else
1574 : : {
1575 : : /* not text mode - just hand back the jsonb */
3151 tgl@sss.pgh.pa.us 1576 :CBC 10 : PG_RETURN_JSONB_P(jb);
1577 : : }
1578 : : }
1579 : :
4426 andrew@dunslane.net 1580 [ + + ]: 827 : for (i = 0; i < npath; i++)
1581 : : {
1582 [ + + ]: 797 : if (have_object)
1583 : : {
1240 tgl@sss.pgh.pa.us 1584 : 508 : text *subscr = DatumGetTextPP(path[i]);
1585 : :
2419 alvherre@alvh.no-ip. 1586 : 508 : jbvp = getKeyJsonValueFromContainer(container,
1240 tgl@sss.pgh.pa.us 1587 [ + + ]: 508 : VARDATA_ANY(subscr),
1588 [ - + - - : 508 : VARSIZE_ANY_EXHDR(subscr),
- - - - +
+ ]
1589 : : NULL);
1590 : : }
4426 andrew@dunslane.net 1591 [ + + ]: 289 : else if (have_array)
1592 : : {
1593 : : int lindex;
1594 : : uint32 index;
1920 akorotkov@postgresql 1595 : 224 : char *indextext = TextDatumGetCString(path[i]);
1596 : : char *endptr;
1597 : :
4274 tgl@sss.pgh.pa.us 1598 : 224 : errno = 0;
1909 1599 : 224 : lindex = strtoint(indextext, &endptr, 10);
1600 [ + + + - : 224 : if (endptr == indextext || *endptr != '\0' || errno != 0)
- + ]
1601 : : {
1920 akorotkov@postgresql 1602 : 30 : *isnull = true;
1603 : 35 : return PointerGetDatum(NULL);
1604 : : }
1605 : :
3945 andrew@dunslane.net 1606 [ + + ]: 194 : if (lindex >= 0)
1607 : : {
1608 : 174 : index = (uint32) lindex;
1609 : : }
1610 : : else
1611 : : {
1612 : : /* Handle negative subscript */
1613 : : uint32 nelements;
1614 : :
1615 : : /* Container must be array, but make sure */
3387 tgl@sss.pgh.pa.us 1616 [ - + ]: 20 : if (!JsonContainerIsArray(container))
3945 andrew@dunslane.net 1617 [ # # ]:UBC 0 : elog(ERROR, "not a jsonb array");
1618 : :
3387 tgl@sss.pgh.pa.us 1619 :CBC 20 : nelements = JsonContainerSize(container);
1620 : :
1909 1621 [ + - + + ]: 20 : if (lindex == INT_MIN || -lindex > nelements)
1622 : : {
1920 akorotkov@postgresql 1623 : 5 : *isnull = true;
1624 : 5 : return PointerGetDatum(NULL);
1625 : : }
1626 : : else
3945 andrew@dunslane.net 1627 : 15 : index = nelements + lindex;
1628 : : }
1629 : :
4381 heikki.linnakangas@i 1630 : 189 : jbvp = getIthJsonbValueFromContainer(container, index);
1631 : : }
1632 : : else
1633 : : {
1634 : : /* scalar, extraction yields a null */
1920 akorotkov@postgresql 1635 : 65 : *isnull = true;
1636 : 65 : return PointerGetDatum(NULL);
1637 : : }
1638 : :
4426 andrew@dunslane.net 1639 [ + + ]: 697 : if (jbvp == NULL)
1640 : : {
1920 akorotkov@postgresql 1641 : 61 : *isnull = true;
1642 : 61 : return PointerGetDatum(NULL);
1643 : : }
4426 andrew@dunslane.net 1644 [ + + ]: 636 : else if (i == npath - 1)
1645 : 303 : break;
1646 : :
1647 [ + + ]: 333 : if (jbvp->type == jbvBinary)
1648 : : {
2419 alvherre@alvh.no-ip. 1649 : 308 : container = jbvp->val.binary.data;
1650 : 308 : have_object = JsonContainerIsObject(container);
1651 : 308 : have_array = JsonContainerIsArray(container);
1652 [ - + ]: 308 : Assert(!JsonContainerIsScalar(container));
1653 : : }
1654 : : else
1655 : : {
1656 [ - + - - ]: 25 : Assert(IsAJsonbScalar(jbvp));
1657 : 25 : have_object = false;
1658 : 25 : have_array = false;
1659 : : }
1660 : : }
1661 : :
4426 andrew@dunslane.net 1662 [ + + ]: 333 : if (as_text)
1663 : : {
4274 tgl@sss.pgh.pa.us 1664 [ + + ]: 95 : if (jbvp->type == jbvNull)
1665 : : {
1920 akorotkov@postgresql 1666 : 20 : *isnull = true;
1667 : 20 : return PointerGetDatum(NULL);
1668 : : }
1669 : :
1670 : 75 : return PointerGetDatum(JsonbValueAsText(jbvp));
1671 : : }
1672 : : else
1673 : : {
2419 alvherre@alvh.no-ip. 1674 : 238 : Jsonb *res = JsonbValueToJsonb(jbvp);
1675 : :
1676 : : /* not text mode - just hand back the jsonb */
3151 tgl@sss.pgh.pa.us 1677 : 238 : PG_RETURN_JSONB_P(res);
1678 : : }
1679 : : }
1680 : :
1681 : : Datum
186 peter@eisentraut.org 1682 :GNC 164 : jsonb_set_element(Jsonb *jb, const Datum *path, int path_len,
1683 : : JsonbValue *newval)
1684 : : {
149 tgl@sss.pgh.pa.us 1685 : 164 : JsonbInState state = {0};
1686 : : JsonbIterator *it;
146 michael@paquier.xyz 1687 : 164 : bool *path_nulls = palloc0_array(bool, path_len);
1688 : :
1920 akorotkov@postgresql 1689 [ - + - - ]:CBC 164 : if (newval->type == jbvArray && newval->val.array.rawScalar)
1920 akorotkov@postgresql 1690 :UBC 0 : *newval = newval->val.array.elems[0];
1691 : :
1920 akorotkov@postgresql 1692 :CBC 164 : it = JsonbIteratorInit(&jb->root);
1693 : :
149 tgl@sss.pgh.pa.us 1694 :GNC 164 : setPath(&it, path, path_nulls, path_len, &state, 0, newval,
1695 : : JB_PATH_CREATE | JB_PATH_FILL_GAPS |
1696 : : JB_PATH_CONSISTENT_POSITION);
1697 : :
1920 akorotkov@postgresql 1698 :CBC 132 : pfree(path_nulls);
1699 : :
149 tgl@sss.pgh.pa.us 1700 :GNC 132 : PG_RETURN_JSONB_P(JsonbValueToJsonb(state.result));
1701 : : }
1702 : :
1703 : : static void
1704 : 72 : push_null_elements(JsonbInState *ps, int num)
1705 : : {
1706 : : JsonbValue null;
1707 : :
1920 akorotkov@postgresql 1708 :CBC 72 : null.type = jbvNull;
1709 : :
1710 [ + + ]: 272 : while (num-- > 0)
1711 : 200 : pushJsonbValue(ps, WJB_ELEM, &null);
1712 : 72 : }
1713 : :
1714 : : /*
1715 : : * Prepare a new structure containing nested empty objects and arrays
1716 : : * corresponding to the specified path, and assign a new value at the end of
1717 : : * this path. E.g. the path [a][0][b] with the new value 1 will produce the
1718 : : * structure {a: [{b: 1}]}.
1719 : : *
1720 : : * Caller is responsible to make sure such path does not exist yet.
1721 : : */
1722 : : static void
149 tgl@sss.pgh.pa.us 1723 :GNC 48 : push_path(JsonbInState *st, int level, const Datum *path_elems,
1724 : : const bool *path_nulls, int path_len, JsonbValue *newval)
1725 : : {
1726 : : /*
1727 : : * tpath contains expected type of an empty jsonb created at each level
1728 : : * higher or equal to the current one, either jbvObject or jbvArray. Since
1729 : : * it contains only information about path slice from level to the end,
1730 : : * the access index must be normalized by level.
1731 : : */
146 michael@paquier.xyz 1732 : 48 : enum jbvType *tpath = palloc0_array(enum jbvType, path_len - level);
1733 : : JsonbValue newkey;
1734 : :
1735 : : /*
1736 : : * Create first part of the chain with beginning tokens. For the current
1737 : : * level WJB_BEGIN_OBJECT/WJB_BEGIN_ARRAY was already created, so start
1738 : : * with the next one.
1739 : : */
1920 akorotkov@postgresql 1740 [ + + ]:CBC 144 : for (int i = level + 1; i < path_len; i++)
1741 : : {
1742 : : char *c,
1743 : : *badp;
1744 : : int lindex;
1745 : :
1746 [ - + ]: 96 : if (path_nulls[i])
1920 akorotkov@postgresql 1747 :UBC 0 : break;
1748 : :
1749 : : /*
1750 : : * Try to convert to an integer to find out the expected type, object
1751 : : * or array.
1752 : : */
1920 akorotkov@postgresql 1753 :CBC 96 : c = TextDatumGetCString(path_elems[i]);
1754 : 96 : errno = 0;
1909 tgl@sss.pgh.pa.us 1755 : 96 : lindex = strtoint(c, &badp, 10);
1756 [ + + + - : 96 : if (badp == c || *badp != '\0' || errno != 0)
- + ]
1757 : : {
1758 : : /* text, an object is expected */
1920 akorotkov@postgresql 1759 : 44 : newkey.type = jbvString;
1240 tgl@sss.pgh.pa.us 1760 : 44 : newkey.val.string.val = c;
1761 : 44 : newkey.val.string.len = strlen(c);
1762 : :
149 tgl@sss.pgh.pa.us 1763 :GNC 44 : pushJsonbValue(st, WJB_BEGIN_OBJECT, NULL);
1764 : 44 : pushJsonbValue(st, WJB_KEY, &newkey);
1765 : :
1920 akorotkov@postgresql 1766 :CBC 44 : tpath[i - level] = jbvObject;
1767 : : }
1768 : : else
1769 : : {
1770 : : /* integer, an array is expected */
149 tgl@sss.pgh.pa.us 1771 :GNC 52 : pushJsonbValue(st, WJB_BEGIN_ARRAY, NULL);
1772 : :
1920 akorotkov@postgresql 1773 :CBC 52 : push_null_elements(st, lindex);
1774 : :
1775 : 52 : tpath[i - level] = jbvArray;
1776 : : }
1777 : : }
1778 : :
1779 : : /* Insert an actual value for either an object or array */
1780 [ + + ]: 48 : if (tpath[(path_len - level) - 1] == jbvArray)
149 tgl@sss.pgh.pa.us 1781 :GNC 32 : pushJsonbValue(st, WJB_ELEM, newval);
1782 : : else
1783 : 16 : pushJsonbValue(st, WJB_VALUE, newval);
1784 : :
1785 : : /*
1786 : : * Close everything up to the last but one level. The last one will be
1787 : : * closed outside of this function.
1788 : : */
1920 akorotkov@postgresql 1789 [ + + ]:CBC 144 : for (int i = path_len - 1; i > level; i--)
1790 : : {
1791 [ - + ]: 96 : if (path_nulls[i])
1920 akorotkov@postgresql 1792 :UBC 0 : break;
1793 : :
1920 akorotkov@postgresql 1794 [ + + ]:CBC 96 : if (tpath[i - level] == jbvObject)
149 tgl@sss.pgh.pa.us 1795 :GNC 44 : pushJsonbValue(st, WJB_END_OBJECT, NULL);
1796 : : else
1797 : 52 : pushJsonbValue(st, WJB_END_ARRAY, NULL);
1798 : : }
1920 akorotkov@postgresql 1799 :CBC 48 : }
1800 : :
1801 : : /*
1802 : : * Return the text representation of the given JsonbValue.
1803 : : */
1804 : : static text *
2419 alvherre@alvh.no-ip. 1805 : 300 : JsonbValueAsText(JsonbValue *v)
1806 : : {
1807 [ - + + + : 300 : switch (v->type)
+ - ]
1808 : : {
2419 alvherre@alvh.no-ip. 1809 :UBC 0 : case jbvNull:
1810 : 0 : return NULL;
1811 : :
2419 alvherre@alvh.no-ip. 1812 :CBC 16 : case jbvBool:
1813 : 16 : return v->val.boolean ?
1814 [ + + ]: 24 : cstring_to_text_with_len("true", 4) :
1815 : 8 : cstring_to_text_with_len("false", 5);
1816 : :
1817 : 164 : case jbvString:
1818 : 164 : return cstring_to_text_with_len(v->val.string.val,
1819 : : v->val.string.len);
1820 : :
1821 : 31 : case jbvNumeric:
1822 : : {
1823 : : Datum cstr;
1824 : :
1825 : 31 : cstr = DirectFunctionCall1(numeric_out,
1826 : : PointerGetDatum(v->val.numeric));
1827 : :
1828 : 31 : return cstring_to_text(DatumGetCString(cstr));
1829 : : }
1830 : :
1831 : 89 : case jbvBinary:
1832 : : {
1833 : : StringInfoData jtext;
1834 : :
1835 : 89 : initStringInfo(&jtext);
1836 : 89 : (void) JsonbToCString(&jtext, v->val.binary.data,
1837 : : v->val.binary.len);
1838 : :
1839 : 89 : return cstring_to_text_with_len(jtext.data, jtext.len);
1840 : : }
1841 : :
2419 alvherre@alvh.no-ip. 1842 :UBC 0 : default:
1843 [ # # ]: 0 : elog(ERROR, "unrecognized jsonb type: %d", (int) v->type);
1844 : : return NULL;
1845 : : }
1846 : : }
1847 : :
1848 : : /*
1849 : : * SQL function json_array_length(json) -> int
1850 : : */
1851 : : Datum
4785 andrew@dunslane.net 1852 :CBC 18 : json_array_length(PG_FUNCTION_ARGS)
1853 : : {
3341 noah@leadboat.com 1854 : 18 : text *json = PG_GETARG_TEXT_PP(0);
1855 : : AlenState *state;
1856 : : JsonLexContext lex;
1857 : : JsonSemAction *sem;
1858 : :
146 michael@paquier.xyz 1859 :GNC 18 : state = palloc0_object(AlenState);
943 alvherre@alvh.no-ip. 1860 :CBC 18 : state->lex = makeJsonLexContext(&lex, json, false);
1861 : : /* palloc0 does this for us */
1862 : : #if 0
1863 : : state->count = 0;
1864 : : #endif
1865 : :
146 michael@paquier.xyz 1866 :GNC 18 : sem = palloc0_object(JsonSemAction);
523 peter@eisentraut.org 1867 :CBC 18 : sem->semstate = state;
4785 andrew@dunslane.net 1868 : 18 : sem->object_start = alen_object_start;
1869 : 18 : sem->scalar = alen_scalar;
1870 : 18 : sem->array_element_start = alen_array_element_start;
1871 : :
943 alvherre@alvh.no-ip. 1872 : 18 : pg_parse_json_or_ereport(state->lex, sem);
1873 : :
4785 andrew@dunslane.net 1874 : 10 : PG_RETURN_INT32(state->count);
1875 : : }
1876 : :
1877 : : Datum
4426 1878 : 220 : jsonb_array_length(PG_FUNCTION_ARGS)
1879 : : {
3151 tgl@sss.pgh.pa.us 1880 : 220 : Jsonb *jb = PG_GETARG_JSONB_P(0);
1881 : :
4426 andrew@dunslane.net 1882 [ + + ]: 220 : if (JB_ROOT_IS_SCALAR(jb))
1883 [ + - ]: 4 : ereport(ERROR,
1884 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1885 : : errmsg("cannot get array length of a scalar")));
1886 [ + + ]: 216 : else if (!JB_ROOT_IS_ARRAY(jb))
1887 [ + - ]: 4 : ereport(ERROR,
1888 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1889 : : errmsg("cannot get array length of a non-array")));
1890 : :
1891 : 212 : PG_RETURN_INT32(JB_ROOT_COUNT(jb));
1892 : : }
1893 : :
1894 : : /*
1895 : : * These next two checks ensure that the json is an array (since it can't be
1896 : : * a scalar or an object).
1897 : : */
1898 : :
1899 : : static JsonParseErrorType
4785 1900 : 9 : alen_object_start(void *state)
1901 : : {
4672 peter_e@gmx.net 1902 : 9 : AlenState *_state = (AlenState *) state;
1903 : :
1904 : : /* json structure check */
4785 andrew@dunslane.net 1905 [ + + ]: 9 : if (_state->lex->lex_level == 0)
1906 [ + - ]: 4 : ereport(ERROR,
1907 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1908 : : errmsg("cannot get array length of a non-array")));
1909 : :
1241 tgl@sss.pgh.pa.us 1910 : 5 : return JSON_SUCCESS;
1911 : : }
1912 : :
1913 : : static JsonParseErrorType
4785 andrew@dunslane.net 1914 : 39 : alen_scalar(void *state, char *token, JsonTokenType tokentype)
1915 : : {
4672 peter_e@gmx.net 1916 : 39 : AlenState *_state = (AlenState *) state;
1917 : :
1918 : : /* json structure check */
4785 andrew@dunslane.net 1919 [ + + ]: 39 : if (_state->lex->lex_level == 0)
1920 [ + - ]: 4 : ereport(ERROR,
1921 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1922 : : errmsg("cannot get array length of a scalar")));
1923 : :
1241 tgl@sss.pgh.pa.us 1924 : 35 : return JSON_SUCCESS;
1925 : : }
1926 : :
1927 : : static JsonParseErrorType
4785 andrew@dunslane.net 1928 : 35 : alen_array_element_start(void *state, bool isnull)
1929 : : {
4672 peter_e@gmx.net 1930 : 35 : AlenState *_state = (AlenState *) state;
1931 : :
1932 : : /* just count up all the level 1 elements */
4785 andrew@dunslane.net 1933 [ + + ]: 35 : if (_state->lex->lex_level == 1)
1934 : 25 : _state->count++;
1935 : :
1241 tgl@sss.pgh.pa.us 1936 : 35 : return JSON_SUCCESS;
1937 : : }
1938 : :
1939 : : /*
1940 : : * SQL function json_each and json_each_text
1941 : : *
1942 : : * decompose a json object into key value pairs.
1943 : : *
1944 : : * Unlike json_object_keys() these SRFs operate in materialize mode,
1945 : : * stashing results into a Tuplestore object as they go.
1946 : : * The construction of tuples is done using a temporary memory context
1947 : : * that is cleared out after each tuple is built.
1948 : : */
1949 : : Datum
4785 andrew@dunslane.net 1950 : 8 : json_each(PG_FUNCTION_ARGS)
1951 : : {
1952 : 8 : return each_worker(fcinfo, false);
1953 : : }
1954 : :
1955 : : Datum
4426 1956 : 8112 : jsonb_each(PG_FUNCTION_ARGS)
1957 : : {
4332 tgl@sss.pgh.pa.us 1958 : 8112 : return each_worker_jsonb(fcinfo, "jsonb_each", false);
1959 : : }
1960 : :
1961 : : Datum
4785 andrew@dunslane.net 1962 : 8 : json_each_text(PG_FUNCTION_ARGS)
1963 : : {
1964 : 8 : return each_worker(fcinfo, true);
1965 : : }
1966 : :
1967 : : Datum
4426 1968 : 16 : jsonb_each_text(PG_FUNCTION_ARGS)
1969 : : {
4332 tgl@sss.pgh.pa.us 1970 : 16 : return each_worker_jsonb(fcinfo, "jsonb_each_text", true);
1971 : : }
1972 : :
1973 : : static Datum
1974 : 8128 : each_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, bool as_text)
1975 : : {
3151 1976 : 8128 : Jsonb *jb = PG_GETARG_JSONB_P(0);
1977 : : ReturnSetInfo *rsi;
1978 : : MemoryContext old_cxt,
1979 : : tmp_cxt;
4426 andrew@dunslane.net 1980 : 8128 : bool skipNested = false;
1981 : : JsonbIterator *it;
1982 : : JsonbValue v;
1983 : : JsonbIteratorToken r;
1984 : :
1985 [ - + ]: 8128 : if (!JB_ROOT_IS_OBJECT(jb))
4426 andrew@dunslane.net 1986 [ # # ]:UBC 0 : ereport(ERROR,
1987 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1988 : : errmsg("cannot call %s on a non-object",
1989 : : funcname)));
1990 : :
4426 andrew@dunslane.net 1991 :CBC 8128 : rsi = (ReturnSetInfo *) fcinfo->resultinfo;
1295 michael@paquier.xyz 1992 : 8128 : InitMaterializedSRF(fcinfo, MAT_SRF_BLESS);
1993 : :
4426 andrew@dunslane.net 1994 : 8128 : tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
1995 : : "jsonb_each temporary cxt",
1996 : : ALLOCSET_DEFAULT_SIZES);
1997 : :
4381 heikki.linnakangas@i 1998 : 8128 : it = JsonbIteratorInit(&jb->root);
1999 : :
4426 andrew@dunslane.net 2000 [ + + ]: 62860 : while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
2001 : : {
2002 : 54732 : skipNested = true;
2003 : :
2004 [ + + ]: 54732 : if (r == WJB_KEY)
2005 : : {
2006 : : text *key;
2007 : : Datum values[2];
2008 : 38476 : bool nulls[2] = {false, false};
2009 : :
2010 : : /* Use the tmp context so we can clean up after each tuple is done */
2011 : 38476 : old_cxt = MemoryContextSwitchTo(tmp_cxt);
2012 : :
4416 tgl@sss.pgh.pa.us 2013 : 38476 : key = cstring_to_text_with_len(v.val.string.val, v.val.string.len);
2014 : :
2015 : : /*
2016 : : * The next thing the iterator fetches should be the value, no
2017 : : * matter what shape it is.
2018 : : */
4426 andrew@dunslane.net 2019 : 38476 : r = JsonbIteratorNext(&it, &v, skipNested);
2942 tgl@sss.pgh.pa.us 2020 [ - + ]: 38476 : Assert(r != WJB_DONE);
2021 : :
4426 andrew@dunslane.net 2022 : 38476 : values[0] = PointerGetDatum(key);
2023 : :
2024 [ + + ]: 38476 : if (as_text)
2025 : : {
2026 [ + + ]: 76 : if (v.type == jbvNull)
2027 : : {
2028 : : /* a json null is an sql null in text mode */
2029 : 16 : nulls[1] = true;
270 tgl@sss.pgh.pa.us 2030 :GNC 16 : values[1] = (Datum) 0;
2031 : : }
2032 : : else
2419 alvherre@alvh.no-ip. 2033 :CBC 60 : values[1] = PointerGetDatum(JsonbValueAsText(&v));
2034 : : }
2035 : : else
2036 : : {
2037 : : /* Not in text mode, just return the Jsonb */
4426 andrew@dunslane.net 2038 : 38400 : Jsonb *val = JsonbValueToJsonb(&v);
2039 : :
2040 : 38400 : values[1] = PointerGetDatum(val);
2041 : : }
2042 : :
1520 michael@paquier.xyz 2043 : 38476 : tuplestore_putvalues(rsi->setResult, rsi->setDesc, values, nulls);
2044 : :
2045 : : /* clean up and switch back */
4426 andrew@dunslane.net 2046 : 38476 : MemoryContextSwitchTo(old_cxt);
2047 : 38476 : MemoryContextReset(tmp_cxt);
2048 : : }
2049 : : }
2050 : :
2051 : 8128 : MemoryContextDelete(tmp_cxt);
2052 : :
2053 : 8128 : PG_RETURN_NULL();
2054 : : }
2055 : :
2056 : :
2057 : : static Datum
2058 : 16 : each_worker(FunctionCallInfo fcinfo, bool as_text)
2059 : : {
3341 noah@leadboat.com 2060 : 16 : text *json = PG_GETARG_TEXT_PP(0);
2061 : : JsonLexContext lex;
2062 : : JsonSemAction *sem;
2063 : : ReturnSetInfo *rsi;
2064 : : EachState *state;
2065 : :
146 michael@paquier.xyz 2066 :GNC 16 : state = palloc0_object(EachState);
2067 : 16 : sem = palloc0_object(JsonSemAction);
2068 : :
4785 andrew@dunslane.net 2069 :CBC 16 : rsi = (ReturnSetInfo *) fcinfo->resultinfo;
2070 : :
1295 michael@paquier.xyz 2071 : 16 : InitMaterializedSRF(fcinfo, MAT_SRF_BLESS);
1520 2072 : 16 : state->tuple_store = rsi->setResult;
2073 : 16 : state->ret_tdesc = rsi->setDesc;
2074 : :
523 peter@eisentraut.org 2075 : 16 : sem->semstate = state;
4785 andrew@dunslane.net 2076 : 16 : sem->array_start = each_array_start;
2077 : 16 : sem->scalar = each_scalar;
2078 : 16 : sem->object_field_start = each_object_field_start;
2079 : 16 : sem->object_field_end = each_object_field_end;
2080 : :
2081 : 16 : state->normalize_results = as_text;
2082 : 16 : state->next_scalar = false;
943 alvherre@alvh.no-ip. 2083 : 16 : state->lex = makeJsonLexContext(&lex, json, true);
4785 andrew@dunslane.net 2084 : 16 : state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
2085 : : "json_each temporary cxt",
2086 : : ALLOCSET_DEFAULT_SIZES);
2087 : :
943 alvherre@alvh.no-ip. 2088 : 16 : pg_parse_json_or_ereport(&lex, sem);
2089 : :
4472 peter_e@gmx.net 2090 : 16 : MemoryContextDelete(state->tmp_cxt);
943 alvherre@alvh.no-ip. 2091 : 16 : freeJsonLexContext(&lex);
2092 : :
4785 andrew@dunslane.net 2093 : 16 : PG_RETURN_NULL();
2094 : : }
2095 : :
2096 : :
2097 : : static JsonParseErrorType
2098 : 84 : each_object_field_start(void *state, char *fname, bool isnull)
2099 : : {
4672 peter_e@gmx.net 2100 : 84 : EachState *_state = (EachState *) state;
2101 : :
2102 : : /* save a pointer to where the value starts */
4785 andrew@dunslane.net 2103 [ + + ]: 84 : if (_state->lex->lex_level == 1)
2104 : : {
2105 : : /*
2106 : : * next_scalar will be reset in the object_field_end handler, and
2107 : : * since we know the value is a scalar there is no danger of it being
2108 : : * on while recursing down the tree.
2109 : : */
2110 [ + + + + ]: 68 : if (_state->normalize_results && _state->lex->token_type == JSON_TOKEN_STRING)
2111 : 8 : _state->next_scalar = true;
2112 : : else
2113 : 60 : _state->result_start = _state->lex->token_start;
2114 : : }
2115 : :
1241 tgl@sss.pgh.pa.us 2116 : 84 : return JSON_SUCCESS;
2117 : : }
2118 : :
2119 : : static JsonParseErrorType
4785 andrew@dunslane.net 2120 : 84 : each_object_field_end(void *state, char *fname, bool isnull)
2121 : : {
4672 peter_e@gmx.net 2122 : 84 : EachState *_state = (EachState *) state;
2123 : : MemoryContext old_cxt;
2124 : : int len;
2125 : : text *val;
2126 : : HeapTuple tuple;
2127 : : Datum values[2];
4785 andrew@dunslane.net 2128 : 84 : bool nulls[2] = {false, false};
2129 : :
2130 : : /* skip over nested objects */
2131 [ + + ]: 84 : if (_state->lex->lex_level != 1)
1241 tgl@sss.pgh.pa.us 2132 : 16 : return JSON_SUCCESS;
2133 : :
2134 : : /* use the tmp context so we can clean up after each tuple is done */
4785 andrew@dunslane.net 2135 : 68 : old_cxt = MemoryContextSwitchTo(_state->tmp_cxt);
2136 : :
2137 : 68 : values[0] = CStringGetTextDatum(fname);
2138 : :
2139 [ + + + + ]: 68 : if (isnull && _state->normalize_results)
2140 : : {
2141 : 8 : nulls[1] = true;
4332 tgl@sss.pgh.pa.us 2142 : 8 : values[1] = (Datum) 0;
2143 : : }
4785 andrew@dunslane.net 2144 [ + + ]: 60 : else if (_state->next_scalar)
2145 : : {
2146 : 8 : values[1] = CStringGetTextDatum(_state->normalized_scalar);
2147 : 8 : _state->next_scalar = false;
2148 : : }
2149 : : else
2150 : : {
4426 2151 : 52 : len = _state->lex->prev_token_terminator - _state->result_start;
2152 : 52 : val = cstring_to_text_with_len(_state->result_start, len);
2153 : 52 : values[1] = PointerGetDatum(val);
2154 : : }
2155 : :
2156 : 68 : tuple = heap_form_tuple(_state->ret_tdesc, values, nulls);
2157 : :
2158 : 68 : tuplestore_puttuple(_state->tuple_store, tuple);
2159 : :
2160 : : /* clean up and switch back */
2161 : 68 : MemoryContextSwitchTo(old_cxt);
2162 : 68 : MemoryContextReset(_state->tmp_cxt);
2163 : :
1241 tgl@sss.pgh.pa.us 2164 : 68 : return JSON_SUCCESS;
2165 : : }
2166 : :
2167 : : static JsonParseErrorType
4426 andrew@dunslane.net 2168 : 16 : each_array_start(void *state)
2169 : : {
2170 : 16 : EachState *_state = (EachState *) state;
2171 : :
2172 : : /* json structure check */
2173 [ - + ]: 16 : if (_state->lex->lex_level == 0)
4426 andrew@dunslane.net 2174 [ # # ]:UBC 0 : ereport(ERROR,
2175 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2176 : : errmsg("cannot deconstruct an array as an object")));
2177 : :
1241 tgl@sss.pgh.pa.us 2178 :CBC 16 : return JSON_SUCCESS;
2179 : : }
2180 : :
2181 : : static JsonParseErrorType
4426 andrew@dunslane.net 2182 : 100 : each_scalar(void *state, char *token, JsonTokenType tokentype)
2183 : : {
2184 : 100 : EachState *_state = (EachState *) state;
2185 : :
2186 : : /* json structure check */
2187 [ - + ]: 100 : if (_state->lex->lex_level == 0)
4426 andrew@dunslane.net 2188 [ # # ]:UBC 0 : ereport(ERROR,
2189 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2190 : : errmsg("cannot deconstruct a scalar")));
2191 : :
2192 : : /* supply de-escaped value if required */
4426 andrew@dunslane.net 2193 [ + + ]:CBC 100 : if (_state->next_scalar)
2194 : 8 : _state->normalized_scalar = token;
2195 : :
1241 tgl@sss.pgh.pa.us 2196 : 100 : return JSON_SUCCESS;
2197 : : }
2198 : :
2199 : : /*
2200 : : * SQL functions json_array_elements and json_array_elements_text
2201 : : *
2202 : : * get the elements from a json array
2203 : : *
2204 : : * a lot of this processing is similar to the json_each* functions
2205 : : */
2206 : :
2207 : : Datum
4426 andrew@dunslane.net 2208 : 24 : jsonb_array_elements(PG_FUNCTION_ARGS)
2209 : : {
4332 tgl@sss.pgh.pa.us 2210 : 24 : return elements_worker_jsonb(fcinfo, "jsonb_array_elements", false);
2211 : : }
2212 : :
2213 : : Datum
4426 andrew@dunslane.net 2214 : 8 : jsonb_array_elements_text(PG_FUNCTION_ARGS)
2215 : : {
4332 tgl@sss.pgh.pa.us 2216 : 8 : return elements_worker_jsonb(fcinfo, "jsonb_array_elements_text", true);
2217 : : }
2218 : :
2219 : : static Datum
2220 : 32 : elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname,
2221 : : bool as_text)
2222 : : {
3151 2223 : 32 : Jsonb *jb = PG_GETARG_JSONB_P(0);
2224 : : ReturnSetInfo *rsi;
2225 : : MemoryContext old_cxt,
2226 : : tmp_cxt;
4426 andrew@dunslane.net 2227 : 32 : bool skipNested = false;
2228 : : JsonbIterator *it;
2229 : : JsonbValue v;
2230 : : JsonbIteratorToken r;
2231 : :
2232 [ - + ]: 32 : if (JB_ROOT_IS_SCALAR(jb))
4426 andrew@dunslane.net 2233 [ # # ]:UBC 0 : ereport(ERROR,
2234 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2235 : : errmsg("cannot extract elements from a scalar")));
4426 andrew@dunslane.net 2236 [ - + ]:CBC 32 : else if (!JB_ROOT_IS_ARRAY(jb))
4426 andrew@dunslane.net 2237 [ # # ]:UBC 0 : ereport(ERROR,
2238 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2239 : : errmsg("cannot extract elements from an object")));
2240 : :
4426 andrew@dunslane.net 2241 :CBC 32 : rsi = (ReturnSetInfo *) fcinfo->resultinfo;
2242 : :
1295 michael@paquier.xyz 2243 : 32 : InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC | MAT_SRF_BLESS);
2244 : :
4426 andrew@dunslane.net 2245 : 32 : tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
2246 : : "jsonb_array_elements temporary cxt",
2247 : : ALLOCSET_DEFAULT_SIZES);
2248 : :
4381 heikki.linnakangas@i 2249 : 32 : it = JsonbIteratorInit(&jb->root);
2250 : :
4426 andrew@dunslane.net 2251 [ + + ]: 216 : while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
2252 : : {
2253 : 184 : skipNested = true;
2254 : :
2255 [ + + ]: 184 : if (r == WJB_ELEM)
2256 : : {
2257 : : Datum values[1];
2258 : 120 : bool nulls[1] = {false};
2259 : :
2260 : : /* use the tmp context so we can clean up after each tuple is done */
2261 : 120 : old_cxt = MemoryContextSwitchTo(tmp_cxt);
2262 : :
2419 alvherre@alvh.no-ip. 2263 [ + + ]: 120 : if (as_text)
2264 : : {
4426 andrew@dunslane.net 2265 [ + + ]: 56 : if (v.type == jbvNull)
2266 : : {
2267 : : /* a json null is an sql null in text mode */
2268 : 8 : nulls[0] = true;
270 tgl@sss.pgh.pa.us 2269 :GNC 8 : values[0] = (Datum) 0;
2270 : : }
2271 : : else
2419 alvherre@alvh.no-ip. 2272 :CBC 48 : values[0] = PointerGetDatum(JsonbValueAsText(&v));
2273 : : }
2274 : : else
2275 : : {
2276 : : /* Not in text mode, just return the Jsonb */
2277 : 64 : Jsonb *val = JsonbValueToJsonb(&v);
2278 : :
2279 : 64 : values[0] = PointerGetDatum(val);
2280 : : }
2281 : :
1520 michael@paquier.xyz 2282 : 120 : tuplestore_putvalues(rsi->setResult, rsi->setDesc, values, nulls);
2283 : :
2284 : : /* clean up and switch back */
4426 andrew@dunslane.net 2285 : 120 : MemoryContextSwitchTo(old_cxt);
2286 : 120 : MemoryContextReset(tmp_cxt);
2287 : : }
2288 : : }
2289 : :
2290 : 32 : MemoryContextDelete(tmp_cxt);
2291 : :
2292 : 32 : PG_RETURN_NULL();
2293 : : }
2294 : :
2295 : : Datum
4785 2296 : 256 : json_array_elements(PG_FUNCTION_ARGS)
2297 : : {
4332 tgl@sss.pgh.pa.us 2298 : 256 : return elements_worker(fcinfo, "json_array_elements", false);
2299 : : }
2300 : :
2301 : : Datum
4479 andrew@dunslane.net 2302 : 8 : json_array_elements_text(PG_FUNCTION_ARGS)
2303 : : {
4332 tgl@sss.pgh.pa.us 2304 : 8 : return elements_worker(fcinfo, "json_array_elements_text", true);
2305 : : }
2306 : :
2307 : : static Datum
2308 : 264 : elements_worker(FunctionCallInfo fcinfo, const char *funcname, bool as_text)
2309 : : {
3341 noah@leadboat.com 2310 : 264 : text *json = PG_GETARG_TEXT_PP(0);
2311 : : JsonLexContext lex;
2312 : : JsonSemAction *sem;
2313 : : ReturnSetInfo *rsi;
2314 : : ElementsState *state;
2315 : :
2316 : : /* elements only needs escaped strings when as_text */
943 alvherre@alvh.no-ip. 2317 : 264 : makeJsonLexContext(&lex, json, as_text);
2318 : :
146 michael@paquier.xyz 2319 :GNC 264 : state = palloc0_object(ElementsState);
2320 : 264 : sem = palloc0_object(JsonSemAction);
2321 : :
1295 michael@paquier.xyz 2322 :CBC 264 : InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC | MAT_SRF_BLESS);
4785 andrew@dunslane.net 2323 : 264 : rsi = (ReturnSetInfo *) fcinfo->resultinfo;
1520 michael@paquier.xyz 2324 : 264 : state->tuple_store = rsi->setResult;
2325 : 264 : state->ret_tdesc = rsi->setDesc;
2326 : :
523 peter@eisentraut.org 2327 : 264 : sem->semstate = state;
4785 andrew@dunslane.net 2328 : 264 : sem->object_start = elements_object_start;
2329 : 264 : sem->scalar = elements_scalar;
2330 : 264 : sem->array_element_start = elements_array_element_start;
2331 : 264 : sem->array_element_end = elements_array_element_end;
2332 : :
4332 tgl@sss.pgh.pa.us 2333 : 264 : state->function_name = funcname;
4479 andrew@dunslane.net 2334 : 264 : state->normalize_results = as_text;
2335 : 264 : state->next_scalar = false;
943 alvherre@alvh.no-ip. 2336 : 264 : state->lex = &lex;
4785 andrew@dunslane.net 2337 : 264 : state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
2338 : : "json_array_elements temporary cxt",
2339 : : ALLOCSET_DEFAULT_SIZES);
2340 : :
943 alvherre@alvh.no-ip. 2341 : 264 : pg_parse_json_or_ereport(&lex, sem);
2342 : :
4472 peter_e@gmx.net 2343 : 264 : MemoryContextDelete(state->tmp_cxt);
943 alvherre@alvh.no-ip. 2344 : 264 : freeJsonLexContext(&lex);
2345 : :
4785 andrew@dunslane.net 2346 : 264 : PG_RETURN_NULL();
2347 : : }
2348 : :
2349 : : static JsonParseErrorType
2350 : 1328 : elements_array_element_start(void *state, bool isnull)
2351 : : {
4672 peter_e@gmx.net 2352 : 1328 : ElementsState *_state = (ElementsState *) state;
2353 : :
2354 : : /* save a pointer to where the value starts */
4785 andrew@dunslane.net 2355 [ + + ]: 1328 : if (_state->lex->lex_level == 1)
2356 : : {
2357 : : /*
2358 : : * next_scalar will be reset in the array_element_end handler, and
2359 : : * since we know the value is a scalar there is no danger of it being
2360 : : * on while recursing down the tree.
2361 : : */
4479 2362 [ + + + + ]: 448 : if (_state->normalize_results && _state->lex->token_type == JSON_TOKEN_STRING)
2363 : 8 : _state->next_scalar = true;
2364 : : else
2365 : 440 : _state->result_start = _state->lex->token_start;
2366 : : }
2367 : :
1241 tgl@sss.pgh.pa.us 2368 : 1328 : return JSON_SUCCESS;
2369 : : }
2370 : :
2371 : : static JsonParseErrorType
4785 andrew@dunslane.net 2372 : 1328 : elements_array_element_end(void *state, bool isnull)
2373 : : {
4672 peter_e@gmx.net 2374 : 1328 : ElementsState *_state = (ElementsState *) state;
2375 : : MemoryContext old_cxt;
2376 : : int len;
2377 : : text *val;
2378 : : HeapTuple tuple;
2379 : : Datum values[1];
4382 bruce@momjian.us 2380 : 1328 : bool nulls[1] = {false};
2381 : :
2382 : : /* skip over nested objects */
4785 andrew@dunslane.net 2383 [ + + ]: 1328 : if (_state->lex->lex_level != 1)
1241 tgl@sss.pgh.pa.us 2384 : 880 : return JSON_SUCCESS;
2385 : :
2386 : : /* use the tmp context so we can clean up after each tuple is done */
4785 andrew@dunslane.net 2387 : 448 : old_cxt = MemoryContextSwitchTo(_state->tmp_cxt);
2388 : :
4479 2389 [ + + + + ]: 448 : if (isnull && _state->normalize_results)
2390 : : {
2391 : 8 : nulls[0] = true;
270 tgl@sss.pgh.pa.us 2392 :GNC 8 : values[0] = (Datum) 0;
2393 : : }
4479 andrew@dunslane.net 2394 [ + + ]:CBC 440 : else if (_state->next_scalar)
2395 : : {
2396 : 8 : values[0] = CStringGetTextDatum(_state->normalized_scalar);
2397 : 8 : _state->next_scalar = false;
2398 : : }
2399 : : else
2400 : : {
2401 : 432 : len = _state->lex->prev_token_terminator - _state->result_start;
2402 : 432 : val = cstring_to_text_with_len(_state->result_start, len);
2403 : 432 : values[0] = PointerGetDatum(val);
2404 : : }
2405 : :
4785 2406 : 448 : tuple = heap_form_tuple(_state->ret_tdesc, values, nulls);
2407 : :
2408 : 448 : tuplestore_puttuple(_state->tuple_store, tuple);
2409 : :
2410 : : /* clean up and switch back */
2411 : 448 : MemoryContextSwitchTo(old_cxt);
2412 : 448 : MemoryContextReset(_state->tmp_cxt);
2413 : :
1241 tgl@sss.pgh.pa.us 2414 : 448 : return JSON_SUCCESS;
2415 : : }
2416 : :
2417 : : static JsonParseErrorType
4785 andrew@dunslane.net 2418 : 1120 : elements_object_start(void *state)
2419 : : {
4672 peter_e@gmx.net 2420 : 1120 : ElementsState *_state = (ElementsState *) state;
2421 : :
2422 : : /* json structure check */
4785 andrew@dunslane.net 2423 [ - + ]: 1120 : if (_state->lex->lex_level == 0)
4785 andrew@dunslane.net 2424 [ # # ]:UBC 0 : ereport(ERROR,
2425 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2426 : : errmsg("cannot call %s on a non-array",
2427 : : _state->function_name)));
2428 : :
1241 tgl@sss.pgh.pa.us 2429 :CBC 1120 : return JSON_SUCCESS;
2430 : : }
2431 : :
2432 : : static JsonParseErrorType
4785 andrew@dunslane.net 2433 : 28848 : elements_scalar(void *state, char *token, JsonTokenType tokentype)
2434 : : {
4672 peter_e@gmx.net 2435 : 28848 : ElementsState *_state = (ElementsState *) state;
2436 : :
2437 : : /* json structure check */
4785 andrew@dunslane.net 2438 [ - + ]: 28848 : if (_state->lex->lex_level == 0)
4785 andrew@dunslane.net 2439 [ # # ]:UBC 0 : ereport(ERROR,
2440 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2441 : : errmsg("cannot call %s on a scalar",
2442 : : _state->function_name)));
2443 : :
2444 : : /* supply de-escaped value if required */
4479 andrew@dunslane.net 2445 [ + + ]:CBC 28848 : if (_state->next_scalar)
2446 : 8 : _state->normalized_scalar = token;
2447 : :
1241 tgl@sss.pgh.pa.us 2448 : 28848 : return JSON_SUCCESS;
2449 : : }
2450 : :
2451 : : /*
2452 : : * SQL function json_populate_record
2453 : : *
2454 : : * set fields in a record from the argument json
2455 : : *
2456 : : * Code adapted shamelessly from hstore's populate_record
2457 : : * which is in turn partly adapted from record_out.
2458 : : *
2459 : : * The json is decomposed into a hash table, in which each
2460 : : * field in the record is then looked up by name. For jsonb
2461 : : * we fetch the values direct from the object.
2462 : : */
2463 : : Datum
4426 andrew@dunslane.net 2464 : 588 : jsonb_populate_record(PG_FUNCTION_ARGS)
2465 : : {
2853 tgl@sss.pgh.pa.us 2466 : 588 : return populate_record_worker(fcinfo, "jsonb_populate_record",
2467 : : false, true, NULL);
2468 : : }
2469 : :
2470 : : /*
2471 : : * SQL function that can be used for testing json_populate_record().
2472 : : *
2473 : : * Returns false if json_populate_record() encounters an error for the
2474 : : * provided input JSON object, true otherwise.
2475 : : */
2476 : : Datum
832 amitlan@postgresql.o 2477 : 40 : jsonb_populate_record_valid(PG_FUNCTION_ARGS)
2478 : : {
2479 : 40 : ErrorSaveContext escontext = {T_ErrorSaveContext};
2480 : :
2481 : 40 : (void) populate_record_worker(fcinfo, "jsonb_populate_record",
2482 : : false, true, (Node *) &escontext);
2483 : :
831 2484 : 40 : return BoolGetDatum(!escontext.error_occurred);
2485 : : }
2486 : :
2487 : : Datum
4423 andrew@dunslane.net 2488 : 68 : jsonb_to_record(PG_FUNCTION_ARGS)
2489 : : {
2853 tgl@sss.pgh.pa.us 2490 : 68 : return populate_record_worker(fcinfo, "jsonb_to_record",
2491 : : false, false, NULL);
2492 : : }
2493 : :
2494 : : Datum
4785 andrew@dunslane.net 2495 : 548 : json_populate_record(PG_FUNCTION_ARGS)
2496 : : {
2853 tgl@sss.pgh.pa.us 2497 : 548 : return populate_record_worker(fcinfo, "json_populate_record",
2498 : : true, true, NULL);
2499 : : }
2500 : :
2501 : : Datum
4480 andrew@dunslane.net 2502 : 68 : json_to_record(PG_FUNCTION_ARGS)
2503 : : {
2853 tgl@sss.pgh.pa.us 2504 : 68 : return populate_record_worker(fcinfo, "json_to_record",
2505 : : true, false, NULL);
2506 : : }
2507 : :
2508 : : /* helper function for diagnostics */
2509 : : static void
3316 andrew@dunslane.net 2510 : 288 : populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim)
2511 : : {
2512 [ + + ]: 288 : if (ndim <= 0)
2513 : : {
2514 [ + + ]: 248 : if (ctx->colname)
832 amitlan@postgresql.o 2515 [ + + ]: 72 : errsave(ctx->escontext,
2516 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
2517 : : errmsg("expected JSON array"),
2518 : : errhint("See the value of key \"%s\".", ctx->colname)));
2519 : : else
2520 [ + + ]: 176 : errsave(ctx->escontext,
2521 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
2522 : : errmsg("expected JSON array")));
2523 : 176 : return;
2524 : : }
2525 : : else
2526 : : {
2527 : : StringInfoData indices;
2528 : : int i;
2529 : :
3316 andrew@dunslane.net 2530 : 40 : initStringInfo(&indices);
2531 : :
2532 [ + - - + ]: 40 : Assert(ctx->ndims > 0 && ndim < ctx->ndims);
2533 : :
2534 [ + + ]: 80 : for (i = 0; i < ndim; i++)
2535 : 40 : appendStringInfo(&indices, "[%d]", ctx->sizes[i]);
2536 : :
2537 [ + - ]: 40 : if (ctx->colname)
832 amitlan@postgresql.o 2538 [ + - ]: 40 : errsave(ctx->escontext,
2539 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
2540 : : errmsg("expected JSON array"),
2541 : : errhint("See the array element %s of key \"%s\".",
2542 : : indices.data, ctx->colname)));
2543 : : else
832 amitlan@postgresql.o 2544 [ # # ]:UBC 0 : errsave(ctx->escontext,
2545 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
2546 : : errmsg("expected JSON array"),
2547 : : errhint("See the array element %s.",
2548 : : indices.data)));
2549 : 0 : return;
2550 : : }
2551 : : }
2552 : :
2553 : : /*
2554 : : * Validate and set ndims for populating an array with some
2555 : : * populate_array_*() function.
2556 : : *
2557 : : * Returns false if the input (ndims) is erroneous.
2558 : : */
2559 : : static bool
3316 andrew@dunslane.net 2560 :CBC 1224 : populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims)
2561 : : {
2562 : : int i;
2563 : :
2564 [ - + ]: 1224 : Assert(ctx->ndims <= 0);
2565 : :
2566 [ + + ]: 1224 : if (ndims <= 0)
2567 : : {
2568 : 32 : populate_array_report_expected_array(ctx, ndims);
2569 : : /* Getting here means the error was reported softly. */
832 amitlan@postgresql.o 2570 [ # # # # :UBC 0 : Assert(SOFT_ERROR_OCCURRED(ctx->escontext));
# # ]
2571 : 0 : return false;
2572 : : }
2573 : :
3316 andrew@dunslane.net 2574 :CBC 1192 : ctx->ndims = ndims;
146 michael@paquier.xyz 2575 :GNC 1192 : ctx->dims = palloc_array(int, ndims);
2576 : 1192 : ctx->sizes = palloc0_array(int, ndims);
2577 : :
3316 andrew@dunslane.net 2578 [ + + ]:CBC 2624 : for (i = 0; i < ndims; i++)
3275 bruce@momjian.us 2579 : 1432 : ctx->dims[i] = -1; /* dimensions are unknown yet */
2580 : :
832 amitlan@postgresql.o 2581 : 1192 : return true;
2582 : : }
2583 : :
2584 : : /*
2585 : : * Check the populated subarray dimension
2586 : : *
2587 : : * Returns false if the input (ndims) is erroneous.
2588 : : */
2589 : : static bool
3316 andrew@dunslane.net 2590 : 1036 : populate_array_check_dimension(PopulateArrayContext *ctx, int ndim)
2591 : : {
3275 bruce@momjian.us 2592 : 1036 : int dim = ctx->sizes[ndim]; /* current dimension counter */
2593 : :
3316 andrew@dunslane.net 2594 [ + + ]: 1036 : if (ctx->dims[ndim] == -1)
3275 bruce@momjian.us 2595 : 764 : ctx->dims[ndim] = dim; /* assign dimension if not yet known */
3316 andrew@dunslane.net 2596 [ + + ]: 272 : else if (ctx->dims[ndim] != dim)
832 amitlan@postgresql.o 2597 [ + + ]: 40 : ereturn(ctx->escontext, false,
2598 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
2599 : : errmsg("malformed JSON array"),
2600 : : errdetail("Multidimensional arrays must have "
2601 : : "sub-arrays with matching dimensions.")));
2602 : :
2603 : : /* reset the current array dimension size counter */
3316 andrew@dunslane.net 2604 : 996 : ctx->sizes[ndim] = 0;
2605 : :
2606 : : /* increment the parent dimension counter if it is a nested sub-array */
2607 [ + + ]: 996 : if (ndim > 0)
2608 : 472 : ctx->sizes[ndim - 1]++;
2609 : :
832 amitlan@postgresql.o 2610 : 996 : return true;
2611 : : }
2612 : :
2613 : : /*
2614 : : * Returns true if the array element value was successfully extracted from jsv
2615 : : * and added to ctx->astate. False if an error occurred when doing so.
2616 : : */
2617 : : static bool
3316 andrew@dunslane.net 2618 : 4108 : populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv)
2619 : : {
2620 : : Datum element;
2621 : : bool element_isnull;
2622 : :
2623 : : /* populate the array element */
2624 : 4108 : element = populate_record_field(ctx->aio->element_info,
2625 : 4108 : ctx->aio->element_type,
2626 : 4108 : ctx->aio->element_typmod,
2627 : : NULL, ctx->mcxt, PointerGetDatum(NULL),
2628 : : jsv, &element_isnull, ctx->escontext,
2629 : : false);
2630 : : /* Nothing to do on an error. */
832 amitlan@postgresql.o 2631 [ + + + - : 4088 : if (SOFT_ERROR_OCCURRED(ctx->escontext))
+ + ]
2632 : 4 : return false;
2633 : :
3316 andrew@dunslane.net 2634 : 4084 : accumArrayResult(ctx->astate, element, element_isnull,
3275 bruce@momjian.us 2635 : 4084 : ctx->aio->element_type, ctx->acxt);
2636 : :
3316 andrew@dunslane.net 2637 [ - + ]: 4084 : Assert(ndim > 0);
3275 bruce@momjian.us 2638 : 4084 : ctx->sizes[ndim - 1]++; /* increment current dimension counter */
2639 : :
832 amitlan@postgresql.o 2640 : 4084 : return true;
2641 : : }
2642 : :
2643 : : /* json object start handler for populate_array_json() */
2644 : : static JsonParseErrorType
3316 andrew@dunslane.net 2645 : 432 : populate_array_object_start(void *_state)
2646 : : {
2647 : 432 : PopulateArrayState *state = (PopulateArrayState *) _state;
3275 bruce@momjian.us 2648 : 432 : int ndim = state->lex->lex_level;
2649 : :
3316 andrew@dunslane.net 2650 [ + + ]: 432 : if (state->ctx->ndims <= 0)
2651 : : {
832 amitlan@postgresql.o 2652 [ - + ]: 208 : if (!populate_array_assign_ndims(state->ctx, ndim))
832 amitlan@postgresql.o 2653 :UBC 0 : return JSON_SEM_ACTION_FAILED;
2654 : : }
3316 andrew@dunslane.net 2655 [ + + ]:CBC 224 : else if (ndim < state->ctx->ndims)
2656 : : {
2657 : 8 : populate_array_report_expected_array(state->ctx, ndim);
2658 : : /* Getting here means the error was reported softly. */
832 amitlan@postgresql.o 2659 [ # # # # :UBC 0 : Assert(SOFT_ERROR_OCCURRED(state->ctx->escontext));
# # ]
2660 : 0 : return JSON_SEM_ACTION_FAILED;
2661 : : }
2662 : :
1241 tgl@sss.pgh.pa.us 2663 :CBC 424 : return JSON_SUCCESS;
2664 : : }
2665 : :
2666 : : /* json array end handler for populate_array_json() */
2667 : : static JsonParseErrorType
3316 andrew@dunslane.net 2668 : 768 : populate_array_array_end(void *_state)
2669 : : {
3275 bruce@momjian.us 2670 : 768 : PopulateArrayState *state = (PopulateArrayState *) _state;
2671 : 768 : PopulateArrayContext *ctx = state->ctx;
2672 : 768 : int ndim = state->lex->lex_level;
2673 : :
3316 andrew@dunslane.net 2674 [ + + ]: 768 : if (ctx->ndims <= 0)
2675 : : {
832 amitlan@postgresql.o 2676 [ - + ]: 8 : if (!populate_array_assign_ndims(ctx, ndim + 1))
832 amitlan@postgresql.o 2677 :UBC 0 : return JSON_SEM_ACTION_FAILED;
2678 : : }
2679 : :
3316 andrew@dunslane.net 2680 [ + + ]:CBC 768 : if (ndim < ctx->ndims)
2681 : : {
2682 : : /* Report if an error occurred. */
832 amitlan@postgresql.o 2683 [ - + ]: 764 : if (!populate_array_check_dimension(ctx, ndim))
832 amitlan@postgresql.o 2684 :UBC 0 : return JSON_SEM_ACTION_FAILED;
2685 : : }
2686 : :
1241 tgl@sss.pgh.pa.us 2687 :CBC 752 : return JSON_SUCCESS;
2688 : : }
2689 : :
2690 : : /* json array element start handler for populate_array_json() */
2691 : : static JsonParseErrorType
3316 andrew@dunslane.net 2692 : 2244 : populate_array_element_start(void *_state, bool isnull)
2693 : : {
2694 : 2244 : PopulateArrayState *state = (PopulateArrayState *) _state;
3275 bruce@momjian.us 2695 : 2244 : int ndim = state->lex->lex_level;
2696 : :
3316 andrew@dunslane.net 2697 [ + + + + ]: 2244 : if (state->ctx->ndims <= 0 || ndim == state->ctx->ndims)
2698 : : {
2699 : : /* remember current array element start */
2700 : 2080 : state->element_start = state->lex->token_start;
2701 : 2080 : state->element_type = state->lex->token_type;
2702 : 2080 : state->element_scalar = NULL;
2703 : : }
2704 : :
1241 tgl@sss.pgh.pa.us 2705 : 2244 : return JSON_SUCCESS;
2706 : : }
2707 : :
2708 : : /* json array element end handler for populate_array_json() */
2709 : : static JsonParseErrorType
3316 andrew@dunslane.net 2710 : 2208 : populate_array_element_end(void *_state, bool isnull)
2711 : : {
3275 bruce@momjian.us 2712 : 2208 : PopulateArrayState *state = (PopulateArrayState *) _state;
2713 : 2208 : PopulateArrayContext *ctx = state->ctx;
2714 : 2208 : int ndim = state->lex->lex_level;
2715 : :
3316 andrew@dunslane.net 2716 [ - + ]: 2208 : Assert(ctx->ndims > 0);
2717 : :
2718 [ + + ]: 2208 : if (ndim == ctx->ndims)
2719 : : {
2720 : : JsValue jsv;
2721 : :
2722 : 1968 : jsv.is_json = true;
2723 : 1968 : jsv.val.json.type = state->element_type;
2724 : :
2725 [ + + ]: 1968 : if (isnull)
2726 : : {
2727 [ - + ]: 472 : Assert(jsv.val.json.type == JSON_TOKEN_NULL);
2728 : 472 : jsv.val.json.str = NULL;
2729 : 472 : jsv.val.json.len = 0;
2730 : : }
2731 [ + + ]: 1496 : else if (state->element_scalar)
2732 : : {
2733 : 1072 : jsv.val.json.str = state->element_scalar;
3240 tgl@sss.pgh.pa.us 2734 : 1072 : jsv.val.json.len = -1; /* null-terminated */
2735 : : }
2736 : : else
2737 : : {
3316 andrew@dunslane.net 2738 : 424 : jsv.val.json.str = state->element_start;
2739 : 424 : jsv.val.json.len = (state->lex->prev_token_terminator -
2740 : 424 : state->element_start) * sizeof(char);
2741 : : }
2742 : :
2743 : : /* Report if an error occurred. */
832 amitlan@postgresql.o 2744 [ - + ]: 1968 : if (!populate_array_element(ctx, ndim, &jsv))
832 amitlan@postgresql.o 2745 :UBC 0 : return JSON_SEM_ACTION_FAILED;
2746 : : }
2747 : :
1241 tgl@sss.pgh.pa.us 2748 :CBC 2200 : return JSON_SUCCESS;
2749 : : }
2750 : :
2751 : : /* json scalar handler for populate_array_json() */
2752 : : static JsonParseErrorType
3316 andrew@dunslane.net 2753 : 2436 : populate_array_scalar(void *_state, char *token, JsonTokenType tokentype)
2754 : : {
3275 bruce@momjian.us 2755 : 2436 : PopulateArrayState *state = (PopulateArrayState *) _state;
2756 : 2436 : PopulateArrayContext *ctx = state->ctx;
2757 : 2436 : int ndim = state->lex->lex_level;
2758 : :
3316 andrew@dunslane.net 2759 [ + + ]: 2436 : if (ctx->ndims <= 0)
2760 : : {
832 amitlan@postgresql.o 2761 [ - + ]: 384 : if (!populate_array_assign_ndims(ctx, ndim))
832 amitlan@postgresql.o 2762 :UBC 0 : return JSON_SEM_ACTION_FAILED;
2763 : : }
3316 andrew@dunslane.net 2764 [ + + ]:CBC 2052 : else if (ndim < ctx->ndims)
2765 : : {
2766 : 12 : populate_array_report_expected_array(ctx, ndim);
2767 : : /* Getting here means the error was reported softly. */
832 amitlan@postgresql.o 2768 [ # # # # :UBC 0 : Assert(SOFT_ERROR_OCCURRED(ctx->escontext));
# # ]
2769 : 0 : return JSON_SEM_ACTION_FAILED;
2770 : : }
2771 : :
3316 andrew@dunslane.net 2772 [ + + ]:CBC 2392 : if (ndim == ctx->ndims)
2773 : : {
2774 : : /* remember the scalar element token */
2775 : 1544 : state->element_scalar = token;
2776 : : /* element_type must already be set in populate_array_element_start() */
2777 [ - + ]: 1544 : Assert(state->element_type == tokentype);
2778 : : }
2779 : :
1241 tgl@sss.pgh.pa.us 2780 : 2392 : return JSON_SUCCESS;
2781 : : }
2782 : :
2783 : : /*
2784 : : * Parse a json array and populate array
2785 : : *
2786 : : * Returns false if an error occurs when parsing.
2787 : : */
2788 : : static bool
683 peter@eisentraut.org 2789 : 600 : populate_array_json(PopulateArrayContext *ctx, const char *json, int len)
2790 : : {
2791 : : PopulateArrayState state;
2792 : : JsonSemAction sem;
2793 : :
943 alvherre@alvh.no-ip. 2794 : 600 : state.lex = makeJsonLexContextCstringLen(NULL, json, len,
2795 : : GetDatabaseEncoding(), true);
3316 andrew@dunslane.net 2796 : 600 : state.ctx = ctx;
2797 : :
2798 : 600 : memset(&sem, 0, sizeof(sem));
523 peter@eisentraut.org 2799 : 600 : sem.semstate = &state;
3316 andrew@dunslane.net 2800 : 600 : sem.object_start = populate_array_object_start;
2801 : 600 : sem.array_end = populate_array_array_end;
2802 : 600 : sem.array_element_start = populate_array_element_start;
2803 : 600 : sem.array_element_end = populate_array_element_end;
2804 : 600 : sem.scalar = populate_array_scalar;
2805 : :
832 amitlan@postgresql.o 2806 [ + - ]: 600 : if (pg_parse_json_or_errsave(state.lex, &sem, ctx->escontext))
2807 : : {
2808 : : /* number of dimensions should be already known */
2809 [ + - - + ]: 524 : Assert(ctx->ndims > 0 && ctx->dims);
2810 : : }
2811 : :
943 alvherre@alvh.no-ip. 2812 : 524 : freeJsonLexContext(state.lex);
2813 : :
832 amitlan@postgresql.o 2814 [ - + - - : 524 : return !SOFT_ERROR_OCCURRED(ctx->escontext);
- - ]
2815 : : }
2816 : :
2817 : : /*
2818 : : * populate_array_dim_jsonb() -- Iterate recursively through jsonb sub-array
2819 : : * elements and accumulate result using given ArrayBuildState.
2820 : : *
2821 : : * Returns false if we return partway through because of an error in a
2822 : : * subroutine.
2823 : : */
2824 : : static bool
3240 tgl@sss.pgh.pa.us 2825 : 1132 : populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */
2826 : : JsonbValue *jbv, /* jsonb sub-array */
2827 : : int ndim) /* current dimension */
2828 : : {
3275 bruce@momjian.us 2829 : 1132 : JsonbContainer *jbc = jbv->val.binary.data;
2830 : : JsonbIterator *it;
2831 : : JsonbIteratorToken tok;
2832 : : JsonbValue val;
2833 : : JsValue jsv;
2834 : :
3316 andrew@dunslane.net 2835 : 1132 : check_stack_depth();
2836 : :
2837 : : /* Even scalars can end up here thanks to ExecEvalJsonCoercion(). */
775 amitlan@postgresql.o 2838 [ + + + + ]: 1132 : if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc) ||
2839 [ + + ]: 1040 : JsonContainerIsScalar(jbc))
2840 : : {
3316 andrew@dunslane.net 2841 : 236 : populate_array_report_expected_array(ctx, ndim - 1);
2842 : : /* Getting here means the error was reported softly. */
832 amitlan@postgresql.o 2843 [ + - + - : 176 : Assert(SOFT_ERROR_OCCURRED(ctx->escontext));
- + ]
2844 : 176 : return false;
2845 : : }
2846 : :
3316 andrew@dunslane.net 2847 : 896 : it = JsonbIteratorInit(jbc);
2848 : :
2849 : 896 : tok = JsonbIteratorNext(&it, &val, true);
2850 [ - + ]: 896 : Assert(tok == WJB_BEGIN_ARRAY);
2851 : :
2852 : 896 : tok = JsonbIteratorNext(&it, &val, true);
2853 : :
2854 : : /*
2855 : : * If the number of dimensions is not yet known and we have found end of
2856 : : * the array, or the first child element is not an array, then assign the
2857 : : * number of dimensions now.
2858 : : */
2859 [ + + + + ]: 896 : if (ctx->ndims <= 0 &&
2860 [ + - ]: 744 : (tok == WJB_END_ARRAY ||
2861 : 744 : (tok == WJB_ELEM &&
2862 [ + + ]: 744 : (val.type != jbvBinary ||
2863 [ + + ]: 348 : !JsonContainerIsArray(val.val.binary.data)))))
2864 : : {
832 amitlan@postgresql.o 2865 [ - + ]: 624 : if (!populate_array_assign_ndims(ctx, ndim))
832 amitlan@postgresql.o 2866 :UBC 0 : return false;
2867 : : }
2868 : :
3316 andrew@dunslane.net 2869 :CBC 896 : jsv.is_json = false;
2870 : 896 : jsv.val.jsonb = &val;
2871 : :
2872 : : /* process all the array elements */
2873 [ + + ]: 3268 : while (tok == WJB_ELEM)
2874 : : {
2875 : : /*
2876 : : * Recurse only if the dimensions of dimensions is still unknown or if
2877 : : * it is not the innermost dimension.
2878 : : */
2879 [ + + + + ]: 2432 : if (ctx->ndims > 0 && ndim >= ctx->ndims)
2880 : : {
832 amitlan@postgresql.o 2881 [ + + ]: 2140 : if (!populate_array_element(ctx, ndim, &jsv))
2882 : 4 : return false;
2883 : : }
2884 : : else
2885 : : {
2886 : : /* populate child sub-array */
2887 [ - + ]: 292 : if (!populate_array_dim_jsonb(ctx, &val, ndim + 1))
832 amitlan@postgresql.o 2888 :UBC 0 : return false;
2889 : :
2890 : : /* number of dimensions should be already known */
3316 andrew@dunslane.net 2891 [ + - - + ]:CBC 272 : Assert(ctx->ndims > 0 && ctx->dims);
2892 : :
832 amitlan@postgresql.o 2893 [ + + ]: 272 : if (!populate_array_check_dimension(ctx, ndim))
2894 : 4 : return false;
2895 : : }
2896 : :
3316 andrew@dunslane.net 2897 : 2372 : tok = JsonbIteratorNext(&it, &val, true);
2898 : : }
2899 : :
2900 [ - + ]: 836 : Assert(tok == WJB_END_ARRAY);
2901 : :
2902 : : /* free iterator, iterating until WJB_DONE */
2903 : 836 : tok = JsonbIteratorNext(&it, &val, true);
2904 [ + - - + ]: 836 : Assert(tok == WJB_DONE && !it);
2905 : :
832 amitlan@postgresql.o 2906 : 836 : return true;
2907 : : }
2908 : :
2909 : : /*
2910 : : * Recursively populate an array from json/jsonb
2911 : : *
2912 : : * *isnull is set to true if an error is reported during parsing.
2913 : : */
2914 : : static Datum
3275 bruce@momjian.us 2915 : 1440 : populate_array(ArrayIOData *aio,
2916 : : const char *colname,
2917 : : MemoryContext mcxt,
2918 : : JsValue *jsv,
2919 : : bool *isnull,
2920 : : Node *escontext)
2921 : : {
2922 : : PopulateArrayContext ctx;
2923 : : Datum result;
2924 : : int *lbs;
2925 : : int i;
2926 : :
3316 andrew@dunslane.net 2927 : 1440 : ctx.aio = aio;
2928 : 1440 : ctx.mcxt = mcxt;
2929 : 1440 : ctx.acxt = CurrentMemoryContext;
2930 : 1440 : ctx.astate = initArrayResult(aio->element_type, ctx.acxt, true);
2931 : 1440 : ctx.colname = colname;
3275 bruce@momjian.us 2932 : 1440 : ctx.ndims = 0; /* unknown yet */
3316 andrew@dunslane.net 2933 : 1440 : ctx.dims = NULL;
2934 : 1440 : ctx.sizes = NULL;
832 amitlan@postgresql.o 2935 : 1440 : ctx.escontext = escontext;
2936 : :
3316 andrew@dunslane.net 2937 [ + + ]: 1440 : if (jsv->is_json)
2938 : : {
2939 : : /* Return null if an error was found. */
832 amitlan@postgresql.o 2940 [ - + ]: 600 : if (!populate_array_json(&ctx, jsv->val.json.str,
2941 [ - + ]: 600 : jsv->val.json.len >= 0 ? jsv->val.json.len
2942 : 600 : : strlen(jsv->val.json.str)))
2943 : : {
832 amitlan@postgresql.o 2944 :UBC 0 : *isnull = true;
2945 : 0 : return (Datum) 0;
2946 : : }
2947 : : }
2948 : : else
2949 : : {
2950 : : /* Return null if an error was found. */
832 amitlan@postgresql.o 2951 [ + + ]:CBC 840 : if (!populate_array_dim_jsonb(&ctx, jsv->val.jsonb, 1))
2952 : : {
2953 : 184 : *isnull = true;
2954 : 184 : return (Datum) 0;
2955 : : }
3316 andrew@dunslane.net 2956 : 564 : ctx.dims[0] = ctx.sizes[0];
2957 : : }
2958 : :
2959 [ - + ]: 1088 : Assert(ctx.ndims > 0);
2960 : :
146 michael@paquier.xyz 2961 :GNC 1088 : lbs = palloc_array(int, ctx.ndims);
2962 : :
3316 andrew@dunslane.net 2963 [ + + ]:CBC 2328 : for (i = 0; i < ctx.ndims; i++)
2964 : 1240 : lbs[i] = 1;
2965 : :
2966 : 1088 : result = makeMdArrayResult(ctx.astate, ctx.ndims, ctx.dims, lbs,
2967 : : ctx.acxt, true);
2968 : :
2969 : 1088 : pfree(ctx.dims);
2970 : 1088 : pfree(ctx.sizes);
2971 : 1088 : pfree(lbs);
2972 : :
832 amitlan@postgresql.o 2973 : 1088 : *isnull = false;
3316 andrew@dunslane.net 2974 : 1088 : return result;
2975 : : }
2976 : :
2977 : : /*
2978 : : * Returns false if an error occurs, provided escontext points to an
2979 : : * ErrorSaveContext.
2980 : : */
2981 : : static bool
832 amitlan@postgresql.o 2982 : 2636 : JsValueToJsObject(JsValue *jsv, JsObject *jso, Node *escontext)
2983 : : {
3316 andrew@dunslane.net 2984 : 2636 : jso->is_json = jsv->is_json;
2985 : :
2986 [ + + ]: 2636 : if (jsv->is_json)
2987 : : {
2988 : : /* convert plain-text json into a hash table */
2989 : 1244 : jso->val.json_hash =
3275 bruce@momjian.us 2990 : 1256 : get_json_object_as_hash(jsv->val.json.str,
2991 [ + + ]: 1256 : jsv->val.json.len >= 0
2992 : : ? jsv->val.json.len
2993 : 228 : : strlen(jsv->val.json.str),
2994 : : "populate_composite",
2995 : : escontext);
832 amitlan@postgresql.o 2996 [ - + - - : 1244 : Assert(jso->val.json_hash != NULL || SOFT_ERROR_OCCURRED(escontext));
- - - - ]
2997 : : }
2998 : : else
2999 : : {
3316 andrew@dunslane.net 3000 : 1380 : JsonbValue *jbv = jsv->val.jsonb;
3001 : :
3002 [ + + ]: 1380 : if (jbv->type == jbvBinary &&
3003 [ + + ]: 1372 : JsonContainerIsObject(jbv->val.binary.data))
3004 : : {
3005 : 1360 : jso->val.jsonb_cont = jbv->val.binary.data;
3006 : : }
3007 : : else
3008 : : {
3009 : : bool is_scalar;
3010 : :
3263 tgl@sss.pgh.pa.us 3011 [ + + + - ]: 32 : is_scalar = IsAJsonbScalar(jbv) ||
3012 [ + - ]: 12 : (jbv->type == jbvBinary &&
3013 [ + + ]: 12 : JsonContainerIsScalar(jbv->val.binary.data));
832 amitlan@postgresql.o 3014 [ + + + + ]: 20 : errsave(escontext,
3015 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3016 : : is_scalar
3017 : : ? errmsg("cannot call %s on a scalar",
3018 : : "populate_composite")
3019 : : : errmsg("cannot call %s on an array",
3020 : : "populate_composite")));
3021 : : }
3022 : : }
3023 : :
3024 [ + + + - : 2608 : return !SOFT_ERROR_OCCURRED(escontext);
+ + ]
3025 : : }
3026 : :
3027 : : /* acquire or update cached tuple descriptor for a composite type */
3028 : : static void
3113 tgl@sss.pgh.pa.us 3029 : 3168 : update_cached_tupdesc(CompositeIOData *io, MemoryContext mcxt)
3030 : : {
3316 andrew@dunslane.net 3031 [ + + ]: 3168 : if (!io->tupdesc ||
3113 tgl@sss.pgh.pa.us 3032 [ + - ]: 1752 : io->tupdesc->tdtypeid != io->base_typid ||
3033 [ - + ]: 1752 : io->tupdesc->tdtypmod != io->base_typmod)
3034 : : {
3035 : 1416 : TupleDesc tupdesc = lookup_rowtype_tupdesc(io->base_typid,
3036 : : io->base_typmod);
3037 : : MemoryContext oldcxt;
3038 : :
3316 andrew@dunslane.net 3039 [ - + ]: 1416 : if (io->tupdesc)
3316 andrew@dunslane.net 3040 :UBC 0 : FreeTupleDesc(io->tupdesc);
3041 : :
3042 : : /* copy tuple desc without constraints into cache memory context */
3316 andrew@dunslane.net 3043 :CBC 1416 : oldcxt = MemoryContextSwitchTo(mcxt);
3044 : 1416 : io->tupdesc = CreateTupleDescCopy(tupdesc);
3045 : 1416 : MemoryContextSwitchTo(oldcxt);
3046 : :
3047 [ + - ]: 1416 : ReleaseTupleDesc(tupdesc);
3048 : : }
3113 tgl@sss.pgh.pa.us 3049 : 3168 : }
3050 : :
3051 : : /*
3052 : : * Recursively populate a composite (row type) value from json/jsonb
3053 : : *
3054 : : * Returns null if an error occurs in a subroutine, provided escontext points
3055 : : * to an ErrorSaveContext.
3056 : : */
3057 : : static Datum
3058 : 2636 : populate_composite(CompositeIOData *io,
3059 : : Oid typid,
3060 : : const char *colname,
3061 : : MemoryContext mcxt,
3062 : : HeapTupleHeader defaultval,
3063 : : JsValue *jsv,
3064 : : bool *isnull,
3065 : : Node *escontext)
3066 : : {
3067 : : Datum result;
3068 : :
3069 : : /* acquire/update cached tuple descriptor */
3070 : 2636 : update_cached_tupdesc(io, mcxt);
3071 : :
832 amitlan@postgresql.o 3072 [ - + ]: 2636 : if (*isnull)
3113 tgl@sss.pgh.pa.us 3073 :UBC 0 : result = (Datum) 0;
3074 : : else
3075 : : {
3076 : : HeapTupleHeader tuple;
3077 : : JsObject jso;
3078 : :
3079 : : /* prepare input value */
832 amitlan@postgresql.o 3080 [ + + ]:CBC 2636 : if (!JsValueToJsObject(jsv, &jso, escontext))
3081 : : {
3082 : 4 : *isnull = true;
3083 : 28 : return (Datum) 0;
3084 : : }
3085 : :
3086 : : /* populate resulting record tuple */
3113 tgl@sss.pgh.pa.us 3087 : 2604 : tuple = populate_record(io->tupdesc, &io->record_io,
3088 : : defaultval, mcxt, &jso, escontext);
3089 : :
832 amitlan@postgresql.o 3090 [ + + + - : 2364 : if (SOFT_ERROR_OCCURRED(escontext))
+ + ]
3091 : : {
3092 : 24 : *isnull = true;
3093 : 24 : return (Datum) 0;
3094 : : }
3113 tgl@sss.pgh.pa.us 3095 : 2340 : result = HeapTupleHeaderGetDatum(tuple);
3096 : :
3097 [ + + ]: 2340 : JsObjectFree(&jso);
3098 : : }
3099 : :
3100 : : /*
3101 : : * If it's domain over composite, check domain constraints. (This should
3102 : : * probably get refactored so that we can see the TYPECAT value, but for
3103 : : * now, we can tell by comparing typid to base_typid.)
3104 : : */
3105 [ + + + - ]: 2340 : if (typid != io->base_typid && typid != RECORDOID)
3106 : : {
832 amitlan@postgresql.o 3107 [ - + ]: 24 : if (!domain_check_safe(result, *isnull, typid, &io->domain_info, mcxt,
3108 : : escontext))
3109 : : {
832 amitlan@postgresql.o 3110 :UBC 0 : *isnull = true;
3111 : 0 : return (Datum) 0;
3112 : : }
3113 : : }
3114 : :
3113 tgl@sss.pgh.pa.us 3115 :CBC 2332 : return result;
3116 : : }
3117 : :
3118 : : /*
3119 : : * Populate non-null scalar value from json/jsonb value.
3120 : : *
3121 : : * Returns null if an error occurs during the call to type input function,
3122 : : * provided escontext is valid.
3123 : : */
3124 : : static Datum
832 amitlan@postgresql.o 3125 : 6152 : populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv,
3126 : : bool *isnull, Node *escontext, bool omit_quotes)
3127 : : {
3128 : : Datum res;
3316 andrew@dunslane.net 3129 : 6152 : char *str = NULL;
683 peter@eisentraut.org 3130 : 6152 : const char *json = NULL;
3131 : :
3316 andrew@dunslane.net 3132 [ + + ]: 6152 : if (jsv->is_json)
3133 : : {
3134 : 2544 : int len = jsv->val.json.len;
3135 : :
3136 : 2544 : json = jsv->val.json.str;
3137 [ - + ]: 2544 : Assert(json);
3138 : :
3139 : : /* If converting to json/jsonb, make string into valid JSON literal */
2520 tgl@sss.pgh.pa.us 3140 [ + + + + ]: 2544 : if ((typid == JSONOID || typid == JSONBOID) &&
3141 [ + + ]: 728 : jsv->val.json.type == JSON_TOKEN_STRING)
3142 : 236 : {
3143 : : StringInfoData buf;
3144 : :
3145 : 236 : initStringInfo(&buf);
647 drowley@postgresql.o 3146 [ - + ]: 236 : if (len >= 0)
647 drowley@postgresql.o 3147 :UBC 0 : escape_json_with_len(&buf, json, len);
3148 : : else
647 drowley@postgresql.o 3149 :CBC 236 : escape_json(&buf, json);
2520 tgl@sss.pgh.pa.us 3150 : 236 : str = buf.data;
3151 : : }
647 drowley@postgresql.o 3152 [ + + ]: 2308 : else if (len >= 0)
3153 : : {
3154 : : /* create a NUL-terminated version */
3155 : 8 : str = palloc(len + 1);
3156 : 8 : memcpy(str, json, len);
3157 : 8 : str[len] = '\0';
3158 : : }
3159 : : else
3160 : : {
3161 : : /* string is already NUL-terminated */
3162 : 2300 : str = unconstify(char *, json);
3163 : : }
3164 : : }
3165 : : else
3166 : : {
3316 andrew@dunslane.net 3167 : 3608 : JsonbValue *jbv = jsv->val.jsonb;
3168 : :
676 amitlan@postgresql.o 3169 [ + + + + ]: 3608 : if (jbv->type == jbvString && omit_quotes)
3170 : 248 : str = pnstrdup(jbv->val.string.val, jbv->val.string.len);
3171 [ + + ]: 3360 : else if (typid == JSONBOID)
3172 : : {
3275 bruce@momjian.us 3173 : 64 : Jsonb *jsonb = JsonbValueToJsonb(jbv); /* directly use jsonb */
3174 : :
3151 tgl@sss.pgh.pa.us 3175 : 64 : return JsonbPGetDatum(jsonb);
3176 : : }
3177 : : /* convert jsonb to string for typio call */
3316 andrew@dunslane.net 3178 [ + + + + ]: 3296 : else if (typid == JSONOID && jbv->type != jbvBinary)
3179 : 652 : {
3180 : : /*
3181 : : * Convert scalar jsonb (non-scalars are passed here as jbvBinary)
3182 : : * to json string, preserving quotes around top-level strings.
3183 : : */
3275 bruce@momjian.us 3184 : 652 : Jsonb *jsonb = JsonbValueToJsonb(jbv);
3185 : :
3316 andrew@dunslane.net 3186 : 652 : str = JsonbToCString(NULL, &jsonb->root, VARSIZE(jsonb));
3187 : : }
3240 tgl@sss.pgh.pa.us 3188 [ + + ]: 2644 : else if (jbv->type == jbvString) /* quotes are stripped */
3316 andrew@dunslane.net 3189 : 1072 : str = pnstrdup(jbv->val.string.val, jbv->val.string.len);
3190 [ + + ]: 1572 : else if (jbv->type == jbvBool)
3191 [ + - ]: 4 : str = pstrdup(jbv->val.boolean ? "true" : "false");
3192 [ + + ]: 1568 : else if (jbv->type == jbvNumeric)
3193 : 892 : str = DatumGetCString(DirectFunctionCall1(numeric_out,
3194 : : PointerGetDatum(jbv->val.numeric)));
3195 [ + - ]: 676 : else if (jbv->type == jbvBinary)
3196 : 676 : str = JsonbToCString(NULL, jbv->val.binary.data,
3197 : : jbv->val.binary.len);
3198 : : else
3316 andrew@dunslane.net 3199 [ # # ]:UBC 0 : elog(ERROR, "unrecognized jsonb type: %d", (int) jbv->type);
3200 : : }
3201 : :
832 amitlan@postgresql.o 3202 [ + + ]:CBC 6088 : if (!InputFunctionCallSafe(&io->typiofunc, str, io->typioparam, typmod,
3203 : : escontext, &res))
3204 : : {
3205 : 148 : res = (Datum) 0;
3206 : 148 : *isnull = true;
3207 : : }
3208 : :
3209 : : /* free temporary buffer */
3316 andrew@dunslane.net 3210 [ + + ]: 5976 : if (str != json)
3211 : 3692 : pfree(str);
3212 : :
3213 : 5976 : return res;
3214 : : }
3215 : :
3216 : : static Datum
3275 bruce@momjian.us 3217 : 1972 : populate_domain(DomainIOData *io,
3218 : : Oid typid,
3219 : : const char *colname,
3220 : : MemoryContext mcxt,
3221 : : JsValue *jsv,
3222 : : bool *isnull,
3223 : : Node *escontext,
3224 : : bool omit_quotes)
3225 : : {
3226 : : Datum res;
3227 : :
832 amitlan@postgresql.o 3228 [ + + ]: 1972 : if (*isnull)
3316 andrew@dunslane.net 3229 : 1804 : res = (Datum) 0;
3230 : : else
3231 : : {
3232 : 168 : res = populate_record_field(io->base_io,
3233 : : io->base_typid, io->base_typmod,
3234 : : colname, mcxt, PointerGetDatum(NULL),
3235 : : jsv, isnull, escontext, omit_quotes);
832 amitlan@postgresql.o 3236 [ + + + - : 148 : Assert(!*isnull || SOFT_ERROR_OCCURRED(escontext));
+ - - + ]
3237 : : }
3238 : :
3239 [ + + ]: 1952 : if (!domain_check_safe(res, *isnull, typid, &io->domain_info, mcxt,
3240 : : escontext))
3241 : : {
3242 : 52 : *isnull = true;
3243 : 52 : return (Datum) 0;
3244 : : }
3245 : :
3316 andrew@dunslane.net 3246 : 1844 : return res;
3247 : : }
3248 : :
3249 : : /* prepare column metadata cache for the given type */
3250 : : static void
3275 bruce@momjian.us 3251 : 14380 : prepare_column_cache(ColumnIOData *column,
3252 : : Oid typid,
3253 : : int32 typmod,
3254 : : MemoryContext mcxt,
3255 : : bool need_scalar)
3256 : : {
3257 : : HeapTuple tup;
3258 : : Form_pg_type type;
3259 : :
3316 andrew@dunslane.net 3260 : 14380 : column->typid = typid;
3261 : 14380 : column->typmod = typmod;
3262 : :
3263 : 14380 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
3264 [ - + ]: 14380 : if (!HeapTupleIsValid(tup))
3316 andrew@dunslane.net 3265 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", typid);
3266 : :
3316 andrew@dunslane.net 3267 :CBC 14380 : type = (Form_pg_type) GETSTRUCT(tup);
3268 : :
3269 [ + + ]: 14380 : if (type->typtype == TYPTYPE_DOMAIN)
3270 : : {
3271 : : /*
3272 : : * We can move directly to the bottom base type; domain_check() will
3273 : : * take care of checking all constraints for a stack of domains.
3274 : : */
3275 : : Oid base_typid;
3113 tgl@sss.pgh.pa.us 3276 : 1428 : int32 base_typmod = typmod;
3277 : :
3278 : 1428 : base_typid = getBaseTypeAndTypmod(typid, &base_typmod);
3279 [ + + ]: 1428 : if (get_typtype(base_typid) == TYPTYPE_COMPOSITE)
3280 : : {
3281 : : /* domain over composite has its own code path */
3282 : 48 : column->typcat = TYPECAT_COMPOSITE_DOMAIN;
3283 : 48 : column->io.composite.record_io = NULL;
3284 : 48 : column->io.composite.tupdesc = NULL;
3285 : 48 : column->io.composite.base_typid = base_typid;
3286 : 48 : column->io.composite.base_typmod = base_typmod;
3287 : 48 : column->io.composite.domain_info = NULL;
3288 : : }
3289 : : else
3290 : : {
3291 : : /* domain over anything else */
3292 : 1380 : column->typcat = TYPECAT_DOMAIN;
3293 : 1380 : column->io.domain.base_typid = base_typid;
3294 : 1380 : column->io.domain.base_typmod = base_typmod;
3295 : 1380 : column->io.domain.base_io =
3296 : 1380 : MemoryContextAllocZero(mcxt, sizeof(ColumnIOData));
3297 : 1380 : column->io.domain.domain_info = NULL;
3298 : : }
3299 : : }
3316 andrew@dunslane.net 3300 [ + + + + ]: 12952 : else if (type->typtype == TYPTYPE_COMPOSITE || typid == RECORDOID)
3301 : : {
3302 : 1816 : column->typcat = TYPECAT_COMPOSITE;
3303 : 1816 : column->io.composite.record_io = NULL;
3304 : 1816 : column->io.composite.tupdesc = NULL;
3113 tgl@sss.pgh.pa.us 3305 : 1816 : column->io.composite.base_typid = typid;
3306 : 1816 : column->io.composite.base_typmod = typmod;
3307 : 1816 : column->io.composite.domain_info = NULL;
3308 : : }
1973 3309 [ + + + - ]: 11136 : else if (IsTrueArrayType(type))
3310 : : {
3316 andrew@dunslane.net 3311 : 5112 : column->typcat = TYPECAT_ARRAY;
3312 : 5112 : column->io.array.element_info = MemoryContextAllocZero(mcxt,
3313 : : sizeof(ColumnIOData));
3314 : 5112 : column->io.array.element_type = type->typelem;
3315 : : /* array element typemod stored in attribute's typmod */
3316 : 5112 : column->io.array.element_typmod = typmod;
3317 : : }
3318 : : else
3319 : : {
3320 : 6024 : column->typcat = TYPECAT_SCALAR;
3113 tgl@sss.pgh.pa.us 3321 : 6024 : need_scalar = true;
3322 : : }
3323 : :
3324 : : /* caller can force us to look up scalar_io info even for non-scalars */
3325 [ + + ]: 14380 : if (need_scalar)
3326 : : {
3327 : : Oid typioproc;
3328 : :
3316 andrew@dunslane.net 3329 : 13280 : getTypeInputInfo(typid, &typioproc, &column->scalar_io.typioparam);
3330 : 13280 : fmgr_info_cxt(typioproc, &column->scalar_io.typiofunc, mcxt);
3331 : : }
3332 : :
3333 : 14380 : ReleaseSysCache(tup);
3334 : 14380 : }
3335 : :
3336 : : /*
3337 : : * Populate and return the value of specified type from a given json/jsonb
3338 : : * value 'json_val'. 'cache' is caller-specified pointer to save the
3339 : : * ColumnIOData that will be initialized on the 1st call and then reused
3340 : : * during any subsequent calls. 'mcxt' gives the memory context to allocate
3341 : : * the ColumnIOData and any other subsidiary memory in. 'escontext',
3342 : : * if not NULL, tells that any errors that occur should be handled softly.
3343 : : */
3344 : : Datum
775 amitlan@postgresql.o 3345 : 1120 : json_populate_type(Datum json_val, Oid json_type,
3346 : : Oid typid, int32 typmod,
3347 : : void **cache, MemoryContext mcxt,
3348 : : bool *isnull, bool omit_quotes,
3349 : : Node *escontext)
3350 : : {
3351 : 1120 : JsValue jsv = {0};
3352 : : JsonbValue jbv;
3353 : :
3354 : 1120 : jsv.is_json = json_type == JSONOID;
3355 : :
3356 [ + + ]: 1120 : if (*isnull)
3357 : : {
3358 [ - + ]: 44 : if (jsv.is_json)
775 amitlan@postgresql.o 3359 :UBC 0 : jsv.val.json.str = NULL;
3360 : : else
775 amitlan@postgresql.o 3361 :CBC 44 : jsv.val.jsonb = NULL;
3362 : : }
3363 [ - + ]: 1076 : else if (jsv.is_json)
3364 : : {
775 amitlan@postgresql.o 3365 :UBC 0 : text *json = DatumGetTextPP(json_val);
3366 : :
3367 [ # # ]: 0 : jsv.val.json.str = VARDATA_ANY(json);
3368 [ # # # # : 0 : jsv.val.json.len = VARSIZE_ANY_EXHDR(json);
# # # # #
# ]
3369 : 0 : jsv.val.json.type = JSON_TOKEN_INVALID; /* not used in
3370 : : * populate_composite() */
3371 : : }
3372 : : else
3373 : : {
775 amitlan@postgresql.o 3374 :CBC 1076 : Jsonb *jsonb = DatumGetJsonbP(json_val);
3375 : :
3376 : 1076 : jsv.val.jsonb = &jbv;
3377 : :
676 3378 [ + + ]: 1076 : if (omit_quotes)
3379 : : {
3380 : 248 : char *str = JsonbUnquote(DatumGetJsonbP(json_val));
3381 : :
3382 : : /* fill the quote-stripped string */
3383 : 248 : jbv.type = jbvString;
3384 : 248 : jbv.val.string.len = strlen(str);
3385 : 248 : jbv.val.string.val = str;
3386 : : }
3387 : : else
3388 : : {
3389 : : /* fill binary jsonb value pointing to jb */
3390 : 828 : jbv.type = jbvBinary;
3391 : 828 : jbv.val.binary.data = &jsonb->root;
3392 : 828 : jbv.val.binary.len = VARSIZE(jsonb) - VARHDRSZ;
3393 : : }
3394 : : }
3395 : :
775 3396 [ + + ]: 1120 : if (*cache == NULL)
3397 : 468 : *cache = MemoryContextAllocZero(mcxt, sizeof(ColumnIOData));
3398 : :
3399 : 1120 : return populate_record_field(*cache, typid, typmod, NULL, mcxt,
3400 : : PointerGetDatum(NULL), &jsv, isnull,
3401 : : escontext, omit_quotes);
3402 : : }
3403 : :
3404 : : /* recursively populate a record field or an array element from a json/jsonb value */
3405 : : static Datum
3316 andrew@dunslane.net 3406 : 25132 : populate_record_field(ColumnIOData *col,
3407 : : Oid typid,
3408 : : int32 typmod,
3409 : : const char *colname,
3410 : : MemoryContext mcxt,
3411 : : Datum defaultval,
3412 : : JsValue *jsv,
3413 : : bool *isnull,
3414 : : Node *escontext,
3415 : : bool omit_scalar_quotes)
3416 : : {
3417 : : TypeCat typcat;
3418 : :
3419 : 25132 : check_stack_depth();
3420 : :
3421 : : /*
3422 : : * Prepare column metadata cache for the given type. Force lookup of the
3423 : : * scalar_io data so that the json string hack below will work.
3424 : : */
3425 [ + + - + ]: 25132 : if (col->typid != typid || col->typmod != typmod)
3113 tgl@sss.pgh.pa.us 3426 : 13280 : prepare_column_cache(col, typid, typmod, mcxt, true);
3427 : :
3316 andrew@dunslane.net 3428 [ + + + + : 25132 : *isnull = JsValueIsNull(jsv);
- + + + +
+ ]
3429 : :
3430 : 25132 : typcat = col->typcat;
3431 : :
3432 : : /* try to convert json string to a non-scalar type through input function */
3433 [ + + + + : 25132 : if (JsValueIsString(jsv) &&
+ + + + +
+ ]
3113 tgl@sss.pgh.pa.us 3434 [ + + ]: 2872 : (typcat == TYPECAT_ARRAY ||
3435 [ - + ]: 2848 : typcat == TYPECAT_COMPOSITE ||
3436 : : typcat == TYPECAT_COMPOSITE_DOMAIN))
3316 andrew@dunslane.net 3437 : 44 : typcat = TYPECAT_SCALAR;
3438 : :
3439 : : /* we must perform domain checks for NULLs, otherwise exit immediately */
3113 tgl@sss.pgh.pa.us 3440 [ + + + + ]: 25132 : if (*isnull &&
3441 [ + - ]: 14236 : typcat != TYPECAT_DOMAIN &&
3442 : : typcat != TYPECAT_COMPOSITE_DOMAIN)
3316 andrew@dunslane.net 3443 : 14236 : return (Datum) 0;
3444 : :
3445 [ + + + + : 10896 : switch (typcat)
- ]
3446 : : {
3447 : 6152 : case TYPECAT_SCALAR:
832 amitlan@postgresql.o 3448 : 6152 : return populate_scalar(&col->scalar_io, typid, typmod, jsv,
3449 : : isnull, escontext, omit_scalar_quotes);
3450 : :
3316 andrew@dunslane.net 3451 : 1440 : case TYPECAT_ARRAY:
832 amitlan@postgresql.o 3452 : 1440 : return populate_array(&col->io.array, colname, mcxt, jsv,
3453 : : isnull, escontext);
3454 : :
3316 andrew@dunslane.net 3455 : 1332 : case TYPECAT_COMPOSITE:
3456 : : case TYPECAT_COMPOSITE_DOMAIN:
3113 tgl@sss.pgh.pa.us 3457 [ + + ]: 1340 : return populate_composite(&col->io.composite, typid,
3458 : : colname, mcxt,
3316 andrew@dunslane.net 3459 : 1332 : DatumGetPointer(defaultval)
3460 : 8 : ? DatumGetHeapTupleHeader(defaultval)
3461 : : : NULL,
3462 : : jsv, isnull,
3463 : : escontext);
3464 : :
3465 : 1972 : case TYPECAT_DOMAIN:
3466 : 1972 : return populate_domain(&col->io.domain, typid, colname, mcxt,
3467 : : jsv, isnull, escontext, omit_scalar_quotes);
3468 : :
3316 andrew@dunslane.net 3469 :UBC 0 : default:
3470 [ # # ]: 0 : elog(ERROR, "unrecognized type category '%c'", typcat);
3471 : : return (Datum) 0;
3472 : : }
3473 : : }
3474 : :
3475 : : static RecordIOData *
3316 andrew@dunslane.net 3476 :CBC 1536 : allocate_record_info(MemoryContext mcxt, int ncolumns)
3477 : : {
3478 : : RecordIOData *data = (RecordIOData *)
1082 tgl@sss.pgh.pa.us 3479 : 1536 : MemoryContextAlloc(mcxt,
3480 : : offsetof(RecordIOData, columns) +
3481 : 1536 : ncolumns * sizeof(ColumnIOData));
3482 : :
3316 andrew@dunslane.net 3483 : 1536 : data->record_type = InvalidOid;
3484 : 1536 : data->record_typmod = 0;
3485 : 1536 : data->ncolumns = ncolumns;
3486 [ + - + - : 28212 : MemSet(data->columns, 0, sizeof(ColumnIOData) * ncolumns);
+ - + + +
+ ]
3487 : :
3488 : 1536 : return data;
3489 : : }
3490 : :
3491 : : static bool
3492 : 20240 : JsObjectGetField(JsObject *obj, char *field, JsValue *jsv)
3493 : : {
3494 : 20240 : jsv->is_json = obj->is_json;
3495 : :
3496 [ + + ]: 20240 : if (jsv->is_json)
3497 : : {
3498 : 10024 : JsonHashEntry *hashentry = hash_search(obj->val.json_hash, field,
3499 : : HASH_FIND, NULL);
3500 : :
3501 [ + + ]: 10024 : jsv->val.json.type = hashentry ? hashentry->type : JSON_TOKEN_NULL;
3502 [ + + ]: 10024 : jsv->val.json.str = jsv->val.json.type == JSON_TOKEN_NULL ? NULL :
3503 : : hashentry->val;
3504 [ + + ]: 10024 : jsv->val.json.len = jsv->val.json.str ? -1 : 0; /* null-terminated */
3505 : :
3506 : 10024 : return hashentry != NULL;
3507 : : }
3508 : : else
3509 : : {
3510 [ + - ]: 10216 : jsv->val.jsonb = !obj->val.jsonb_cont ? NULL :
2419 alvherre@alvh.no-ip. 3511 : 10216 : getKeyJsonValueFromContainer(obj->val.jsonb_cont, field, strlen(field),
3512 : : NULL);
3513 : :
3316 andrew@dunslane.net 3514 : 10216 : return jsv->val.jsonb != NULL;
3515 : : }
3516 : : }
3517 : :
3518 : : /* populate a record tuple from json/jsonb value */
3519 : : static HeapTupleHeader
3275 bruce@momjian.us 3520 : 2924 : populate_record(TupleDesc tupdesc,
3521 : : RecordIOData **record_p,
3522 : : HeapTupleHeader defaultval,
3523 : : MemoryContext mcxt,
3524 : : JsObject *obj,
3525 : : Node *escontext)
3526 : : {
3273 peter_e@gmx.net 3527 : 2924 : RecordIOData *record = *record_p;
3528 : : Datum *values;
3529 : : bool *nulls;
3530 : : HeapTuple res;
3275 bruce@momjian.us 3531 : 2924 : int ncolumns = tupdesc->natts;
3532 : : int i;
3533 : :
3534 : : /*
3535 : : * if the input json is empty, we can only skip the rest if we were passed
3536 : : * in a non-null record, since otherwise there may be issues with domain
3537 : : * nulls.
3538 : : */
3316 andrew@dunslane.net 3539 [ + + + + : 2924 : if (defaultval && JsObjectIsEmpty(obj))
+ - + + +
+ ]
3540 : 8 : return defaultval;
3541 : :
3542 : : /* (re)allocate metadata cache */
3543 [ + + ]: 2916 : if (record == NULL ||
3544 [ - + ]: 1380 : record->ncolumns != ncolumns)
3273 peter_e@gmx.net 3545 : 1536 : *record_p = record = allocate_record_info(mcxt, ncolumns);
3546 : :
3547 : : /* invalidate metadata cache if the record type has changed */
3316 andrew@dunslane.net 3548 [ + + ]: 2916 : if (record->record_type != tupdesc->tdtypeid ||
3549 [ - + ]: 1380 : record->record_typmod != tupdesc->tdtypmod)
3550 : : {
3551 [ + - + - : 29908 : MemSet(record, 0, offsetof(RecordIOData, columns) +
+ - + + +
+ ]
3552 : : ncolumns * sizeof(ColumnIOData));
3553 : 1536 : record->record_type = tupdesc->tdtypeid;
3554 : 1536 : record->record_typmod = tupdesc->tdtypmod;
3555 : 1536 : record->ncolumns = ncolumns;
3556 : : }
3557 : :
3558 : 2916 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
3559 : 2916 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
3560 : :
3561 [ + + ]: 2916 : if (defaultval)
3562 : : {
3563 : : HeapTupleData tuple;
3564 : :
3565 : : /* Build a temporary HeapTuple control structure */
3566 : 288 : tuple.t_len = HeapTupleHeaderGetDatumLength(defaultval);
3567 : 288 : ItemPointerSetInvalid(&(tuple.t_self));
3568 : 288 : tuple.t_tableOid = InvalidOid;
3569 : 288 : tuple.t_data = defaultval;
3570 : :
3571 : : /* Break down the tuple into fields */
3572 : 288 : heap_deform_tuple(&tuple, tupdesc, values, nulls);
3573 : : }
3574 : : else
3575 : : {
3576 [ + + ]: 23452 : for (i = 0; i < ncolumns; ++i)
3577 : : {
3578 : 20824 : values[i] = (Datum) 0;
3579 : 20824 : nulls[i] = true;
3580 : : }
3581 : : }
3582 : :
3583 [ + + ]: 22908 : for (i = 0; i < ncolumns; ++i)
3584 : : {
3180 andres@anarazel.de 3585 : 20240 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
3275 bruce@momjian.us 3586 : 20240 : char *colname = NameStr(att->attname);
3587 : 20240 : JsValue field = {0};
3588 : : bool found;
3589 : :
3590 : : /* Ignore dropped columns in datatype */
3316 andrew@dunslane.net 3591 [ - + ]: 20240 : if (att->attisdropped)
3592 : : {
3316 andrew@dunslane.net 3593 :UBC 0 : nulls[i] = true;
3316 andrew@dunslane.net 3594 :CBC 504 : continue;
3595 : : }
3596 : :
3597 : 20240 : found = JsObjectGetField(obj, colname, &field);
3598 : :
3599 : : /*
3600 : : * we can't just skip here if the key wasn't found since we might have
3601 : : * a domain to deal with. If we were passed in a non-null record
3602 : : * datum, we assume that the existing values are valid (if they're
3603 : : * not, then it's not our fault), but if we were passed in a null,
3604 : : * then every field which we don't populate needs to be run through
3605 : : * the input function just in case it's a domain type.
3606 : : */
3607 [ + + + + ]: 20240 : if (defaultval && !found)
3608 : 504 : continue;
3609 : :
3610 : 19736 : values[i] = populate_record_field(&record->columns[i],
3611 : : att->atttypid,
3612 : : att->atttypmod,
3613 : : colname,
3614 : : mcxt,
3615 [ + + ]: 19736 : nulls[i] ? (Datum) 0 : values[i],
3616 : : &field,
3617 : : &nulls[i],
3618 : : escontext,
3619 : : false);
3620 : : }
3621 : :
3622 : 2668 : res = heap_form_tuple(tupdesc, values, nulls);
3623 : :
3624 : 2668 : pfree(values);
3625 : 2668 : pfree(nulls);
3626 : :
3627 : 2668 : return res->t_data;
3628 : : }
3629 : :
3630 : : /*
3631 : : * Setup for json{b}_populate_record{set}: result type will be same as first
3632 : : * argument's type --- unless first argument is "null::record", which we can't
3633 : : * extract type info from; we handle that later.
3634 : : */
3635 : : static void
2451 tgl@sss.pgh.pa.us 3636 : 1100 : get_record_type_from_argument(FunctionCallInfo fcinfo,
3637 : : const char *funcname,
3638 : : PopulateRecordCache *cache)
3639 : : {
3640 : 1100 : cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
3641 : 1100 : prepare_column_cache(&cache->c,
3642 : : cache->argtype, -1,
3643 : : cache->fn_mcxt, false);
3644 [ + + ]: 1100 : if (cache->c.typcat != TYPECAT_COMPOSITE &&
3645 [ - + ]: 48 : cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN)
2451 tgl@sss.pgh.pa.us 3646 [ # # ]:UBC 0 : ereport(ERROR,
3647 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3648 : : /* translator: %s is a function name, eg json_to_record */
3649 : : errmsg("first argument of %s must be a row type",
3650 : : funcname)));
2451 tgl@sss.pgh.pa.us 3651 :CBC 1100 : }
3652 : :
3653 : : /*
3654 : : * Setup for json{b}_to_record{set}: result type is specified by calling
3655 : : * query. We'll also use this code for json{b}_populate_record{set},
3656 : : * if we discover that the first argument is a null of type RECORD.
3657 : : *
3658 : : * Here it is syntactically impossible to specify the target type
3659 : : * as domain-over-composite.
3660 : : */
3661 : : static void
3662 : 208 : get_record_type_from_query(FunctionCallInfo fcinfo,
3663 : : const char *funcname,
3664 : : PopulateRecordCache *cache)
3665 : : {
3666 : : TupleDesc tupdesc;
3667 : : MemoryContext old_cxt;
3668 : :
3669 [ + + ]: 208 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3670 [ + - ]: 24 : ereport(ERROR,
3671 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3672 : : /* translator: %s is a function name, eg json_to_record */
3673 : : errmsg("could not determine row type for result of %s",
3674 : : funcname),
3675 : : errhint("Provide a non-null record argument, "
3676 : : "or call the function in the FROM clause "
3677 : : "using a column definition list.")));
3678 : :
3679 [ - + ]: 184 : Assert(tupdesc);
3680 : 184 : cache->argtype = tupdesc->tdtypeid;
3681 : :
3682 : : /* If we go through this more than once, avoid memory leak */
3683 [ - + ]: 184 : if (cache->c.io.composite.tupdesc)
2451 tgl@sss.pgh.pa.us 3684 :UBC 0 : FreeTupleDesc(cache->c.io.composite.tupdesc);
3685 : :
3686 : : /* Save identified tupdesc */
2451 tgl@sss.pgh.pa.us 3687 :CBC 184 : old_cxt = MemoryContextSwitchTo(cache->fn_mcxt);
3688 : 184 : cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc);
3689 : 184 : cache->c.io.composite.base_typid = tupdesc->tdtypeid;
3690 : 184 : cache->c.io.composite.base_typmod = tupdesc->tdtypmod;
3691 : 184 : MemoryContextSwitchTo(old_cxt);
3692 : 184 : }
3693 : :
3694 : : /*
3695 : : * common worker for json{b}_populate_record() and json{b}_to_record()
3696 : : * is_json and have_record_arg identify the specific function
3697 : : */
3698 : : static Datum
3316 andrew@dunslane.net 3699 : 1312 : populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
3700 : : bool is_json, bool have_record_arg,
3701 : : Node *escontext)
3702 : : {
3703 : 1312 : int json_arg_num = have_record_arg ? 1 : 0;
3275 bruce@momjian.us 3704 : 1312 : JsValue jsv = {0};
3705 : : HeapTupleHeader rec;
3706 : : Datum rettuple;
3707 : : bool isnull;
3708 : : JsonbValue jbv;
3316 andrew@dunslane.net 3709 : 1312 : MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt;
3710 : 1312 : PopulateRecordCache *cache = fcinfo->flinfo->fn_extra;
3711 : :
3712 : : /*
3713 : : * If first time through, identify input/result record type. Note that
3714 : : * this stanza looks only at fcinfo context, which can't change during the
3715 : : * query; so we may not be able to fully resolve a RECORD input type yet.
3716 : : */
3717 [ + + ]: 1312 : if (!cache)
3718 : : {
3719 : 1040 : fcinfo->flinfo->fn_extra = cache =
3275 bruce@momjian.us 3720 : 1040 : MemoryContextAllocZero(fnmcxt, sizeof(*cache));
2451 tgl@sss.pgh.pa.us 3721 : 1040 : cache->fn_mcxt = fnmcxt;
3722 : :
3113 3723 [ + + ]: 1040 : if (have_record_arg)
2451 3724 : 904 : get_record_type_from_argument(fcinfo, funcname, cache);
3725 : : else
3726 : 136 : get_record_type_from_query(fcinfo, funcname, cache);
3727 : : }
3728 : :
3729 : : /* Collect record arg if we have one */
3730 [ + + ]: 1312 : if (!have_record_arg)
3731 : 136 : rec = NULL; /* it's json{b}_to_record() */
3732 [ + + ]: 1176 : else if (!PG_ARGISNULL(0))
3733 : : {
3113 3734 : 72 : rec = PG_GETARG_HEAPTUPLEHEADER(0);
3735 : :
3736 : : /*
3737 : : * When declared arg type is RECORD, identify actual record type from
3738 : : * the tuple itself.
3739 : : */
3740 [ + + ]: 72 : if (cache->argtype == RECORDOID)
3741 : : {
3742 : 8 : cache->c.io.composite.base_typid = HeapTupleHeaderGetTypeId(rec);
3743 : 8 : cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec);
3744 : : }
3745 : : }
3746 : : else
3747 : : {
3748 : 1104 : rec = NULL;
3749 : :
3750 : : /*
3751 : : * When declared arg type is RECORD, identify actual record type from
3752 : : * calling query, or fail if we can't.
3753 : : */
2451 3754 [ + + ]: 1104 : if (cache->argtype == RECORDOID)
3755 : : {
3756 : 16 : get_record_type_from_query(fcinfo, funcname, cache);
3757 : : /* This can't change argtype, which is important for next time */
3758 [ - + ]: 8 : Assert(cache->argtype == RECORDOID);
3759 : : }
3760 : : }
3761 : :
3762 : : /* If no JSON argument, just return the record (if any) unchanged */
3113 3763 [ - + ]: 1304 : if (PG_ARGISNULL(json_arg_num))
3764 : : {
3113 tgl@sss.pgh.pa.us 3765 [ # # ]:UBC 0 : if (rec)
3766 : 0 : PG_RETURN_POINTER(rec);
3767 : : else
3768 : 0 : PG_RETURN_NULL();
3769 : : }
3770 : :
2853 tgl@sss.pgh.pa.us 3771 :CBC 1304 : jsv.is_json = is_json;
3772 : :
3773 [ + + ]: 1304 : if (is_json)
3774 : : {
3316 andrew@dunslane.net 3775 : 612 : text *json = PG_GETARG_TEXT_PP(json_arg_num);
3776 : :
3777 [ - + ]: 612 : jsv.val.json.str = VARDATA_ANY(json);
3778 [ - + - - : 612 : jsv.val.json.len = VARSIZE_ANY_EXHDR(json);
- - - - -
+ ]
3275 bruce@momjian.us 3779 : 612 : jsv.val.json.type = JSON_TOKEN_INVALID; /* not used in
3780 : : * populate_composite() */
3781 : : }
3782 : : else
3783 : : {
3151 tgl@sss.pgh.pa.us 3784 : 692 : Jsonb *jb = PG_GETARG_JSONB_P(json_arg_num);
3785 : :
3316 andrew@dunslane.net 3786 : 692 : jsv.val.jsonb = &jbv;
3787 : :
3788 : : /* fill binary jsonb value pointing to jb */
3789 : 692 : jbv.type = jbvBinary;
3790 : 692 : jbv.val.binary.data = &jb->root;
3791 : 692 : jbv.val.binary.len = VARSIZE(jb) - VARHDRSZ;
3792 : : }
3793 : :
832 amitlan@postgresql.o 3794 : 1304 : isnull = false;
3113 tgl@sss.pgh.pa.us 3795 : 1304 : rettuple = populate_composite(&cache->c.io.composite, cache->argtype,
3796 : : NULL, fnmcxt, rec, &jsv, &isnull,
3797 : : escontext);
832 amitlan@postgresql.o 3798 [ + + + - : 1060 : Assert(!isnull || SOFT_ERROR_OCCURRED(escontext));
+ - - + ]
3799 : :
3316 andrew@dunslane.net 3800 : 1060 : PG_RETURN_DATUM(rettuple);
3801 : : }
3802 : :
3803 : : /*
3804 : : * get_json_object_as_hash
3805 : : *
3806 : : * Decomposes a json object into a hash table.
3807 : : *
3808 : : * Returns the hash table if the json is parsed successfully, NULL otherwise.
3809 : : */
3810 : : static HTAB *
683 peter@eisentraut.org 3811 : 1256 : get_json_object_as_hash(const char *json, int len, const char *funcname,
3812 : : Node *escontext)
3813 : : {
3814 : : HASHCTL ctl;
3815 : : HTAB *tab;
3816 : : JHashState *state;
3817 : : JsonSemAction *sem;
3818 : :
3316 andrew@dunslane.net 3819 : 1256 : ctl.keysize = NAMEDATALEN;
3820 : 1256 : ctl.entrysize = sizeof(JsonHashEntry);
3821 : 1256 : ctl.hcxt = CurrentMemoryContext;
3822 : 1256 : tab = hash_create("json object hashtable",
3823 : : 100,
3824 : : &ctl,
3825 : : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
3826 : :
146 michael@paquier.xyz 3827 :GNC 1256 : state = palloc0_object(JHashState);
3828 : 1256 : sem = palloc0_object(JsonSemAction);
3829 : :
3316 andrew@dunslane.net 3830 :CBC 1256 : state->function_name = funcname;
3831 : 1256 : state->hash = tab;
943 alvherre@alvh.no-ip. 3832 : 1256 : state->lex = makeJsonLexContextCstringLen(NULL, json, len,
3833 : : GetDatabaseEncoding(), true);
3834 : :
523 peter@eisentraut.org 3835 : 1256 : sem->semstate = state;
3316 andrew@dunslane.net 3836 : 1256 : sem->array_start = hash_array_start;
3837 : 1256 : sem->scalar = hash_scalar;
3838 : 1256 : sem->object_field_start = hash_object_field_start;
3839 : 1256 : sem->object_field_end = hash_object_field_end;
3840 : :
832 amitlan@postgresql.o 3841 [ - + ]: 1256 : if (!pg_parse_json_or_errsave(state->lex, sem, escontext))
3842 : : {
832 amitlan@postgresql.o 3843 :UBC 0 : hash_destroy(state->hash);
3844 : 0 : tab = NULL;
3845 : : }
3846 : :
943 alvherre@alvh.no-ip. 3847 :CBC 1244 : freeJsonLexContext(state->lex);
3848 : :
3316 andrew@dunslane.net 3849 : 1244 : return tab;
3850 : : }
3851 : :
3852 : : static JsonParseErrorType
3853 : 4104 : hash_object_field_start(void *state, char *fname, bool isnull)
3854 : : {
3855 : 4104 : JHashState *_state = (JHashState *) state;
3856 : :
3857 [ + + ]: 4104 : if (_state->lex->lex_level > 1)
1241 tgl@sss.pgh.pa.us 3858 : 1544 : return JSON_SUCCESS;
3859 : :
3860 : : /* remember token type */
3316 andrew@dunslane.net 3861 : 2560 : _state->saved_token_type = _state->lex->token_type;
3862 : :
3863 [ + + ]: 2560 : if (_state->lex->token_type == JSON_TOKEN_ARRAY_START ||
3864 [ + + ]: 1960 : _state->lex->token_type == JSON_TOKEN_OBJECT_START)
3865 : : {
3866 : : /* remember start position of the whole text of the subobject */
3867 : 836 : _state->save_json_start = _state->lex->token_start;
3868 : : }
3869 : : else
3870 : : {
3871 : : /* must be a scalar */
4785 3872 : 1724 : _state->save_json_start = NULL;
3873 : : }
3874 : :
1241 tgl@sss.pgh.pa.us 3875 : 2560 : return JSON_SUCCESS;
3876 : : }
3877 : :
3878 : : static JsonParseErrorType
4785 andrew@dunslane.net 3879 : 4104 : hash_object_field_end(void *state, char *fname, bool isnull)
3880 : : {
4672 peter_e@gmx.net 3881 : 4104 : JHashState *_state = (JHashState *) state;
3882 : : JsonHashEntry *hashentry;
3883 : : bool found;
3884 : :
3885 : : /*
3886 : : * Ignore nested fields.
3887 : : */
3716 tgl@sss.pgh.pa.us 3888 [ + + ]: 4104 : if (_state->lex->lex_level > 1)
1241 3889 : 1544 : return JSON_SUCCESS;
3890 : :
3891 : : /*
3892 : : * Ignore field names >= NAMEDATALEN - they can't match a record field.
3893 : : * (Note: without this test, the hash code would truncate the string at
3894 : : * NAMEDATALEN-1, and could then match against a similarly-truncated
3895 : : * record field name. That would be a reasonable behavior, but this code
3896 : : * has previously insisted on exact equality, so we keep this behavior.)
3897 : : */
4332 3898 [ - + ]: 2560 : if (strlen(fname) >= NAMEDATALEN)
1241 tgl@sss.pgh.pa.us 3899 :UBC 0 : return JSON_SUCCESS;
3900 : :
4332 tgl@sss.pgh.pa.us 3901 :CBC 2560 : hashentry = hash_search(_state->hash, fname, HASH_ENTER, &found);
3902 : :
3903 : : /*
3904 : : * found being true indicates a duplicate. We don't do anything about
3905 : : * that, a later field with the same name overrides the earlier field.
3906 : : */
3907 : :
3316 andrew@dunslane.net 3908 : 2560 : hashentry->type = _state->saved_token_type;
3909 [ - + ]: 2560 : Assert(isnull == (hashentry->type == JSON_TOKEN_NULL));
3910 : :
4785 3911 [ + + ]: 2560 : if (_state->save_json_start != NULL)
3912 : : {
3913 : 836 : int len = _state->lex->prev_token_terminator - _state->save_json_start;
3914 : 836 : char *val = palloc((len + 1) * sizeof(char));
3915 : :
3916 : 836 : memcpy(val, _state->save_json_start, len);
3917 : 836 : val[len] = '\0';
3918 : 836 : hashentry->val = val;
3919 : : }
3920 : : else
3921 : : {
3922 : : /* must have had a scalar instead */
3923 : 1724 : hashentry->val = _state->saved_scalar;
3924 : : }
3925 : :
1241 tgl@sss.pgh.pa.us 3926 : 2560 : return JSON_SUCCESS;
3927 : : }
3928 : :
3929 : : static JsonParseErrorType
4785 andrew@dunslane.net 3930 : 848 : hash_array_start(void *state)
3931 : : {
4672 peter_e@gmx.net 3932 : 848 : JHashState *_state = (JHashState *) state;
3933 : :
4785 andrew@dunslane.net 3934 [ + + ]: 848 : if (_state->lex->lex_level == 0)
3935 [ + - ]: 4 : ereport(ERROR,
3936 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3937 : : errmsg("cannot call %s on an array", _state->function_name)));
3938 : :
1241 tgl@sss.pgh.pa.us 3939 : 844 : return JSON_SUCCESS;
3940 : : }
3941 : :
3942 : : static JsonParseErrorType
4785 andrew@dunslane.net 3943 : 4920 : hash_scalar(void *state, char *token, JsonTokenType tokentype)
3944 : : {
4672 peter_e@gmx.net 3945 : 4920 : JHashState *_state = (JHashState *) state;
3946 : :
4785 andrew@dunslane.net 3947 [ + + ]: 4920 : if (_state->lex->lex_level == 0)
3948 [ + - ]: 8 : ereport(ERROR,
3949 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3950 : : errmsg("cannot call %s on a scalar", _state->function_name)));
3951 : :
3952 [ + + ]: 4912 : if (_state->lex->lex_level == 1)
3953 : : {
3954 : 1724 : _state->saved_scalar = token;
3955 : : /* saved_token_type must already be set in hash_object_field_start() */
3316 3956 [ - + ]: 1724 : Assert(_state->saved_token_type == tokentype);
3957 : : }
3958 : :
1241 tgl@sss.pgh.pa.us 3959 : 4912 : return JSON_SUCCESS;
3960 : : }
3961 : :
3962 : :
3963 : : /*
3964 : : * SQL function json_populate_recordset
3965 : : *
3966 : : * set fields in a set of records from the argument json,
3967 : : * which must be an array of objects.
3968 : : *
3969 : : * similar to json_populate_record, but the tuple-building code
3970 : : * is pushed down into the semantic action handlers so it's done
3971 : : * per object in the array.
3972 : : */
3973 : : Datum
4426 andrew@dunslane.net 3974 : 100 : jsonb_populate_recordset(PG_FUNCTION_ARGS)
3975 : : {
2853 tgl@sss.pgh.pa.us 3976 : 100 : return populate_recordset_worker(fcinfo, "jsonb_populate_recordset",
3977 : : false, true);
3978 : : }
3979 : :
3980 : : Datum
4423 andrew@dunslane.net 3981 : 12 : jsonb_to_recordset(PG_FUNCTION_ARGS)
3982 : : {
2853 tgl@sss.pgh.pa.us 3983 : 12 : return populate_recordset_worker(fcinfo, "jsonb_to_recordset",
3984 : : false, false);
3985 : : }
3986 : :
3987 : : Datum
4423 andrew@dunslane.net 3988 : 104 : json_populate_recordset(PG_FUNCTION_ARGS)
3989 : : {
2853 tgl@sss.pgh.pa.us 3990 : 104 : return populate_recordset_worker(fcinfo, "json_populate_recordset",
3991 : : true, true);
3992 : : }
3993 : :
3994 : : Datum
4423 andrew@dunslane.net 3995 : 12 : json_to_recordset(PG_FUNCTION_ARGS)
3996 : : {
2853 tgl@sss.pgh.pa.us 3997 : 12 : return populate_recordset_worker(fcinfo, "json_to_recordset",
3998 : : true, false);
3999 : : }
4000 : :
4001 : : static void
3316 andrew@dunslane.net 4002 : 320 : populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
4003 : : {
2451 tgl@sss.pgh.pa.us 4004 : 320 : PopulateRecordCache *cache = state->cache;
4005 : : HeapTupleHeader tuphead;
4006 : : HeapTupleData tuple;
4007 : :
4008 : : /* acquire/update cached tuple descriptor */
3113 4009 : 320 : update_cached_tupdesc(&cache->c.io.composite, cache->fn_mcxt);
4010 : :
4011 : : /* replace record fields from json */
4012 : 320 : tuphead = populate_record(cache->c.io.composite.tupdesc,
4013 : : &cache->c.io.composite.record_io,
4014 : : state->rec,
4015 : : cache->fn_mcxt,
4016 : : obj,
4017 : : NULL);
4018 : :
4019 : : /* if it's domain over composite, check domain constraints */
4020 [ + + ]: 312 : if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
832 amitlan@postgresql.o 4021 : 32 : (void) domain_check_safe(HeapTupleHeaderGetDatum(tuphead), false,
4022 : : cache->argtype,
4023 : : &cache->c.io.composite.domain_info,
4024 : : cache->fn_mcxt,
4025 : : NULL);
4026 : :
4027 : : /* ok, save into tuplestore */
3316 andrew@dunslane.net 4028 : 304 : tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead);
4029 : 304 : ItemPointerSetInvalid(&(tuple.t_self));
4030 : 304 : tuple.t_tableOid = InvalidOid;
4031 : 304 : tuple.t_data = tuphead;
4032 : :
4033 : 304 : tuplestore_puttuple(state->tuple_store, &tuple);
4426 4034 : 304 : }
4035 : :
4036 : : /*
4037 : : * common worker for json{b}_populate_recordset() and json{b}_to_recordset()
4038 : : * is_json and have_record_arg identify the specific function
4039 : : */
4040 : : static Datum
4332 tgl@sss.pgh.pa.us 4041 : 228 : populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
4042 : : bool is_json, bool have_record_arg)
4043 : : {
4382 bruce@momjian.us 4044 : 228 : int json_arg_num = have_record_arg ? 1 : 0;
4045 : : ReturnSetInfo *rsi;
4046 : : MemoryContext old_cxt;
4047 : : HeapTupleHeader rec;
2451 tgl@sss.pgh.pa.us 4048 : 228 : PopulateRecordCache *cache = fcinfo->flinfo->fn_extra;
4049 : : PopulateRecordsetState *state;
4050 : :
4785 andrew@dunslane.net 4051 : 228 : rsi = (ReturnSetInfo *) fcinfo->resultinfo;
4052 : :
1531 michael@paquier.xyz 4053 [ + - - + ]: 228 : if (!rsi || !IsA(rsi, ReturnSetInfo))
1531 michael@paquier.xyz 4054 [ # # ]:UBC 0 : ereport(ERROR,
4055 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4056 : : errmsg("set-valued function called in context that cannot accept a set")));
4057 : :
1531 michael@paquier.xyz 4058 [ - + ]:CBC 228 : if (!(rsi->allowedModes & SFRM_Materialize))
4785 andrew@dunslane.net 4059 [ # # ]:UBC 0 : ereport(ERROR,
4060 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4061 : : errmsg("materialize mode required, but it is not allowed in this context")));
4062 : :
4785 andrew@dunslane.net 4063 :CBC 228 : rsi->returnMode = SFRM_Materialize;
4064 : :
4065 : : /*
4066 : : * If first time through, identify input/result record type. Note that
4067 : : * this stanza looks only at fcinfo context, which can't change during the
4068 : : * query; so we may not be able to fully resolve a RECORD input type yet.
4069 : : */
3113 tgl@sss.pgh.pa.us 4070 [ + + ]: 228 : if (!cache)
4071 : : {
4072 : 220 : fcinfo->flinfo->fn_extra = cache =
4073 : 220 : MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(*cache));
4074 : 220 : cache->fn_mcxt = fcinfo->flinfo->fn_mcxt;
4075 : :
4076 [ + + ]: 220 : if (have_record_arg)
2451 4077 : 196 : get_record_type_from_argument(fcinfo, funcname, cache);
4078 : : else
4079 : 24 : get_record_type_from_query(fcinfo, funcname, cache);
4080 : : }
4081 : :
4082 : : /* Collect record arg if we have one */
4083 [ + + ]: 228 : if (!have_record_arg)
4084 : 24 : rec = NULL; /* it's json{b}_to_recordset() */
4085 [ + + ]: 204 : else if (!PG_ARGISNULL(0))
4086 : : {
3113 4087 : 128 : rec = PG_GETARG_HEAPTUPLEHEADER(0);
4088 : :
4089 : : /*
4090 : : * When declared arg type is RECORD, identify actual record type from
4091 : : * the tuple itself.
4092 : : */
4093 [ + + ]: 128 : if (cache->argtype == RECORDOID)
4094 : : {
4095 : 64 : cache->c.io.composite.base_typid = HeapTupleHeaderGetTypeId(rec);
4096 : 64 : cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec);
4097 : : }
4098 : : }
4099 : : else
4100 : : {
4101 : 76 : rec = NULL;
4102 : :
4103 : : /*
4104 : : * When declared arg type is RECORD, identify actual record type from
4105 : : * calling query, or fail if we can't.
4106 : : */
2451 4107 [ + + ]: 76 : if (cache->argtype == RECORDOID)
4108 : : {
4109 : 32 : get_record_type_from_query(fcinfo, funcname, cache);
4110 : : /* This can't change argtype, which is important for next time */
4111 [ - + ]: 16 : Assert(cache->argtype == RECORDOID);
4112 : : }
4113 : : }
4114 : :
4115 : : /* if the json is null send back an empty set */
4424 andrew@dunslane.net 4116 [ - + ]: 212 : if (PG_ARGISNULL(json_arg_num))
4424 andrew@dunslane.net 4117 :UBC 0 : PG_RETURN_NULL();
4118 : :
4119 : : /*
4120 : : * Forcibly update the cached tupdesc, to ensure we have the right tupdesc
4121 : : * to return even if the JSON contains no rows.
4122 : : */
2721 tgl@sss.pgh.pa.us 4123 :CBC 212 : update_cached_tupdesc(&cache->c.io.composite, cache->fn_mcxt);
4124 : :
146 michael@paquier.xyz 4125 :GNC 212 : state = palloc0_object(PopulateRecordsetState);
4126 : :
4127 : : /* make tuplestore in a sufficiently long-lived memory context */
4426 andrew@dunslane.net 4128 :CBC 212 : old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
4129 : 212 : state->tuple_store = tuplestore_begin_heap(rsi->allowedModes &
4130 : : SFRM_Materialize_Random,
4131 : : false, work_mem);
4132 : 212 : MemoryContextSwitchTo(old_cxt);
4133 : :
4332 tgl@sss.pgh.pa.us 4134 : 212 : state->function_name = funcname;
3113 4135 : 212 : state->cache = cache;
4785 andrew@dunslane.net 4136 : 212 : state->rec = rec;
4137 : :
2853 tgl@sss.pgh.pa.us 4138 [ + + ]: 212 : if (is_json)
4139 : : {
3341 noah@leadboat.com 4140 : 108 : text *json = PG_GETARG_TEXT_PP(json_arg_num);
4141 : : JsonLexContext lex;
4142 : : JsonSemAction *sem;
4143 : :
146 michael@paquier.xyz 4144 :GNC 108 : sem = palloc0_object(JsonSemAction);
4145 : :
943 alvherre@alvh.no-ip. 4146 :CBC 108 : makeJsonLexContext(&lex, json, true);
4147 : :
523 peter@eisentraut.org 4148 : 108 : sem->semstate = state;
4426 andrew@dunslane.net 4149 : 108 : sem->array_start = populate_recordset_array_start;
4150 : 108 : sem->array_element_start = populate_recordset_array_element_start;
4151 : 108 : sem->scalar = populate_recordset_scalar;
4152 : 108 : sem->object_field_start = populate_recordset_object_field_start;
4153 : 108 : sem->object_field_end = populate_recordset_object_field_end;
4154 : 108 : sem->object_start = populate_recordset_object_start;
4155 : 108 : sem->object_end = populate_recordset_object_end;
4156 : :
943 alvherre@alvh.no-ip. 4157 : 108 : state->lex = &lex;
4158 : :
4159 : 108 : pg_parse_json_or_ereport(&lex, sem);
4160 : :
4161 : 100 : freeJsonLexContext(&lex);
4162 : 100 : state->lex = NULL;
4163 : : }
4164 : : else
4165 : : {
3151 tgl@sss.pgh.pa.us 4166 : 104 : Jsonb *jb = PG_GETARG_JSONB_P(json_arg_num);
4167 : : JsonbIterator *it;
4168 : : JsonbValue v;
4426 andrew@dunslane.net 4169 : 104 : bool skipNested = false;
4170 : : JsonbIteratorToken r;
4171 : :
4172 [ + - - + ]: 104 : if (JB_ROOT_IS_SCALAR(jb) || !JB_ROOT_IS_ARRAY(jb))
4426 andrew@dunslane.net 4173 [ # # ]:UBC 0 : ereport(ERROR,
4174 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4175 : : errmsg("cannot call %s on a non-array",
4176 : : funcname)));
4177 : :
4381 heikki.linnakangas@i 4178 :CBC 104 : it = JsonbIteratorInit(&jb->root);
4179 : :
4426 andrew@dunslane.net 4180 [ + + ]: 452 : while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
4181 : : {
4182 : 356 : skipNested = true;
4183 : :
4184 [ + + ]: 356 : if (r == WJB_ELEM)
4185 : : {
4186 : : JsObject obj;
4187 : :
3316 4188 [ + - ]: 156 : if (v.type != jbvBinary ||
4189 [ - + ]: 156 : !JsonContainerIsObject(v.val.binary.data))
4426 andrew@dunslane.net 4190 [ # # ]:UBC 0 : ereport(ERROR,
4191 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4192 : : errmsg("argument of %s must be an array of objects",
4193 : : funcname)));
4194 : :
3316 andrew@dunslane.net 4195 :CBC 156 : obj.is_json = false;
4196 : 156 : obj.val.jsonb_cont = v.val.binary.data;
4197 : :
4198 : 156 : populate_recordset_record(state, &obj);
4199 : : }
4200 : : }
4201 : : }
4202 : :
4203 : : /*
4204 : : * Note: we must copy the cached tupdesc because the executor will free
4205 : : * the passed-back setDesc, but we want to hang onto the cache in case
4206 : : * we're called again in the same query.
4207 : : */
4785 4208 : 196 : rsi->setResult = state->tuple_store;
2853 tgl@sss.pgh.pa.us 4209 : 196 : rsi->setDesc = CreateTupleDescCopy(cache->c.io.composite.tupdesc);
4210 : :
4785 andrew@dunslane.net 4211 : 196 : PG_RETURN_NULL();
4212 : : }
4213 : :
4214 : : static JsonParseErrorType
4215 : 188 : populate_recordset_object_start(void *state)
4216 : : {
4672 peter_e@gmx.net 4217 : 188 : PopulateRecordsetState *_state = (PopulateRecordsetState *) state;
4785 andrew@dunslane.net 4218 : 188 : int lex_level = _state->lex->lex_level;
4219 : : HASHCTL ctl;
4220 : :
4221 : : /* Reject object at top level: we must have an array at level 0 */
4222 [ - + ]: 188 : if (lex_level == 0)
4785 andrew@dunslane.net 4223 [ # # ]:UBC 0 : ereport(ERROR,
4224 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4225 : : errmsg("cannot call %s on an object",
4226 : : _state->function_name)));
4227 : :
4228 : : /* Nested objects require no special processing */
4333 tgl@sss.pgh.pa.us 4229 [ + + ]:CBC 188 : if (lex_level > 1)
1241 4230 : 24 : return JSON_SUCCESS;
4231 : :
4232 : : /* Object at level 1: set up a new hash table for this object */
4785 andrew@dunslane.net 4233 : 164 : ctl.keysize = NAMEDATALEN;
4672 peter_e@gmx.net 4234 : 164 : ctl.entrysize = sizeof(JsonHashEntry);
4785 andrew@dunslane.net 4235 : 164 : ctl.hcxt = CurrentMemoryContext;
4236 : 164 : _state->json_hash = hash_create("json object hashtable",
4237 : : 100,
4238 : : &ctl,
4239 : : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
4240 : :
1241 tgl@sss.pgh.pa.us 4241 : 164 : return JSON_SUCCESS;
4242 : : }
4243 : :
4244 : : static JsonParseErrorType
4785 andrew@dunslane.net 4245 : 188 : populate_recordset_object_end(void *state)
4246 : : {
4672 peter_e@gmx.net 4247 : 188 : PopulateRecordsetState *_state = (PopulateRecordsetState *) state;
4248 : : JsObject obj;
4249 : :
4250 : : /* Nested objects require no special processing */
4785 andrew@dunslane.net 4251 [ + + ]: 188 : if (_state->lex->lex_level > 1)
1241 tgl@sss.pgh.pa.us 4252 : 24 : return JSON_SUCCESS;
4253 : :
3316 andrew@dunslane.net 4254 : 164 : obj.is_json = true;
4255 : 164 : obj.val.json_hash = _state->json_hash;
4256 : :
4257 : : /* Otherwise, construct and return a tuple based on this level-1 object */
4258 : 164 : populate_recordset_record(_state, &obj);
4259 : :
4260 : : /* Done with hash for this object */
4261 : 156 : hash_destroy(_state->json_hash);
4333 tgl@sss.pgh.pa.us 4262 : 156 : _state->json_hash = NULL;
4263 : :
1241 4264 : 156 : return JSON_SUCCESS;
4265 : : }
4266 : :
4267 : : static JsonParseErrorType
4785 andrew@dunslane.net 4268 : 200 : populate_recordset_array_element_start(void *state, bool isnull)
4269 : : {
4672 peter_e@gmx.net 4270 : 200 : PopulateRecordsetState *_state = (PopulateRecordsetState *) state;
4271 : :
4785 andrew@dunslane.net 4272 [ + + ]: 200 : if (_state->lex->lex_level == 1 &&
4273 [ - + ]: 164 : _state->lex->token_type != JSON_TOKEN_OBJECT_START)
4785 andrew@dunslane.net 4274 [ # # ]:UBC 0 : ereport(ERROR,
4275 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4276 : : errmsg("argument of %s must be an array of objects",
4277 : : _state->function_name)));
4278 : :
1241 tgl@sss.pgh.pa.us 4279 :CBC 200 : return JSON_SUCCESS;
4280 : : }
4281 : :
4282 : : static JsonParseErrorType
4785 andrew@dunslane.net 4283 : 120 : populate_recordset_array_start(void *state)
4284 : : {
4285 : : /* nothing to do */
1241 tgl@sss.pgh.pa.us 4286 : 120 : return JSON_SUCCESS;
4287 : : }
4288 : :
4289 : : static JsonParseErrorType
4785 andrew@dunslane.net 4290 : 344 : populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype)
4291 : : {
4672 peter_e@gmx.net 4292 : 344 : PopulateRecordsetState *_state = (PopulateRecordsetState *) state;
4293 : :
4785 andrew@dunslane.net 4294 [ - + ]: 344 : if (_state->lex->lex_level == 0)
4785 andrew@dunslane.net 4295 [ # # ]:UBC 0 : ereport(ERROR,
4296 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4297 : : errmsg("cannot call %s on a scalar",
4298 : : _state->function_name)));
4299 : :
4785 andrew@dunslane.net 4300 [ + + ]:CBC 344 : if (_state->lex->lex_level == 2)
4301 : 280 : _state->saved_scalar = token;
4302 : :
1241 tgl@sss.pgh.pa.us 4303 : 344 : return JSON_SUCCESS;
4304 : : }
4305 : :
4306 : : static JsonParseErrorType
4785 andrew@dunslane.net 4307 : 344 : populate_recordset_object_field_start(void *state, char *fname, bool isnull)
4308 : : {
4672 peter_e@gmx.net 4309 : 344 : PopulateRecordsetState *_state = (PopulateRecordsetState *) state;
4310 : :
4785 andrew@dunslane.net 4311 [ + + ]: 344 : if (_state->lex->lex_level > 2)
1241 tgl@sss.pgh.pa.us 4312 : 28 : return JSON_SUCCESS;
4313 : :
3316 andrew@dunslane.net 4314 : 316 : _state->saved_token_type = _state->lex->token_type;
4315 : :
4785 4316 [ + + ]: 316 : if (_state->lex->token_type == JSON_TOKEN_ARRAY_START ||
4317 [ + + ]: 304 : _state->lex->token_type == JSON_TOKEN_OBJECT_START)
4318 : : {
4319 : 36 : _state->save_json_start = _state->lex->token_start;
4320 : : }
4321 : : else
4322 : : {
4323 : 280 : _state->save_json_start = NULL;
4324 : : }
4325 : :
1241 tgl@sss.pgh.pa.us 4326 : 316 : return JSON_SUCCESS;
4327 : : }
4328 : :
4329 : : static JsonParseErrorType
4785 andrew@dunslane.net 4330 : 344 : populate_recordset_object_field_end(void *state, char *fname, bool isnull)
4331 : : {
4672 peter_e@gmx.net 4332 : 344 : PopulateRecordsetState *_state = (PopulateRecordsetState *) state;
4333 : : JsonHashEntry *hashentry;
4334 : : bool found;
4335 : :
4336 : : /*
4337 : : * Ignore nested fields.
4338 : : */
4332 tgl@sss.pgh.pa.us 4339 [ + + ]: 344 : if (_state->lex->lex_level > 2)
1241 4340 : 28 : return JSON_SUCCESS;
4341 : :
4342 : : /*
4343 : : * Ignore field names >= NAMEDATALEN - they can't match a record field.
4344 : : * (Note: without this test, the hash code would truncate the string at
4345 : : * NAMEDATALEN-1, and could then match against a similarly-truncated
4346 : : * record field name. That would be a reasonable behavior, but this code
4347 : : * has previously insisted on exact equality, so we keep this behavior.)
4348 : : */
4332 4349 [ - + ]: 316 : if (strlen(fname) >= NAMEDATALEN)
1241 tgl@sss.pgh.pa.us 4350 :UBC 0 : return JSON_SUCCESS;
4351 : :
4332 tgl@sss.pgh.pa.us 4352 :CBC 316 : hashentry = hash_search(_state->json_hash, fname, HASH_ENTER, &found);
4353 : :
4354 : : /*
4355 : : * found being true indicates a duplicate. We don't do anything about
4356 : : * that, a later field with the same name overrides the earlier field.
4357 : : */
4358 : :
3316 andrew@dunslane.net 4359 : 316 : hashentry->type = _state->saved_token_type;
4360 [ - + ]: 316 : Assert(isnull == (hashentry->type == JSON_TOKEN_NULL));
4361 : :
4785 4362 [ + + ]: 316 : if (_state->save_json_start != NULL)
4363 : : {
4364 : 36 : int len = _state->lex->prev_token_terminator - _state->save_json_start;
4365 : 36 : char *val = palloc((len + 1) * sizeof(char));
4366 : :
4367 : 36 : memcpy(val, _state->save_json_start, len);
4368 : 36 : val[len] = '\0';
4369 : 36 : hashentry->val = val;
4370 : : }
4371 : : else
4372 : : {
4373 : : /* must have had a scalar instead */
4374 : 280 : hashentry->val = _state->saved_scalar;
4375 : : }
4376 : :
1241 tgl@sss.pgh.pa.us 4377 : 316 : return JSON_SUCCESS;
4378 : : }
4379 : :
4380 : : /*
4381 : : * Semantic actions for json_strip_nulls.
4382 : : *
4383 : : * Simply repeat the input on the output unless we encounter
4384 : : * a null object field. State for this is set when the field
4385 : : * is started and reset when the scalar action (which must be next)
4386 : : * is called.
4387 : : */
4388 : :
4389 : : static JsonParseErrorType
4162 andrew@dunslane.net 4390 : 60 : sn_object_start(void *state)
4391 : : {
4392 : 60 : StripnullState *_state = (StripnullState *) state;
4393 : :
4394 [ - + ]: 60 : appendStringInfoCharMacro(_state->strval, '{');
4395 : :
1241 tgl@sss.pgh.pa.us 4396 : 60 : return JSON_SUCCESS;
4397 : : }
4398 : :
4399 : : static JsonParseErrorType
4162 andrew@dunslane.net 4400 : 60 : sn_object_end(void *state)
4401 : : {
4402 : 60 : StripnullState *_state = (StripnullState *) state;
4403 : :
4404 [ - + ]: 60 : appendStringInfoCharMacro(_state->strval, '}');
4405 : :
1241 tgl@sss.pgh.pa.us 4406 : 60 : return JSON_SUCCESS;
4407 : : }
4408 : :
4409 : : static JsonParseErrorType
4162 andrew@dunslane.net 4410 : 30 : sn_array_start(void *state)
4411 : : {
4412 : 30 : StripnullState *_state = (StripnullState *) state;
4413 : :
4414 [ - + ]: 30 : appendStringInfoCharMacro(_state->strval, '[');
4415 : :
1241 tgl@sss.pgh.pa.us 4416 : 30 : return JSON_SUCCESS;
4417 : : }
4418 : :
4419 : : static JsonParseErrorType
4162 andrew@dunslane.net 4420 : 30 : sn_array_end(void *state)
4421 : : {
4422 : 30 : StripnullState *_state = (StripnullState *) state;
4423 : :
4424 [ - + ]: 30 : appendStringInfoCharMacro(_state->strval, ']');
4425 : :
1241 tgl@sss.pgh.pa.us 4426 : 30 : return JSON_SUCCESS;
4427 : : }
4428 : :
4429 : : static JsonParseErrorType
4000 bruce@momjian.us 4430 : 130 : sn_object_field_start(void *state, char *fname, bool isnull)
4431 : : {
4162 andrew@dunslane.net 4432 : 130 : StripnullState *_state = (StripnullState *) state;
4433 : :
4434 [ + + ]: 130 : if (isnull)
4435 : : {
4436 : : /*
4437 : : * The next thing must be a scalar or isnull couldn't be true, so
4438 : : * there is no danger of this state being carried down into a nested
4439 : : * object or array. The flag will be reset in the scalar action.
4440 : : */
4441 : 50 : _state->skip_next_null = true;
1241 tgl@sss.pgh.pa.us 4442 : 50 : return JSON_SUCCESS;
4443 : : }
4444 : :
4162 andrew@dunslane.net 4445 [ + + ]: 80 : if (_state->strval->data[_state->strval->len - 1] != '{')
4446 [ - + ]: 40 : appendStringInfoCharMacro(_state->strval, ',');
4447 : :
4448 : : /*
4449 : : * Unfortunately we don't have the quoted and escaped string any more, so
4450 : : * we have to re-escape it.
4451 : : */
4000 bruce@momjian.us 4452 : 80 : escape_json(_state->strval, fname);
4453 : :
4162 andrew@dunslane.net 4454 [ - + ]: 80 : appendStringInfoCharMacro(_state->strval, ':');
4455 : :
1241 tgl@sss.pgh.pa.us 4456 : 80 : return JSON_SUCCESS;
4457 : : }
4458 : :
4459 : : static JsonParseErrorType
4000 bruce@momjian.us 4460 : 110 : sn_array_element_start(void *state, bool isnull)
4461 : : {
4162 andrew@dunslane.net 4462 : 110 : StripnullState *_state = (StripnullState *) state;
4463 : :
4464 : : /* If strip_in_arrays is enabled and this is a null, mark it for skipping */
426 4465 [ + + + + ]: 110 : if (isnull && _state->strip_in_arrays)
4466 : : {
4467 : 10 : _state->skip_next_null = true;
4468 : 10 : return JSON_SUCCESS;
4469 : : }
4470 : :
4471 : : /* Only add a comma if this is not the first valid element */
4472 [ + - ]: 100 : if (_state->strval->len > 0 &&
4473 [ + + ]: 100 : _state->strval->data[_state->strval->len - 1] != '[')
4474 : : {
4162 4475 [ - + ]: 70 : appendStringInfoCharMacro(_state->strval, ',');
4476 : : }
4477 : :
1241 tgl@sss.pgh.pa.us 4478 : 100 : return JSON_SUCCESS;
4479 : : }
4480 : :
4481 : : static JsonParseErrorType
4162 andrew@dunslane.net 4482 : 220 : sn_scalar(void *state, char *token, JsonTokenType tokentype)
4483 : : {
4484 : 220 : StripnullState *_state = (StripnullState *) state;
4485 : :
4486 [ + + ]: 220 : if (_state->skip_next_null)
4487 : : {
4000 bruce@momjian.us 4488 [ - + ]: 60 : Assert(tokentype == JSON_TOKEN_NULL);
4162 andrew@dunslane.net 4489 : 60 : _state->skip_next_null = false;
1241 tgl@sss.pgh.pa.us 4490 : 60 : return JSON_SUCCESS;
4491 : : }
4492 : :
4162 andrew@dunslane.net 4493 [ + + ]: 160 : if (tokentype == JSON_TOKEN_STRING)
4494 : 10 : escape_json(_state->strval, token);
4495 : : else
4496 : 150 : appendStringInfoString(_state->strval, token);
4497 : :
1241 tgl@sss.pgh.pa.us 4498 : 160 : return JSON_SUCCESS;
4499 : : }
4500 : :
4501 : : /*
4502 : : * SQL function json_strip_nulls(json) -> json
4503 : : */
4504 : : Datum
4162 andrew@dunslane.net 4505 : 70 : json_strip_nulls(PG_FUNCTION_ARGS)
4506 : : {
3341 noah@leadboat.com 4507 : 70 : text *json = PG_GETARG_TEXT_PP(0);
426 andrew@dunslane.net 4508 [ + - + + ]: 70 : bool strip_in_arrays = PG_NARGS() == 2 ? PG_GETARG_BOOL(1) : false;
4509 : : StripnullState *state;
4510 : : StringInfoData strbuf;
4511 : : JsonLexContext lex;
4512 : : JsonSemAction *sem;
4513 : :
146 michael@paquier.xyz 4514 :GNC 70 : state = palloc0_object(StripnullState);
4515 : 70 : sem = palloc0_object(JsonSemAction);
180 drowley@postgresql.o 4516 : 70 : initStringInfo(&strbuf);
4517 : :
943 alvherre@alvh.no-ip. 4518 :CBC 70 : state->lex = makeJsonLexContext(&lex, json, true);
180 drowley@postgresql.o 4519 :GNC 70 : state->strval = &strbuf;
4162 andrew@dunslane.net 4520 :CBC 70 : state->skip_next_null = false;
426 4521 : 70 : state->strip_in_arrays = strip_in_arrays;
4522 : :
523 peter@eisentraut.org 4523 : 70 : sem->semstate = state;
4162 andrew@dunslane.net 4524 : 70 : sem->object_start = sn_object_start;
4525 : 70 : sem->object_end = sn_object_end;
4526 : 70 : sem->array_start = sn_array_start;
4527 : 70 : sem->array_end = sn_array_end;
4528 : 70 : sem->scalar = sn_scalar;
4529 : 70 : sem->array_element_start = sn_array_element_start;
4530 : 70 : sem->object_field_start = sn_object_field_start;
4531 : :
943 alvherre@alvh.no-ip. 4532 : 70 : pg_parse_json_or_ereport(&lex, sem);
4533 : :
4162 andrew@dunslane.net 4534 : 70 : PG_RETURN_TEXT_P(cstring_to_text_with_len(state->strval->data,
4535 : : state->strval->len));
4536 : : }
4537 : :
4538 : : /*
4539 : : * SQL function jsonb_strip_nulls(jsonb, bool) -> jsonb
4540 : : */
4541 : : Datum
4542 : 117 : jsonb_strip_nulls(PG_FUNCTION_ARGS)
4543 : : {
3151 tgl@sss.pgh.pa.us 4544 : 117 : Jsonb *jb = PG_GETARG_JSONB_P(0);
426 andrew@dunslane.net 4545 : 117 : bool strip_in_arrays = false;
4546 : : JsonbIterator *it;
149 tgl@sss.pgh.pa.us 4547 :GNC 117 : JsonbInState parseState = {0};
4548 : : JsonbValue v,
4549 : : k;
4550 : : JsonbIteratorToken type;
4000 bruce@momjian.us 4551 :CBC 117 : bool last_was_key = false;
4552 : :
426 andrew@dunslane.net 4553 [ + - ]: 117 : if (PG_NARGS() == 2)
4554 : 117 : strip_in_arrays = PG_GETARG_BOOL(1);
4555 : :
4162 4556 [ + + ]: 117 : if (JB_ROOT_IS_SCALAR(jb))
4557 : 30 : PG_RETURN_POINTER(jb);
4558 : :
4559 : 87 : it = JsonbIteratorInit(&jb->root);
4560 : :
4561 [ + + ]: 1903 : while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
4562 : : {
4000 bruce@momjian.us 4563 [ + + - + ]: 1816 : Assert(!(type == WJB_KEY && last_was_key));
4564 : :
4162 andrew@dunslane.net 4565 [ + + ]: 1816 : if (type == WJB_KEY)
4566 : : {
4567 : : /* stash the key until we know if it has a null value */
4568 : 741 : k = v;
4569 : 741 : last_was_key = true;
4570 : 741 : continue;
4571 : : }
4572 : :
4573 [ + + ]: 1075 : if (last_was_key)
4574 : : {
4575 : : /* if the last element was a key this one can't be */
4576 : 741 : last_was_key = false;
4577 : :
4578 : : /* skip this field if value is null */
4579 [ + + + + ]: 741 : if (type == WJB_VALUE && v.type == jbvNull)
4580 : 375 : continue;
4581 : :
4582 : : /* otherwise, do a delayed push of the key */
149 tgl@sss.pgh.pa.us 4583 :GNC 366 : pushJsonbValue(&parseState, WJB_KEY, &k);
4584 : : }
4585 : :
4586 : : /* if strip_in_arrays is set, also skip null array elements */
426 andrew@dunslane.net 4587 [ + + ]:CBC 700 : if (strip_in_arrays)
4588 [ + + + + ]: 160 : if (type == WJB_ELEM && v.type == jbvNull)
4589 : 10 : continue;
4590 : :
4162 4591 [ + + + + ]: 690 : if (type == WJB_VALUE || type == WJB_ELEM)
149 tgl@sss.pgh.pa.us 4592 :GNC 416 : pushJsonbValue(&parseState, type, &v);
4593 : : else
4594 : 274 : pushJsonbValue(&parseState, type, NULL);
4595 : : }
4596 : :
4597 : 87 : PG_RETURN_POINTER(JsonbValueToJsonb(parseState.result));
4598 : : }
4599 : :
4600 : : /*
4601 : : * SQL function jsonb_pretty (jsonb)
4602 : : *
4603 : : * Pretty-printed text for the jsonb
4604 : : */
4605 : : Datum
4011 andrew@dunslane.net 4606 :CBC 60 : jsonb_pretty(PG_FUNCTION_ARGS)
4607 : : {
3151 tgl@sss.pgh.pa.us 4608 : 60 : Jsonb *jb = PG_GETARG_JSONB_P(0);
4609 : : StringInfoData str;
4610 : :
180 drowley@postgresql.o 4611 :GNC 60 : initStringInfo(&str);
4612 : 60 : JsonbToCStringIndent(&str, &jb->root, VARSIZE(jb));
4613 : :
4614 : 60 : PG_RETURN_TEXT_P(cstring_to_text_with_len(str.data, str.len));
4615 : : }
4616 : :
4617 : : /*
4618 : : * SQL function jsonb_concat (jsonb, jsonb)
4619 : : *
4620 : : * function for || operator
4621 : : */
4622 : : Datum
4011 andrew@dunslane.net 4623 :CBC 281 : jsonb_concat(PG_FUNCTION_ARGS)
4624 : : {
3151 tgl@sss.pgh.pa.us 4625 : 281 : Jsonb *jb1 = PG_GETARG_JSONB_P(0);
4626 : 281 : Jsonb *jb2 = PG_GETARG_JSONB_P(1);
149 tgl@sss.pgh.pa.us 4627 :GNC 281 : JsonbInState state = {0};
4628 : : JsonbIterator *it1,
4629 : : *it2;
4630 : :
4631 : : /*
4632 : : * If one of the jsonb is empty, just return the other if it's not scalar
4633 : : * and both are of the same kind. If it's a scalar or they are of
4634 : : * different kinds we need to perform the concatenation even if one is
4635 : : * empty.
4636 : : */
3887 andrew@dunslane.net 4637 [ + + ]:CBC 281 : if (JB_ROOT_IS_OBJECT(jb1) == JB_ROOT_IS_OBJECT(jb2))
4638 : : {
4639 [ + + + + ]: 217 : if (JB_ROOT_COUNT(jb1) == 0 && !JB_ROOT_IS_SCALAR(jb2))
3151 tgl@sss.pgh.pa.us 4640 : 137 : PG_RETURN_JSONB_P(jb2);
3887 andrew@dunslane.net 4641 [ + + + + ]: 80 : else if (JB_ROOT_COUNT(jb2) == 0 && !JB_ROOT_IS_SCALAR(jb1))
3151 tgl@sss.pgh.pa.us 4642 : 10 : PG_RETURN_JSONB_P(jb1);
4643 : : }
4644 : :
4011 andrew@dunslane.net 4645 : 134 : it1 = JsonbIteratorInit(&jb1->root);
4646 : 134 : it2 = JsonbIteratorInit(&jb2->root);
4647 : :
149 tgl@sss.pgh.pa.us 4648 :GNC 134 : IteratorConcat(&it1, &it2, &state);
4649 : :
4650 : 134 : PG_RETURN_JSONB_P(JsonbValueToJsonb(state.result));
4651 : : }
4652 : :
4653 : :
4654 : : /*
4655 : : * SQL function jsonb_delete (jsonb, text)
4656 : : *
4657 : : * return a copy of the jsonb with the indicated item
4658 : : * removed.
4659 : : */
4660 : : Datum
4011 andrew@dunslane.net 4661 :CBC 133 : jsonb_delete(PG_FUNCTION_ARGS)
4662 : : {
3151 tgl@sss.pgh.pa.us 4663 : 133 : Jsonb *in = PG_GETARG_JSONB_P(0);
4011 andrew@dunslane.net 4664 : 133 : text *key = PG_GETARG_TEXT_PP(1);
4665 [ - + ]: 133 : char *keyptr = VARDATA_ANY(key);
4666 [ - + - - : 133 : int keylen = VARSIZE_ANY_EXHDR(key);
- - - - -
+ ]
149 tgl@sss.pgh.pa.us 4667 :GNC 133 : JsonbInState pstate = {0};
4668 : : JsonbIterator *it;
4669 : : JsonbValue v;
4011 andrew@dunslane.net 4670 :CBC 133 : bool skipNested = false;
4671 : : JsonbIteratorToken r;
4672 : :
4010 4673 [ + + ]: 133 : if (JB_ROOT_IS_SCALAR(in))
4674 [ + - ]: 4 : ereport(ERROR,
4675 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4676 : : errmsg("cannot delete from scalar")));
4677 : :
4011 4678 [ + + ]: 129 : if (JB_ROOT_COUNT(in) == 0)
3151 tgl@sss.pgh.pa.us 4679 : 10 : PG_RETURN_JSONB_P(in);
4680 : :
4011 andrew@dunslane.net 4681 : 119 : it = JsonbIteratorInit(&in->root);
4682 : :
2942 tgl@sss.pgh.pa.us 4683 [ + + ]: 1618 : while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
4684 : : {
4011 andrew@dunslane.net 4685 : 1499 : skipNested = true;
4686 : :
4687 [ + - + + ]: 1499 : if ((r == WJB_ELEM || r == WJB_KEY) &&
4688 [ + - + + ]: 685 : (v.type == jbvString && keylen == v.val.string.len &&
4689 [ + + ]: 229 : memcmp(keyptr, v.val.string.val, keylen) == 0))
4690 : : {
4691 : : /* skip corresponding value as well */
4692 [ + - ]: 109 : if (r == WJB_KEY)
2942 tgl@sss.pgh.pa.us 4693 : 109 : (void) JsonbIteratorNext(&it, &v, true);
4694 : :
4011 andrew@dunslane.net 4695 : 109 : continue;
4696 : : }
4697 : :
149 tgl@sss.pgh.pa.us 4698 [ + + ]:GNC 1390 : pushJsonbValue(&pstate, r, r < WJB_BEGIN_ARRAY ? &v : NULL);
4699 : : }
4700 : :
4701 : 119 : PG_RETURN_JSONB_P(JsonbValueToJsonb(pstate.result));
4702 : : }
4703 : :
4704 : : /*
4705 : : * SQL function jsonb_delete (jsonb, variadic text[])
4706 : : *
4707 : : * return a copy of the jsonb with the indicated items
4708 : : * removed.
4709 : : */
4710 : : Datum
3394 magnus@hagander.net 4711 :CBC 15 : jsonb_delete_array(PG_FUNCTION_ARGS)
4712 : : {
3151 tgl@sss.pgh.pa.us 4713 : 15 : Jsonb *in = PG_GETARG_JSONB_P(0);
3394 magnus@hagander.net 4714 : 15 : ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1);
4715 : : Datum *keys_elems;
4716 : : bool *keys_nulls;
4717 : : int keys_len;
149 tgl@sss.pgh.pa.us 4718 :GNC 15 : JsonbInState pstate = {0};
4719 : : JsonbIterator *it;
4720 : : JsonbValue v;
3394 magnus@hagander.net 4721 :CBC 15 : bool skipNested = false;
4722 : : JsonbIteratorToken r;
4723 : :
4724 [ - + ]: 15 : if (ARR_NDIM(keys) > 1)
3394 magnus@hagander.net 4725 [ # # ]:UBC 0 : ereport(ERROR,
4726 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
4727 : : errmsg("wrong number of array subscripts")));
4728 : :
3394 magnus@hagander.net 4729 [ - + ]:CBC 15 : if (JB_ROOT_IS_SCALAR(in))
3394 magnus@hagander.net 4730 [ # # ]:UBC 0 : ereport(ERROR,
4731 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4732 : : errmsg("cannot delete from scalar")));
4733 : :
3394 magnus@hagander.net 4734 [ - + ]:CBC 15 : if (JB_ROOT_COUNT(in) == 0)
3151 tgl@sss.pgh.pa.us 4735 :UBC 0 : PG_RETURN_JSONB_P(in);
4736 : :
1404 peter@eisentraut.org 4737 :CBC 15 : deconstruct_array_builtin(keys, TEXTOID, &keys_elems, &keys_nulls, &keys_len);
4738 : :
3394 magnus@hagander.net 4739 [ + + ]: 15 : if (keys_len == 0)
3151 tgl@sss.pgh.pa.us 4740 : 5 : PG_RETURN_JSONB_P(in);
4741 : :
3394 magnus@hagander.net 4742 : 10 : it = JsonbIteratorInit(&in->root);
4743 : :
2942 tgl@sss.pgh.pa.us 4744 [ + + ]: 75 : while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
4745 : : {
3394 magnus@hagander.net 4746 : 65 : skipNested = true;
4747 : :
4748 [ + - + + : 65 : if ((r == WJB_ELEM || r == WJB_KEY) && v.type == jbvString)
+ - ]
4749 : : {
4750 : : int i;
4751 : 30 : bool found = false;
4752 : :
4753 [ + + ]: 55 : for (i = 0; i < keys_len; i++)
4754 : : {
4755 : : char *keyptr;
4756 : : int keylen;
4757 : :
4758 [ - + ]: 40 : if (keys_nulls[i])
3394 magnus@hagander.net 4759 :UBC 0 : continue;
4760 : :
4761 : : /* We rely on the array elements not being toasted */
273 peter@eisentraut.org 4762 :GNC 40 : keyptr = VARDATA_ANY(DatumGetPointer(keys_elems[i]));
4763 : 40 : keylen = VARSIZE_ANY_EXHDR(DatumGetPointer(keys_elems[i]));
3394 magnus@hagander.net 4764 [ + - ]:CBC 40 : if (keylen == v.val.string.len &&
4765 [ + + ]: 40 : memcmp(keyptr, v.val.string.val, keylen) == 0)
4766 : : {
4767 : 15 : found = true;
4768 : 15 : break;
4769 : : }
4770 : : }
4771 [ + + ]: 30 : if (found)
4772 : : {
4773 : : /* skip corresponding value as well */
4774 [ + - ]: 15 : if (r == WJB_KEY)
2942 tgl@sss.pgh.pa.us 4775 : 15 : (void) JsonbIteratorNext(&it, &v, true);
4776 : :
3394 magnus@hagander.net 4777 : 15 : continue;
4778 : : }
4779 : : }
4780 : :
149 tgl@sss.pgh.pa.us 4781 [ + + ]:GNC 50 : pushJsonbValue(&pstate, r, r < WJB_BEGIN_ARRAY ? &v : NULL);
4782 : : }
4783 : :
4784 : 10 : PG_RETURN_JSONB_P(JsonbValueToJsonb(pstate.result));
4785 : : }
4786 : :
4787 : : /*
4788 : : * SQL function jsonb_delete (jsonb, int)
4789 : : *
4790 : : * return a copy of the jsonb with the indicated item
4791 : : * removed. Negative int means count back from the
4792 : : * end of the items.
4793 : : */
4794 : : Datum
4011 andrew@dunslane.net 4795 :CBC 181 : jsonb_delete_idx(PG_FUNCTION_ARGS)
4796 : : {
3151 tgl@sss.pgh.pa.us 4797 : 181 : Jsonb *in = PG_GETARG_JSONB_P(0);
4011 andrew@dunslane.net 4798 : 181 : int idx = PG_GETARG_INT32(1);
149 tgl@sss.pgh.pa.us 4799 :GNC 181 : JsonbInState pstate = {0};
4800 : : JsonbIterator *it;
3859 noah@leadboat.com 4801 :CBC 181 : uint32 i = 0,
4802 : : n;
4803 : : JsonbValue v;
4804 : : JsonbIteratorToken r;
4805 : :
4010 andrew@dunslane.net 4806 [ + + ]: 181 : if (JB_ROOT_IS_SCALAR(in))
4807 [ + - ]: 4 : ereport(ERROR,
4808 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4809 : : errmsg("cannot delete from scalar")));
4810 : :
3985 4811 [ + + ]: 177 : if (JB_ROOT_IS_OBJECT(in))
4812 [ + - ]: 4 : ereport(ERROR,
4813 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4814 : : errmsg("cannot delete from object using integer index")));
4815 : :
4011 4816 [ + + ]: 173 : if (JB_ROOT_COUNT(in) == 0)
3151 tgl@sss.pgh.pa.us 4817 : 5 : PG_RETURN_JSONB_P(in);
4818 : :
4011 andrew@dunslane.net 4819 : 168 : it = JsonbIteratorInit(&in->root);
4820 : :
4821 : 168 : r = JsonbIteratorNext(&it, &v, false);
3617 rhaas@postgresql.org 4822 [ - + ]: 168 : Assert(r == WJB_BEGIN_ARRAY);
3945 andrew@dunslane.net 4823 : 168 : n = v.val.array.nElems;
4824 : :
4011 4825 [ + + ]: 168 : if (idx < 0)
4826 : : {
627 nathan@postgresql.or 4827 [ + + ]: 20 : if (pg_abs_s32(idx) > n)
4011 andrew@dunslane.net 4828 : 5 : idx = n;
4829 : : else
4830 : 15 : idx = n + idx;
4831 : : }
4832 : :
4833 [ + + ]: 168 : if (idx >= n)
3151 tgl@sss.pgh.pa.us 4834 : 10 : PG_RETURN_JSONB_P(in);
4835 : :
149 tgl@sss.pgh.pa.us 4836 :GNC 158 : pushJsonbValue(&pstate, r, NULL);
4837 : :
2942 tgl@sss.pgh.pa.us 4838 [ + + ]:CBC 534 : while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
4839 : : {
3945 andrew@dunslane.net 4840 [ + + ]: 376 : if (r == WJB_ELEM)
4841 : : {
4011 4842 [ + + ]: 218 : if (i++ == idx)
4843 : 158 : continue;
4844 : : }
4845 : :
149 tgl@sss.pgh.pa.us 4846 [ + + ]:GNC 218 : pushJsonbValue(&pstate, r, r < WJB_BEGIN_ARRAY ? &v : NULL);
4847 : : }
4848 : :
4849 : 158 : PG_RETURN_JSONB_P(JsonbValueToJsonb(pstate.result));
4850 : : }
4851 : :
4852 : : /*
4853 : : * SQL function jsonb_set(jsonb, text[], jsonb, boolean)
4854 : : */
4855 : : Datum
3992 andrew@dunslane.net 4856 :CBC 218 : jsonb_set(PG_FUNCTION_ARGS)
4857 : : {
3151 tgl@sss.pgh.pa.us 4858 : 218 : Jsonb *in = PG_GETARG_JSONB_P(0);
4011 andrew@dunslane.net 4859 : 218 : ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
1920 akorotkov@postgresql 4860 : 218 : Jsonb *newjsonb = PG_GETARG_JSONB_P(2);
4861 : : JsonbValue newval;
3992 andrew@dunslane.net 4862 : 218 : bool create = PG_GETARG_BOOL(3);
4863 : : Datum *path_elems;
4864 : : bool *path_nulls;
4865 : : int path_len;
4866 : : JsonbIterator *it;
149 tgl@sss.pgh.pa.us 4867 :GNC 218 : JsonbInState st = {0};
4868 : :
1920 akorotkov@postgresql 4869 :CBC 218 : JsonbToJsonbValue(newjsonb, &newval);
4870 : :
4011 andrew@dunslane.net 4871 [ - + ]: 218 : if (ARR_NDIM(path) > 1)
4011 andrew@dunslane.net 4872 [ # # ]:UBC 0 : ereport(ERROR,
4873 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
4874 : : errmsg("wrong number of array subscripts")));
4875 : :
4010 andrew@dunslane.net 4876 [ + + ]:CBC 218 : if (JB_ROOT_IS_SCALAR(in))
4877 [ + - ]: 4 : ereport(ERROR,
4878 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4879 : : errmsg("cannot set path in scalar")));
4880 : :
3992 4881 [ + + + + ]: 214 : if (JB_ROOT_COUNT(in) == 0 && !create)
3151 tgl@sss.pgh.pa.us 4882 : 10 : PG_RETURN_JSONB_P(in);
4883 : :
1404 peter@eisentraut.org 4884 : 204 : deconstruct_array_builtin(path, TEXTOID, &path_elems, &path_nulls, &path_len);
4885 : :
4011 andrew@dunslane.net 4886 [ - + ]: 204 : if (path_len == 0)
3151 tgl@sss.pgh.pa.us 4887 :UBC 0 : PG_RETURN_JSONB_P(in);
4888 : :
4011 andrew@dunslane.net 4889 :CBC 204 : it = JsonbIteratorInit(&in->root);
4890 : :
149 tgl@sss.pgh.pa.us 4891 [ + + ]:GNC 204 : setPath(&it, path_elems, path_nulls, path_len, &st,
4892 : : 0, &newval, create ? JB_PATH_CREATE : JB_PATH_REPLACE);
4893 : :
4894 : 184 : PG_RETURN_JSONB_P(JsonbValueToJsonb(st.result));
4895 : : }
4896 : :
4897 : :
4898 : : /*
4899 : : * SQL function jsonb_set_lax(jsonb, text[], jsonb, boolean, text)
4900 : : */
4901 : : Datum
2300 andrew@dunslane.net 4902 :CBC 47 : jsonb_set_lax(PG_FUNCTION_ARGS)
4903 : : {
4904 : : text *handle_null;
4905 : : char *handle_val;
4906 : :
4907 [ + - + - : 47 : if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(3))
- + ]
2300 andrew@dunslane.net 4908 :UBC 0 : PG_RETURN_NULL();
4909 : :
4910 : : /* could happen if they pass in an explicit NULL */
2300 andrew@dunslane.net 4911 [ + + ]:CBC 47 : if (PG_ARGISNULL(4))
4912 [ + - ]: 4 : ereport(ERROR,
4913 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4914 : : errmsg("null_value_treatment must be \"delete_key\", \"return_target\", \"use_json_null\", or \"raise_exception\"")));
4915 : :
4916 : : /* if the new value isn't an SQL NULL just call jsonb_set */
2182 tgl@sss.pgh.pa.us 4917 [ + + ]: 43 : if (!PG_ARGISNULL(2))
2300 andrew@dunslane.net 4918 : 10 : return jsonb_set(fcinfo);
4919 : :
4920 : 33 : handle_null = PG_GETARG_TEXT_P(4);
4921 : 33 : handle_val = text_to_cstring(handle_null);
4922 : :
2182 tgl@sss.pgh.pa.us 4923 [ + + ]: 33 : if (strcmp(handle_val, "raise_exception") == 0)
4924 : : {
2300 andrew@dunslane.net 4925 [ + - ]: 4 : ereport(ERROR,
4926 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
4927 : : errmsg("JSON value must not be null"),
4928 : : errdetail("Exception was raised because null_value_treatment is \"raise_exception\"."),
4929 : : errhint("To avoid, either change the null_value_treatment argument or ensure that an SQL NULL is not passed.")));
4930 : : return (Datum) 0; /* silence stupider compilers */
4931 : : }
4932 [ + + ]: 29 : else if (strcmp(handle_val, "use_json_null") == 0)
4933 : : {
4934 : : Datum newval;
4935 : :
4936 : 15 : newval = DirectFunctionCall1(jsonb_in, CStringGetDatum("null"));
4937 : :
4938 : 15 : fcinfo->args[2].value = newval;
4939 : 15 : fcinfo->args[2].isnull = false;
4940 : 15 : return jsonb_set(fcinfo);
4941 : : }
4942 [ + + ]: 14 : else if (strcmp(handle_val, "delete_key") == 0)
4943 : : {
4944 : 5 : return jsonb_delete_path(fcinfo);
4945 : : }
4946 [ + + ]: 9 : else if (strcmp(handle_val, "return_target") == 0)
4947 : : {
4948 : 5 : Jsonb *in = PG_GETARG_JSONB_P(0);
4949 : :
4950 : 5 : PG_RETURN_JSONB_P(in);
4951 : : }
4952 : : else
4953 : : {
4954 [ + - ]: 4 : ereport(ERROR,
4955 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4956 : : errmsg("null_value_treatment must be \"delete_key\", \"return_target\", \"use_json_null\", or \"raise_exception\"")));
4957 : : return (Datum) 0; /* silence stupider compilers */
4958 : : }
4959 : : }
4960 : :
4961 : : /*
4962 : : * SQL function jsonb_delete_path(jsonb, text[])
4963 : : */
4964 : : Datum
4011 4965 : 74 : jsonb_delete_path(PG_FUNCTION_ARGS)
4966 : : {
3151 tgl@sss.pgh.pa.us 4967 : 74 : Jsonb *in = PG_GETARG_JSONB_P(0);
4011 andrew@dunslane.net 4968 : 74 : ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
4969 : : Datum *path_elems;
4970 : : bool *path_nulls;
4971 : : int path_len;
4972 : : JsonbIterator *it;
149 tgl@sss.pgh.pa.us 4973 :GNC 74 : JsonbInState st = {0};
4974 : :
4011 andrew@dunslane.net 4975 [ - + ]:CBC 74 : if (ARR_NDIM(path) > 1)
4011 andrew@dunslane.net 4976 [ # # ]:UBC 0 : ereport(ERROR,
4977 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
4978 : : errmsg("wrong number of array subscripts")));
4979 : :
4010 andrew@dunslane.net 4980 [ + + ]:CBC 74 : if (JB_ROOT_IS_SCALAR(in))
4981 [ + - ]: 4 : ereport(ERROR,
4982 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4983 : : errmsg("cannot delete path in scalar")));
4984 : :
4011 4985 [ + + ]: 70 : if (JB_ROOT_COUNT(in) == 0)
3151 tgl@sss.pgh.pa.us 4986 : 10 : PG_RETURN_JSONB_P(in);
4987 : :
1404 peter@eisentraut.org 4988 : 60 : deconstruct_array_builtin(path, TEXTOID, &path_elems, &path_nulls, &path_len);
4989 : :
4011 andrew@dunslane.net 4990 [ - + ]: 60 : if (path_len == 0)
3151 tgl@sss.pgh.pa.us 4991 :UBC 0 : PG_RETURN_JSONB_P(in);
4992 : :
4011 andrew@dunslane.net 4993 :CBC 60 : it = JsonbIteratorInit(&in->root);
4994 : :
149 tgl@sss.pgh.pa.us 4995 :GNC 60 : setPath(&it, path_elems, path_nulls, path_len, &st,
4996 : : 0, NULL, JB_PATH_DELETE);
4997 : :
4998 : 56 : PG_RETURN_JSONB_P(JsonbValueToJsonb(st.result));
4999 : : }
5000 : :
5001 : : /*
5002 : : * SQL function jsonb_insert(jsonb, text[], jsonb, boolean)
5003 : : */
5004 : : Datum
3681 teodor@sigaev.ru 5005 :CBC 108 : jsonb_insert(PG_FUNCTION_ARGS)
5006 : : {
3151 tgl@sss.pgh.pa.us 5007 : 108 : Jsonb *in = PG_GETARG_JSONB_P(0);
3681 teodor@sigaev.ru 5008 : 108 : ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
1920 akorotkov@postgresql 5009 : 108 : Jsonb *newjsonb = PG_GETARG_JSONB_P(2);
5010 : : JsonbValue newval;
3681 teodor@sigaev.ru 5011 : 108 : bool after = PG_GETARG_BOOL(3);
5012 : : Datum *path_elems;
5013 : : bool *path_nulls;
5014 : : int path_len;
5015 : : JsonbIterator *it;
149 tgl@sss.pgh.pa.us 5016 :GNC 108 : JsonbInState st = {0};
5017 : :
1920 akorotkov@postgresql 5018 :CBC 108 : JsonbToJsonbValue(newjsonb, &newval);
5019 : :
3681 teodor@sigaev.ru 5020 [ - + ]: 108 : if (ARR_NDIM(path) > 1)
3681 teodor@sigaev.ru 5021 [ # # ]:UBC 0 : ereport(ERROR,
5022 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
5023 : : errmsg("wrong number of array subscripts")));
5024 : :
3681 teodor@sigaev.ru 5025 [ - + ]:CBC 108 : if (JB_ROOT_IS_SCALAR(in))
3681 teodor@sigaev.ru 5026 [ # # ]:UBC 0 : ereport(ERROR,
5027 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5028 : : errmsg("cannot set path in scalar")));
5029 : :
1404 peter@eisentraut.org 5030 :CBC 108 : deconstruct_array_builtin(path, TEXTOID, &path_elems, &path_nulls, &path_len);
5031 : :
3681 teodor@sigaev.ru 5032 [ - + ]: 108 : if (path_len == 0)
3151 tgl@sss.pgh.pa.us 5033 :UBC 0 : PG_RETURN_JSONB_P(in);
5034 : :
3681 teodor@sigaev.ru 5035 :CBC 108 : it = JsonbIteratorInit(&in->root);
5036 : :
149 tgl@sss.pgh.pa.us 5037 [ + + ]:GNC 108 : setPath(&it, path_elems, path_nulls, path_len, &st, 0, &newval,
5038 : : after ? JB_PATH_INSERT_AFTER : JB_PATH_INSERT_BEFORE);
5039 : :
5040 : 100 : PG_RETURN_JSONB_P(JsonbValueToJsonb(st.result));
5041 : : }
5042 : :
5043 : : /*
5044 : : * Iterate over all jsonb objects and merge them into one.
5045 : : * The logic of this function copied from the same hstore function,
5046 : : * except the case, when it1 & it2 represents jbvObject.
5047 : : * In that case we just append the content of it2 to it1 without any
5048 : : * verifications.
5049 : : */
5050 : : static void
4011 andrew@dunslane.net 5051 :CBC 134 : IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
5052 : : JsonbInState *state)
5053 : : {
5054 : : JsonbValue v1,
5055 : : v2;
5056 : : JsonbIteratorToken r1,
5057 : : r2,
5058 : : rk1,
5059 : : rk2;
5060 : :
2069 tgl@sss.pgh.pa.us 5061 : 134 : rk1 = JsonbIteratorNext(it1, &v1, false);
5062 : 134 : rk2 = JsonbIteratorNext(it2, &v2, false);
5063 : :
5064 : : /*
5065 : : * JsonbIteratorNext reports raw scalars as if they were single-element
5066 : : * arrays; hence we only need consider "object" and "array" cases here.
5067 : : */
4011 andrew@dunslane.net 5068 [ + + + + ]: 134 : if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT)
5069 : : {
5070 : : /*
5071 : : * Both inputs are objects.
5072 : : *
5073 : : * Append all the tokens from v1 to res, except last WJB_END_OBJECT
5074 : : * (because res will not be finished yet).
5075 : : */
2069 tgl@sss.pgh.pa.us 5076 : 25 : pushJsonbValue(state, rk1, NULL);
3998 andrew@dunslane.net 5077 [ + + ]: 145 : while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_OBJECT)
3992 5078 : 120 : pushJsonbValue(state, r1, &v1);
5079 : :
5080 : : /*
5081 : : * Append all the tokens from v2 to res, including last WJB_END_OBJECT
5082 : : * (the concatenation will be completed). Any duplicate keys will
5083 : : * automatically override the value from the first object.
5084 : : */
2942 tgl@sss.pgh.pa.us 5085 [ + + ]: 130 : while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
149 tgl@sss.pgh.pa.us 5086 [ + + ]:GNC 105 : pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
5087 : : }
4011 andrew@dunslane.net 5088 [ + + + + ]:CBC 109 : else if (rk1 == WJB_BEGIN_ARRAY && rk2 == WJB_BEGIN_ARRAY)
5089 : : {
5090 : : /*
5091 : : * Both inputs are arrays.
5092 : : */
2069 tgl@sss.pgh.pa.us 5093 : 45 : pushJsonbValue(state, rk1, NULL);
5094 : :
3998 andrew@dunslane.net 5095 [ + + ]: 100 : while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
5096 : : {
5097 [ - + ]: 55 : Assert(r1 == WJB_ELEM);
4011 5098 : 55 : pushJsonbValue(state, r1, &v1);
5099 : : }
5100 : :
3992 5101 [ + + ]: 100 : while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_END_ARRAY)
5102 : : {
3998 5103 [ - + ]: 55 : Assert(r2 == WJB_ELEM);
5104 : 55 : pushJsonbValue(state, WJB_ELEM, &v2);
5105 : : }
5106 : :
149 tgl@sss.pgh.pa.us 5107 :GNC 45 : pushJsonbValue(state, WJB_END_ARRAY, NULL /* signal to sort */ );
5108 : : }
1961 tgl@sss.pgh.pa.us 5109 [ + + ]:CBC 64 : else if (rk1 == WJB_BEGIN_OBJECT)
5110 : : {
5111 : : /*
5112 : : * We have object || array.
5113 : : */
5114 [ - + ]: 15 : Assert(rk2 == WJB_BEGIN_ARRAY);
5115 : :
4011 andrew@dunslane.net 5116 : 15 : pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
5117 : :
1961 tgl@sss.pgh.pa.us 5118 : 15 : pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
5119 [ + + ]: 60 : while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_DONE)
5120 [ + + ]: 45 : pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
5121 : :
5122 [ + + ]: 45 : while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
149 tgl@sss.pgh.pa.us 5123 [ + + ]:GNC 30 : pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
5124 : : }
5125 : : else
5126 : : {
5127 : : /*
5128 : : * We have array || object.
5129 : : */
1961 tgl@sss.pgh.pa.us 5130 [ - + ]:CBC 49 : Assert(rk1 == WJB_BEGIN_ARRAY);
5131 [ - + ]: 49 : Assert(rk2 == WJB_BEGIN_OBJECT);
5132 : :
5133 : 49 : pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
5134 : :
5135 [ + + ]: 74 : while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
5136 : 25 : pushJsonbValue(state, r1, &v1);
5137 : :
5138 : 49 : pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
5139 [ + + ]: 632 : while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
5140 [ + + ]: 583 : pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
5141 : :
149 tgl@sss.pgh.pa.us 5142 :GNC 49 : pushJsonbValue(state, WJB_END_ARRAY, NULL);
5143 : : }
4011 andrew@dunslane.net 5144 :GIC 134 : }
5145 : :
5146 : : /*
5147 : : * Do most of the heavy work for jsonb_set/jsonb_insert
5148 : : *
5149 : : * If JB_PATH_DELETE bit is set in op_type, the element is to be removed.
5150 : : *
5151 : : * If any bit mentioned in JB_PATH_CREATE_OR_INSERT is set in op_type,
5152 : : * we create the new value if the key or array index does not exist.
5153 : : *
5154 : : * Bits JB_PATH_INSERT_BEFORE and JB_PATH_INSERT_AFTER in op_type
5155 : : * behave as JB_PATH_CREATE if new value is inserted in JsonbObject.
5156 : : *
5157 : : * If JB_PATH_FILL_GAPS bit is set, this will change an assignment logic in
5158 : : * case if target is an array. The assignment index will not be restricted by
5159 : : * number of elements in the array, and if there are any empty slots between
5160 : : * last element of the array and a new one they will be filled with nulls. If
5161 : : * the index is negative, it still will be considered an index from the end
5162 : : * of the array. Of a part of the path is not present and this part is more
5163 : : * than just one last element, this flag will instruct to create the whole
5164 : : * chain of corresponding objects and insert the value.
5165 : : *
5166 : : * JB_PATH_CONSISTENT_POSITION for an array indicates that the caller wants to
5167 : : * keep values with fixed indices. Indices for existing elements could be
5168 : : * changed (shifted forward) in case if the array is prepended with a new value
5169 : : * and a negative index out of the range, so this behavior will be prevented
5170 : : * and return an error.
5171 : : *
5172 : : * All path elements before the last must already exist
5173 : : * whatever bits in op_type are set, or nothing is done.
5174 : : */
5175 : : static void
186 peter@eisentraut.org 5176 :GNC 978 : setPath(JsonbIterator **it, const Datum *path_elems,
5177 : : const bool *path_nulls, int path_len,
5178 : : JsonbInState *st, int level, JsonbValue *newval, int op_type)
5179 : : {
5180 : : JsonbValue v;
5181 : : JsonbIteratorToken r;
5182 : :
3865 noah@leadboat.com 5183 :CBC 978 : check_stack_depth();
5184 : :
3866 andrew@dunslane.net 5185 [ + + ]: 978 : if (path_nulls[level])
3695 tgl@sss.pgh.pa.us 5186 [ + - ]: 12 : ereport(ERROR,
5187 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
5188 : : errmsg("path element at position %d is null",
5189 : : level + 1)));
5190 : :
4011 andrew@dunslane.net 5191 : 966 : r = JsonbIteratorNext(it, &v, false);
5192 : :
5193 [ + + + - ]: 966 : switch (r)
5194 : : {
5195 : 293 : case WJB_BEGIN_ARRAY:
5196 : :
5197 : : /*
5198 : : * If instructed complain about attempts to replace within a raw
5199 : : * scalar value. This happens even when current level is equal to
5200 : : * path_len, because the last path key should also correspond to
5201 : : * an object or an array, not raw scalar.
5202 : : */
1920 akorotkov@postgresql 5203 [ + + + - ]: 293 : if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
5204 [ + + ]: 60 : v.val.array.rawScalar)
5205 [ + - ]: 8 : ereport(ERROR,
5206 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5207 : : errmsg("cannot replace existing key"),
5208 : : errdetail("The path assumes key is a composite object, "
5209 : : "but it is a scalar value.")));
5210 : :
149 tgl@sss.pgh.pa.us 5211 :GNC 285 : pushJsonbValue(st, r, NULL);
3992 andrew@dunslane.net 5212 :CBC 285 : setPathArray(it, path_elems, path_nulls, path_len, st, level,
3681 teodor@sigaev.ru 5213 : 285 : newval, v.val.array.nElems, op_type);
4011 andrew@dunslane.net 5214 : 269 : r = JsonbIteratorNext(it, &v, false);
5215 [ - + ]: 269 : Assert(r == WJB_END_ARRAY);
149 tgl@sss.pgh.pa.us 5216 :GNC 269 : pushJsonbValue(st, r, NULL);
4011 andrew@dunslane.net 5217 :CBC 269 : break;
5218 : 653 : case WJB_BEGIN_OBJECT:
149 tgl@sss.pgh.pa.us 5219 :GNC 653 : pushJsonbValue(st, r, NULL);
3992 andrew@dunslane.net 5220 :CBC 653 : setPathObject(it, path_elems, path_nulls, path_len, st, level,
3681 teodor@sigaev.ru 5221 : 653 : newval, v.val.object.nPairs, op_type);
4011 andrew@dunslane.net 5222 : 585 : r = JsonbIteratorNext(it, &v, true);
5223 [ - + ]: 585 : Assert(r == WJB_END_OBJECT);
149 tgl@sss.pgh.pa.us 5224 :GNC 585 : pushJsonbValue(st, r, NULL);
4011 andrew@dunslane.net 5225 :CBC 585 : break;
5226 : 20 : case WJB_ELEM:
5227 : : case WJB_VALUE:
5228 : :
5229 : : /*
5230 : : * If instructed complain about attempts to replace within a
5231 : : * scalar value. This happens even when current level is equal to
5232 : : * path_len, because the last path key should also correspond to
5233 : : * an object or an array, not an element or value.
5234 : : */
1920 akorotkov@postgresql 5235 [ + - + - ]: 20 : if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
5236 [ + - ]: 20 : ereport(ERROR,
5237 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5238 : : errmsg("cannot replace existing key"),
5239 : : errdetail("The path assumes key is a composite object, "
5240 : : "but it is a scalar value.")));
5241 : :
149 tgl@sss.pgh.pa.us 5242 :UNC 0 : pushJsonbValue(st, r, &v);
4011 andrew@dunslane.net 5243 :UBC 0 : break;
5244 : 0 : default:
3695 tgl@sss.pgh.pa.us 5245 [ # # ]: 0 : elog(ERROR, "unrecognized iterator result: %d", (int) r);
5246 : : break;
5247 : : }
4011 andrew@dunslane.net 5248 :GIC 854 : }
5249 : :
5250 : : /*
5251 : : * Object walker for setPath
5252 : : */
5253 : : static void
186 peter@eisentraut.org 5254 :GNC 653 : setPathObject(JsonbIterator **it, const Datum *path_elems, const bool *path_nulls,
5255 : : int path_len, JsonbInState *st, int level,
5256 : : JsonbValue *newval, uint32 npairs, int op_type)
5257 : : {
1240 tgl@sss.pgh.pa.us 5258 :CBC 653 : text *pathelem = NULL;
5259 : : int i;
5260 : : JsonbValue k,
5261 : : v;
4011 andrew@dunslane.net 5262 : 653 : bool done = false;
5263 : :
5264 [ + - - + ]: 653 : if (level >= path_len || path_nulls[level])
4011 andrew@dunslane.net 5265 :UBC 0 : done = true;
5266 : : else
5267 : : {
5268 : : /* The path Datum could be toasted, in which case we must detoast it */
1240 tgl@sss.pgh.pa.us 5269 :CBC 653 : pathelem = DatumGetTextPP(path_elems[level]);
5270 : : }
5271 : :
5272 : : /* empty object is a special case for create */
3681 teodor@sigaev.ru 5273 [ + + + - ]: 653 : if ((npairs == 0) && (op_type & JB_PATH_CREATE_OR_INSERT) &&
5274 [ + + ]: 37 : (level == path_len - 1))
5275 : : {
5276 : : JsonbValue newkey;
5277 : :
3992 andrew@dunslane.net 5278 : 13 : newkey.type = jbvString;
1240 tgl@sss.pgh.pa.us 5279 [ - + ]: 13 : newkey.val.string.val = VARDATA_ANY(pathelem);
5280 [ - + - - : 13 : newkey.val.string.len = VARSIZE_ANY_EXHDR(pathelem);
- - - - -
+ ]
5281 : :
149 tgl@sss.pgh.pa.us 5282 :GNC 13 : pushJsonbValue(st, WJB_KEY, &newkey);
5283 : 13 : pushJsonbValue(st, WJB_VALUE, newval);
5284 : : }
5285 : :
3992 andrew@dunslane.net 5286 [ + + ]:CBC 3379 : for (i = 0; i < npairs; i++)
5287 : : {
3859 noah@leadboat.com 5288 : 2794 : JsonbIteratorToken r = JsonbIteratorNext(it, &k, true);
5289 : :
4011 andrew@dunslane.net 5290 [ - + ]: 2794 : Assert(r == WJB_KEY);
5291 : :
5292 [ + + + + ]: 4459 : if (!done &&
1240 tgl@sss.pgh.pa.us 5293 [ - + - - : 1665 : k.val.string.len == VARSIZE_ANY_EXHDR(pathelem) &&
- - - - +
+ ]
5294 [ + + ]: 861 : memcmp(k.val.string.val, VARDATA_ANY(pathelem),
4011 andrew@dunslane.net 5295 [ + + ]: 861 : k.val.string.len) == 0)
5296 : : {
1920 akorotkov@postgresql 5297 : 513 : done = true;
5298 : :
4011 andrew@dunslane.net 5299 [ + + ]: 513 : if (level == path_len - 1)
5300 : : {
5301 : : /*
5302 : : * called from jsonb_insert(), it forbids redefining an
5303 : : * existing value
5304 : : */
3681 teodor@sigaev.ru 5305 [ + + ]: 120 : if (op_type & (JB_PATH_INSERT_BEFORE | JB_PATH_INSERT_AFTER))
5306 [ + - ]: 8 : ereport(ERROR,
5307 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5308 : : errmsg("cannot replace existing key"),
5309 : : errhint("Try using the function jsonb_set "
5310 : : "to replace key value.")));
5311 : :
3617 rhaas@postgresql.org 5312 : 112 : r = JsonbIteratorNext(it, &v, true); /* skip value */
3681 teodor@sigaev.ru 5313 [ + + ]: 112 : if (!(op_type & JB_PATH_DELETE))
5314 : : {
149 tgl@sss.pgh.pa.us 5315 :GNC 81 : pushJsonbValue(st, WJB_KEY, &k);
5316 : 81 : pushJsonbValue(st, WJB_VALUE, newval);
5317 : : }
5318 : : }
5319 : : else
5320 : : {
5321 : 393 : pushJsonbValue(st, r, &k);
3992 andrew@dunslane.net 5322 :CBC 393 : setPath(it, path_elems, path_nulls, path_len,
5323 : : st, level + 1, newval, op_type);
5324 : : }
5325 : : }
5326 : : else
5327 : : {
3681 teodor@sigaev.ru 5328 [ + + + + ]: 2281 : if ((op_type & JB_PATH_CREATE_OR_INSERT) && !done &&
5329 [ + + + + ]: 263 : level == path_len - 1 && i == npairs - 1)
5330 : : {
5331 : : JsonbValue newkey;
5332 : :
3992 andrew@dunslane.net 5333 : 45 : newkey.type = jbvString;
1240 tgl@sss.pgh.pa.us 5334 [ - + ]: 45 : newkey.val.string.val = VARDATA_ANY(pathelem);
5335 [ - + - - : 45 : newkey.val.string.len = VARSIZE_ANY_EXHDR(pathelem);
- - - - -
+ ]
5336 : :
149 tgl@sss.pgh.pa.us 5337 :GNC 45 : pushJsonbValue(st, WJB_KEY, &newkey);
5338 : 45 : pushJsonbValue(st, WJB_VALUE, newval);
5339 : : }
5340 : :
5341 : 2281 : pushJsonbValue(st, r, &k);
4011 andrew@dunslane.net 5342 :CBC 2281 : r = JsonbIteratorNext(it, &v, false);
149 tgl@sss.pgh.pa.us 5343 [ + + ]:GNC 2281 : pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL);
4011 andrew@dunslane.net 5344 [ + + + + ]:CBC 2281 : if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT)
5345 : : {
4000 bruce@momjian.us 5346 : 584 : int walking_level = 1;
5347 : :
4011 andrew@dunslane.net 5348 [ + + ]: 5277 : while (walking_level != 0)
5349 : : {
5350 : 4693 : r = JsonbIteratorNext(it, &v, false);
5351 : :
5352 [ + + + + ]: 4693 : if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT)
5353 : 186 : ++walking_level;
5354 [ + + + + ]: 4693 : if (r == WJB_END_ARRAY || r == WJB_END_OBJECT)
5355 : 770 : --walking_level;
5356 : :
149 tgl@sss.pgh.pa.us 5357 [ + + ]:GNC 4693 : pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL);
5358 : : }
5359 : : }
5360 : : }
5361 : : }
5362 : :
5363 : : /*--
5364 : : * If we got here there are only few possibilities:
5365 : : * - no target path was found, and an open object with some keys/values was
5366 : : * pushed into the state
5367 : : * - an object is empty, only WJB_BEGIN_OBJECT is pushed
5368 : : *
5369 : : * In both cases if instructed to create the path when not present,
5370 : : * generate the whole chain of empty objects and insert the new value
5371 : : * there.
5372 : : */
1920 akorotkov@postgresql 5373 [ + + + + :CBC 585 : if (!done && (op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ + ]
5374 : : {
5375 : : JsonbValue newkey;
5376 : :
5377 : 32 : newkey.type = jbvString;
1240 tgl@sss.pgh.pa.us 5378 [ - + ]: 32 : newkey.val.string.val = VARDATA_ANY(pathelem);
5379 [ - + - - : 32 : newkey.val.string.len = VARSIZE_ANY_EXHDR(pathelem);
- - - - -
+ ]
5380 : :
149 tgl@sss.pgh.pa.us 5381 :GNC 32 : pushJsonbValue(st, WJB_KEY, &newkey);
154 peter@eisentraut.org 5382 : 32 : push_path(st, level, path_elems, path_nulls, path_len, newval);
5383 : :
5384 : : /* Result is closed with WJB_END_OBJECT outside of this function */
5385 : : }
4011 andrew@dunslane.net 5386 :CBC 585 : }
5387 : :
5388 : : /*
5389 : : * Array walker for setPath
5390 : : */
5391 : : static void
186 peter@eisentraut.org 5392 :GNC 285 : setPathArray(JsonbIterator **it, const Datum *path_elems, const bool *path_nulls,
5393 : : int path_len, JsonbInState *st, int level,
5394 : : JsonbValue *newval, uint32 nelems, int op_type)
5395 : : {
5396 : : JsonbValue v;
5397 : : int idx,
5398 : : i;
3992 andrew@dunslane.net 5399 :CBC 285 : bool done = false;
5400 : :
5401 : : /* pick correct index */
4011 5402 [ + - + - ]: 285 : if (level < path_len && !path_nulls[level])
5403 : 273 : {
3695 tgl@sss.pgh.pa.us 5404 : 285 : char *c = TextDatumGetCString(path_elems[level]);
5405 : : char *badp;
5406 : :
4011 andrew@dunslane.net 5407 : 285 : errno = 0;
1909 tgl@sss.pgh.pa.us 5408 : 285 : idx = strtoint(c, &badp, 10);
5409 [ + + + + : 285 : if (badp == c || *badp != '\0' || errno != 0)
- + ]
3695 5410 [ + - ]: 12 : ereport(ERROR,
5411 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
5412 : : errmsg("path element at position %d is not an integer: \"%s\"",
5413 : : level + 1, c)));
5414 : : }
5415 : : else
3992 andrew@dunslane.net 5416 :UBC 0 : idx = nelems;
5417 : :
4011 andrew@dunslane.net 5418 [ + + ]:CBC 273 : if (idx < 0)
5419 : : {
627 nathan@postgresql.or 5420 [ + + ]: 68 : if (pg_abs_s32(idx) > nelems)
5421 : : {
5422 : : /*
5423 : : * If asked to keep elements position consistent, it's not allowed
5424 : : * to prepend the array.
5425 : : */
1920 akorotkov@postgresql 5426 [ + + ]: 24 : if (op_type & JB_PATH_CONSISTENT_POSITION)
5427 [ + - ]: 4 : ereport(ERROR,
5428 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5429 : : errmsg("path element at position %d is out of range: %d",
5430 : : level + 1, idx)));
5431 : : else
627 nathan@postgresql.or 5432 : 20 : idx = PG_INT32_MIN;
5433 : : }
5434 : : else
3992 andrew@dunslane.net 5435 : 44 : idx = nelems + idx;
5436 : : }
5437 : :
5438 : : /*
5439 : : * Filling the gaps means there are no limits on the positive index are
5440 : : * imposed, we can set any element. Otherwise limit the index by nelems.
5441 : : */
1920 akorotkov@postgresql 5442 [ + + ]: 269 : if (!(op_type & JB_PATH_FILL_GAPS))
5443 : : {
5444 [ + + + + ]: 221 : if (idx > 0 && idx > nelems)
5445 : 40 : idx = nelems;
5446 : : }
5447 : :
5448 : : /*
5449 : : * if we're creating, and idx == INT_MIN, we prepend the new value to the
5450 : : * array also if the array is empty - in which case we don't really care
5451 : : * what the idx value is
5452 : : */
3681 teodor@sigaev.ru 5453 [ + + + + : 269 : if ((idx == INT_MIN || nelems == 0) && (level == path_len - 1) &&
+ + ]
5454 [ + + ]: 58 : (op_type & JB_PATH_CREATE_OR_INSERT))
5455 : : {
3992 andrew@dunslane.net 5456 [ - + ]: 53 : Assert(newval != NULL);
5457 : :
1920 akorotkov@postgresql 5458 [ + + + - : 53 : if (op_type & JB_PATH_FILL_GAPS && nelems == 0 && idx > 0)
+ + ]
5459 : 4 : push_null_elements(st, idx);
5460 : :
149 tgl@sss.pgh.pa.us 5461 :GNC 53 : pushJsonbValue(st, WJB_ELEM, newval);
5462 : :
3992 andrew@dunslane.net 5463 :CBC 53 : done = true;
5464 : : }
5465 : :
5466 : : /* iterate over the array elements */
5467 [ + + ]: 767 : for (i = 0; i < nelems; i++)
5468 : : {
5469 : : JsonbIteratorToken r;
5470 : :
4011 5471 [ + + + - ]: 498 : if (i == idx && level < path_len)
5472 : : {
1920 akorotkov@postgresql 5473 : 168 : done = true;
5474 : :
4011 andrew@dunslane.net 5475 [ + + ]: 168 : if (level == path_len - 1)
5476 : : {
4000 bruce@momjian.us 5477 : 119 : r = JsonbIteratorNext(it, &v, true); /* skip */
5478 : :
3681 teodor@sigaev.ru 5479 [ + + ]: 119 : if (op_type & (JB_PATH_INSERT_BEFORE | JB_PATH_CREATE))
149 tgl@sss.pgh.pa.us 5480 :GNC 69 : pushJsonbValue(st, WJB_ELEM, newval);
5481 : :
5482 : : /*
5483 : : * We should keep current value only in case of
5484 : : * JB_PATH_INSERT_BEFORE or JB_PATH_INSERT_AFTER because
5485 : : * otherwise it should be deleted or replaced
5486 : : */
3681 teodor@sigaev.ru 5487 [ + + ]:CBC 119 : if (op_type & (JB_PATH_INSERT_AFTER | JB_PATH_INSERT_BEFORE))
149 tgl@sss.pgh.pa.us 5488 :GNC 60 : pushJsonbValue(st, r, &v);
5489 : :
3491 tgl@sss.pgh.pa.us 5490 [ + + ]:CBC 119 : if (op_type & (JB_PATH_INSERT_AFTER | JB_PATH_REPLACE))
149 tgl@sss.pgh.pa.us 5491 :GNC 30 : pushJsonbValue(st, WJB_ELEM, newval);
5492 : : }
5493 : : else
5494 : 49 : setPath(it, path_elems, path_nulls, path_len,
5495 : : st, level + 1, newval, op_type);
5496 : : }
5497 : : else
5498 : : {
4011 andrew@dunslane.net 5499 :CBC 330 : r = JsonbIteratorNext(it, &v, false);
5500 : :
149 tgl@sss.pgh.pa.us 5501 [ + + ]:GNC 330 : pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL);
5502 : :
4011 andrew@dunslane.net 5503 [ + - + + ]:CBC 330 : if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT)
5504 : : {
4000 bruce@momjian.us 5505 : 4 : int walking_level = 1;
5506 : :
4011 andrew@dunslane.net 5507 [ + + ]: 16 : while (walking_level != 0)
5508 : : {
5509 : 12 : r = JsonbIteratorNext(it, &v, false);
5510 : :
5511 [ + - - + ]: 12 : if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT)
4011 andrew@dunslane.net 5512 :UBC 0 : ++walking_level;
4011 andrew@dunslane.net 5513 [ + - + + ]:CBC 12 : if (r == WJB_END_ARRAY || r == WJB_END_OBJECT)
5514 : 4 : --walking_level;
5515 : :
149 tgl@sss.pgh.pa.us 5516 [ + + ]:GNC 12 : pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL);
5517 : : }
5518 : : }
5519 : : }
5520 : : }
5521 : :
1920 akorotkov@postgresql 5522 [ + + + + :CBC 269 : if ((op_type & JB_PATH_CREATE_OR_INSERT) && !done && level == path_len - 1)
+ + ]
5523 : : {
5524 : : /*
5525 : : * If asked to fill the gaps, idx could be bigger than nelems, so
5526 : : * prepend the new element with nulls if that's the case.
5527 : : */
5528 [ + + + + ]: 27 : if (op_type & JB_PATH_FILL_GAPS && idx > nelems)
5529 : 8 : push_null_elements(st, idx - nelems);
5530 : :
149 tgl@sss.pgh.pa.us 5531 :GNC 27 : pushJsonbValue(st, WJB_ELEM, newval);
1920 akorotkov@postgresql 5532 :CBC 27 : done = true;
5533 : : }
5534 : :
5535 : : /*--
5536 : : * If we got here there are only few possibilities:
5537 : : * - no target path was found, and an open array with some keys/values was
5538 : : * pushed into the state
5539 : : * - an array is empty, only WJB_BEGIN_ARRAY is pushed
5540 : : *
5541 : : * In both cases if instructed to create the path when not present,
5542 : : * generate the whole chain of empty objects and insert the new value
5543 : : * there.
5544 : : */
5545 [ + + + + : 269 : if (!done && (op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ - ]
5546 : : {
5547 [ + + ]: 16 : if (idx > 0)
5548 : 8 : push_null_elements(st, idx - nelems);
5549 : :
154 peter@eisentraut.org 5550 :GNC 16 : push_path(st, level, path_elems, path_nulls, path_len, newval);
5551 : :
5552 : : /* Result is closed with WJB_END_OBJECT outside of this function */
5553 : : }
4011 andrew@dunslane.net 5554 :CBC 269 : }
5555 : :
5556 : : /*
5557 : : * Parse information about what elements of a jsonb document we want to iterate
5558 : : * in functions iterate_json(b)_values. This information is presented in jsonb
5559 : : * format, so that it can be easily extended in the future.
5560 : : */
5561 : : uint32
2950 teodor@sigaev.ru 5562 : 194 : parse_jsonb_index_flags(Jsonb *jb)
5563 : : {
5564 : : JsonbIterator *it;
5565 : : JsonbValue v;
5566 : : JsonbIteratorToken type;
2931 tgl@sss.pgh.pa.us 5567 : 194 : uint32 flags = 0;
5568 : :
2950 teodor@sigaev.ru 5569 : 194 : it = JsonbIteratorInit(&jb->root);
5570 : :
5571 : 194 : type = JsonbIteratorNext(&it, &v, false);
5572 : :
5573 : : /*
5574 : : * We iterate over array (scalar internally is represented as array, so,
5575 : : * we will accept it too) to check all its elements. Flag names are
5576 : : * chosen the same as jsonb_typeof uses.
5577 : : */
5578 [ + + ]: 194 : if (type != WJB_BEGIN_ARRAY)
5579 [ + - ]: 8 : ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5580 : : errmsg("wrong flag type, only arrays and scalars are allowed")));
5581 : :
5582 [ + + ]: 366 : while ((type = JsonbIteratorNext(&it, &v, false)) == WJB_ELEM)
5583 : : {
5584 [ + + ]: 204 : if (v.type != jbvString)
5585 [ + - ]: 16 : ereport(ERROR,
5586 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5587 : : errmsg("flag array element is not a string"),
5588 : : errhint("Possible values are: \"string\", \"numeric\", \"boolean\", \"key\", and \"all\".")));
5589 : :
5590 [ + + + + ]: 268 : if (v.val.string.len == 3 &&
2931 tgl@sss.pgh.pa.us 5591 : 80 : pg_strncasecmp(v.val.string.val, "all", 3) == 0)
2950 teodor@sigaev.ru 5592 : 60 : flags |= jtiAll;
5593 [ + + + - ]: 148 : else if (v.val.string.len == 3 &&
5594 : 20 : pg_strncasecmp(v.val.string.val, "key", 3) == 0)
5595 : 20 : flags |= jtiKey;
5596 [ + + + - ]: 148 : else if (v.val.string.len == 6 &&
2286 tgl@sss.pgh.pa.us 5597 : 40 : pg_strncasecmp(v.val.string.val, "string", 6) == 0)
2950 teodor@sigaev.ru 5598 : 40 : flags |= jtiString;
5599 [ + + + + ]: 128 : else if (v.val.string.len == 7 &&
5600 : 60 : pg_strncasecmp(v.val.string.val, "numeric", 7) == 0)
5601 : 40 : flags |= jtiNumeric;
5602 [ + + + - ]: 48 : else if (v.val.string.len == 7 &&
5603 : 20 : pg_strncasecmp(v.val.string.val, "boolean", 7) == 0)
5604 : 20 : flags |= jtiBool;
5605 : : else
5606 [ + - ]: 8 : ereport(ERROR,
5607 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5608 : : errmsg("wrong flag in flag array: \"%s\"",
5609 : : pnstrdup(v.val.string.val, v.val.string.len)),
5610 : : errhint("Possible values are: \"string\", \"numeric\", \"boolean\", \"key\", and \"all\".")));
5611 : : }
5612 : :
5613 : : /* expect end of array now */
5614 [ - + ]: 162 : if (type != WJB_END_ARRAY)
2950 teodor@sigaev.ru 5615 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of flag array");
5616 : :
5617 : : /* get final WJB_DONE and free iterator */
2942 tgl@sss.pgh.pa.us 5618 :CBC 162 : type = JsonbIteratorNext(&it, &v, false);
5619 [ - + ]: 162 : if (type != WJB_DONE)
2942 tgl@sss.pgh.pa.us 5620 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of flag array");
5621 : :
2950 teodor@sigaev.ru 5622 :CBC 162 : return flags;
5623 : : }
5624 : :
5625 : : /*
5626 : : * Iterate over jsonb values or elements, specified by flags, and pass them
5627 : : * together with an iteration state to a specified JsonIterateStringValuesAction.
5628 : : */
5629 : : void
5630 : 116 : iterate_jsonb_values(Jsonb *jb, uint32 flags, void *state,
5631 : : JsonIterateStringValuesAction action)
5632 : : {
5633 : : JsonbIterator *it;
5634 : : JsonbValue v;
5635 : : JsonbIteratorToken type;
5636 : :
3322 andrew@dunslane.net 5637 : 116 : it = JsonbIteratorInit(&jb->root);
5638 : :
5639 : : /*
5640 : : * Just recursively iterating over jsonb and call callback on all
5641 : : * corresponding elements
5642 : : */
5643 [ + + ]: 1328 : while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
5644 : : {
2950 teodor@sigaev.ru 5645 [ + + ]: 1212 : if (type == WJB_KEY)
5646 : : {
5647 [ + + ]: 461 : if (flags & jtiKey)
5648 : 120 : action(state, v.val.string.val, v.val.string.len);
5649 : :
5650 : 461 : continue;
5651 : : }
5652 [ + + + + ]: 751 : else if (!(type == WJB_VALUE || type == WJB_ELEM))
5653 : : {
5654 : : /* do not call callback for composite JsonbValue */
5655 : 288 : continue;
5656 : : }
5657 : :
5658 : : /* JsonbValue is a value of object or element of array */
2931 tgl@sss.pgh.pa.us 5659 [ + + + + ]: 463 : switch (v.type)
5660 : : {
2950 teodor@sigaev.ru 5661 : 120 : case jbvString:
5662 [ + + ]: 120 : if (flags & jtiString)
5663 : 85 : action(state, v.val.string.val, v.val.string.len);
5664 : 120 : break;
5665 : 140 : case jbvNumeric:
5666 [ + + ]: 140 : if (flags & jtiNumeric)
5667 : : {
5668 : : char *val;
5669 : :
5670 : 60 : val = DatumGetCString(DirectFunctionCall1(numeric_out,
5671 : : NumericGetDatum(v.val.numeric)));
5672 : :
5673 : 60 : action(state, val, strlen(val));
5674 : 60 : pfree(val);
5675 : : }
5676 : 140 : break;
5677 : 130 : case jbvBool:
5678 [ + + ]: 130 : if (flags & jtiBool)
5679 : : {
5680 [ + + ]: 40 : if (v.val.boolean)
5681 : 20 : action(state, "true", 4);
5682 : : else
5683 : 20 : action(state, "false", 5);
5684 : : }
5685 : 130 : break;
5686 : 73 : default:
5687 : : /* do not call callback for composite JsonbValue */
5688 : 73 : break;
5689 : : }
5690 : : }
3322 andrew@dunslane.net 5691 : 116 : }
5692 : :
5693 : : /*
5694 : : * Iterate over json values and elements, specified by flags, and pass them
5695 : : * together with an iteration state to a specified JsonIterateStringValuesAction.
5696 : : */
5697 : : void
2950 teodor@sigaev.ru 5698 : 116 : iterate_json_values(text *json, uint32 flags, void *action_state,
5699 : : JsonIterateStringValuesAction action)
5700 : : {
5701 : : JsonLexContext lex;
146 michael@paquier.xyz 5702 :GNC 116 : JsonSemAction *sem = palloc0_object(JsonSemAction);
5703 : 116 : IterateJsonStringValuesState *state = palloc0_object(IterateJsonStringValuesState);
5704 : :
943 alvherre@alvh.no-ip. 5705 :CBC 116 : state->lex = makeJsonLexContext(&lex, json, true);
3322 andrew@dunslane.net 5706 : 116 : state->action = action;
5707 : 116 : state->action_state = action_state;
2950 teodor@sigaev.ru 5708 : 116 : state->flags = flags;
5709 : :
523 peter@eisentraut.org 5710 : 116 : sem->semstate = state;
2950 teodor@sigaev.ru 5711 : 116 : sem->scalar = iterate_values_scalar;
5712 : 116 : sem->object_field_start = iterate_values_object_field_start;
5713 : :
943 alvherre@alvh.no-ip. 5714 : 116 : pg_parse_json_or_ereport(&lex, sem);
5715 : 116 : freeJsonLexContext(&lex);
3322 andrew@dunslane.net 5716 : 116 : }
5717 : :
5718 : : /*
5719 : : * An auxiliary function for iterate_json_values to invoke a specified
5720 : : * JsonIterateStringValuesAction for specified values.
5721 : : */
5722 : : static JsonParseErrorType
2950 teodor@sigaev.ru 5723 : 463 : iterate_values_scalar(void *state, char *token, JsonTokenType tokentype)
5724 : : {
3275 bruce@momjian.us 5725 : 463 : IterateJsonStringValuesState *_state = (IterateJsonStringValuesState *) state;
5726 : :
2931 tgl@sss.pgh.pa.us 5727 [ + + + + ]: 463 : switch (tokentype)
5728 : : {
2950 teodor@sigaev.ru 5729 : 120 : case JSON_TOKEN_STRING:
5730 [ + + ]: 120 : if (_state->flags & jtiString)
5731 : 85 : _state->action(_state->action_state, token, strlen(token));
5732 : 120 : break;
5733 : 140 : case JSON_TOKEN_NUMBER:
5734 [ + + ]: 140 : if (_state->flags & jtiNumeric)
5735 : 60 : _state->action(_state->action_state, token, strlen(token));
5736 : 140 : break;
5737 : 130 : case JSON_TOKEN_TRUE:
5738 : : case JSON_TOKEN_FALSE:
5739 [ + + ]: 130 : if (_state->flags & jtiBool)
5740 : 40 : _state->action(_state->action_state, token, strlen(token));
5741 : 130 : break;
5742 : 73 : default:
5743 : : /* do not call callback for any other token */
5744 : 73 : break;
5745 : : }
5746 : :
1241 tgl@sss.pgh.pa.us 5747 : 463 : return JSON_SUCCESS;
5748 : : }
5749 : :
5750 : : static JsonParseErrorType
2950 teodor@sigaev.ru 5751 : 461 : iterate_values_object_field_start(void *state, char *fname, bool isnull)
5752 : : {
5753 : 461 : IterateJsonStringValuesState *_state = (IterateJsonStringValuesState *) state;
5754 : :
5755 [ + + ]: 461 : if (_state->flags & jtiKey)
5756 : : {
2931 tgl@sss.pgh.pa.us 5757 : 120 : char *val = pstrdup(fname);
5758 : :
2950 teodor@sigaev.ru 5759 : 120 : _state->action(_state->action_state, val, strlen(val));
5760 : : }
5761 : :
1241 tgl@sss.pgh.pa.us 5762 : 461 : return JSON_SUCCESS;
5763 : : }
5764 : :
5765 : : /*
5766 : : * Iterate over a jsonb, and apply a specified JsonTransformStringValuesAction
5767 : : * to every string value or element. Any necessary context for a
5768 : : * JsonTransformStringValuesAction can be passed in the action_state variable.
5769 : : * Function returns a copy of an original jsonb object with transformed values.
5770 : : */
5771 : : Jsonb *
3322 andrew@dunslane.net 5772 : 30 : transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
5773 : : JsonTransformStringValuesAction transform_action)
5774 : : {
5775 : : JsonbIterator *it;
5776 : : JsonbValue v;
5777 : : JsonbIteratorToken type;
149 tgl@sss.pgh.pa.us 5778 :GNC 30 : JsonbInState st = {0};
5779 : : text *out;
3275 bruce@momjian.us 5780 :CBC 30 : bool is_scalar = false;
5781 : :
3322 andrew@dunslane.net 5782 : 30 : it = JsonbIteratorInit(&jsonb->root);
5783 : 30 : is_scalar = it->isScalar;
5784 : :
5785 [ + + ]: 336 : while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
5786 : : {
5787 [ + + + + : 306 : if ((type == WJB_VALUE || type == WJB_ELEM) && v.type == jbvString)
+ + ]
5788 : : {
5789 : 85 : out = transform_action(action_state, v.val.string.val, v.val.string.len);
5790 : : /* out is probably not toasted, but let's be sure */
1240 tgl@sss.pgh.pa.us 5791 : 85 : out = pg_detoast_datum_packed(out);
3322 andrew@dunslane.net 5792 [ - + ]: 85 : v.val.string.val = VARDATA_ANY(out);
5793 [ - + - - : 85 : v.val.string.len = VARSIZE_ANY_EXHDR(out);
- - - - -
+ ]
149 tgl@sss.pgh.pa.us 5794 [ + - ]:GNC 85 : pushJsonbValue(&st, type, type < WJB_BEGIN_ARRAY ? &v : NULL);
5795 : : }
5796 : : else
5797 : : {
5798 [ + + + - ]: 357 : pushJsonbValue(&st, type, (type == WJB_KEY ||
5799 [ + + ]: 136 : type == WJB_VALUE ||
5800 : : type == WJB_ELEM) ? &v : NULL);
5801 : : }
5802 : : }
5803 : :
5804 [ + + ]: 30 : if (st.result->type == jbvArray)
5805 : 8 : st.result->val.array.rawScalar = is_scalar;
5806 : :
5807 : 30 : return JsonbValueToJsonb(st.result);
5808 : : }
5809 : :
5810 : : /*
5811 : : * Iterate over a json, and apply a specified JsonTransformStringValuesAction
5812 : : * to every string value or element. Any necessary context for a
5813 : : * JsonTransformStringValuesAction can be passed in the action_state variable.
5814 : : * Function returns a Text Datum, which is a copy of an original json with
5815 : : * transformed values.
5816 : : */
5817 : : text *
3322 andrew@dunslane.net 5818 :CBC 30 : transform_json_string_values(text *json, void *action_state,
5819 : : JsonTransformStringValuesAction transform_action)
5820 : : {
5821 : : JsonLexContext lex;
146 michael@paquier.xyz 5822 :GNC 30 : JsonSemAction *sem = palloc0_object(JsonSemAction);
5823 : 30 : TransformJsonStringValuesState *state = palloc0_object(TransformJsonStringValuesState);
5824 : : StringInfoData strbuf;
5825 : :
180 drowley@postgresql.o 5826 : 30 : initStringInfo(&strbuf);
5827 : :
943 alvherre@alvh.no-ip. 5828 :CBC 30 : state->lex = makeJsonLexContext(&lex, json, true);
180 drowley@postgresql.o 5829 :GNC 30 : state->strval = &strbuf;
3322 andrew@dunslane.net 5830 :CBC 30 : state->action = transform_action;
5831 : 30 : state->action_state = action_state;
5832 : :
523 peter@eisentraut.org 5833 : 30 : sem->semstate = state;
3322 andrew@dunslane.net 5834 : 30 : sem->object_start = transform_string_values_object_start;
5835 : 30 : sem->object_end = transform_string_values_object_end;
5836 : 30 : sem->array_start = transform_string_values_array_start;
5837 : 30 : sem->array_end = transform_string_values_array_end;
5838 : 30 : sem->scalar = transform_string_values_scalar;
5839 : 30 : sem->array_element_start = transform_string_values_array_element_start;
5840 : 30 : sem->object_field_start = transform_string_values_object_field_start;
5841 : :
943 alvherre@alvh.no-ip. 5842 : 30 : pg_parse_json_or_ereport(&lex, sem);
5843 : 30 : freeJsonLexContext(&lex);
5844 : :
3322 andrew@dunslane.net 5845 : 30 : return cstring_to_text_with_len(state->strval->data, state->strval->len);
5846 : : }
5847 : :
5848 : : /*
5849 : : * Set of auxiliary functions for transform_json_string_values to invoke a
5850 : : * specified JsonTransformStringValuesAction for all values and left everything
5851 : : * else untouched.
5852 : : */
5853 : : static JsonParseErrorType
5854 : 40 : transform_string_values_object_start(void *state)
5855 : : {
5856 : 40 : TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
5857 : :
5858 [ - + ]: 40 : appendStringInfoCharMacro(_state->strval, '{');
5859 : :
1241 tgl@sss.pgh.pa.us 5860 : 40 : return JSON_SUCCESS;
5861 : : }
5862 : :
5863 : : static JsonParseErrorType
3322 andrew@dunslane.net 5864 : 40 : transform_string_values_object_end(void *state)
5865 : : {
5866 : 40 : TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
5867 : :
5868 [ - + ]: 40 : appendStringInfoCharMacro(_state->strval, '}');
5869 : :
1241 tgl@sss.pgh.pa.us 5870 : 40 : return JSON_SUCCESS;
5871 : : }
5872 : :
5873 : : static JsonParseErrorType
3322 andrew@dunslane.net 5874 : 22 : transform_string_values_array_start(void *state)
5875 : : {
5876 : 22 : TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
5877 : :
5878 [ - + ]: 22 : appendStringInfoCharMacro(_state->strval, '[');
5879 : :
1241 tgl@sss.pgh.pa.us 5880 : 22 : return JSON_SUCCESS;
5881 : : }
5882 : :
5883 : : static JsonParseErrorType
3322 andrew@dunslane.net 5884 : 22 : transform_string_values_array_end(void *state)
5885 : : {
5886 : 22 : TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
5887 : :
5888 [ - + ]: 22 : appendStringInfoCharMacro(_state->strval, ']');
5889 : :
1241 tgl@sss.pgh.pa.us 5890 : 22 : return JSON_SUCCESS;
5891 : : }
5892 : :
5893 : : static JsonParseErrorType
3322 andrew@dunslane.net 5894 : 85 : transform_string_values_object_field_start(void *state, char *fname, bool isnull)
5895 : : {
5896 : 85 : TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
5897 : :
5898 [ + + ]: 85 : if (_state->strval->data[_state->strval->len - 1] != '{')
5899 [ - + ]: 49 : appendStringInfoCharMacro(_state->strval, ',');
5900 : :
5901 : : /*
5902 : : * Unfortunately we don't have the quoted and escaped string any more, so
5903 : : * we have to re-escape it.
5904 : : */
5905 : 85 : escape_json(_state->strval, fname);
5906 [ - + ]: 85 : appendStringInfoCharMacro(_state->strval, ':');
5907 : :
1241 tgl@sss.pgh.pa.us 5908 : 85 : return JSON_SUCCESS;
5909 : : }
5910 : :
5911 : : static JsonParseErrorType
3322 andrew@dunslane.net 5912 : 36 : transform_string_values_array_element_start(void *state, bool isnull)
5913 : : {
5914 : 36 : TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
5915 : :
5916 [ + + ]: 36 : if (_state->strval->data[_state->strval->len - 1] != '[')
5917 [ - + ]: 18 : appendStringInfoCharMacro(_state->strval, ',');
5918 : :
1241 tgl@sss.pgh.pa.us 5919 : 36 : return JSON_SUCCESS;
5920 : : }
5921 : :
5922 : : static JsonParseErrorType
3322 andrew@dunslane.net 5923 : 89 : transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype)
5924 : : {
5925 : 89 : TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
5926 : :
5927 [ + + ]: 89 : if (tokentype == JSON_TOKEN_STRING)
5928 : : {
3162 peter_e@gmx.net 5929 : 85 : text *out = _state->action(_state->action_state, token, strlen(token));
5930 : :
647 drowley@postgresql.o 5931 : 85 : escape_json_text(_state->strval, out);
5932 : : }
5933 : : else
3322 andrew@dunslane.net 5934 : 4 : appendStringInfoString(_state->strval, token);
5935 : :
1241 tgl@sss.pgh.pa.us 5936 : 89 : return JSON_SUCCESS;
5937 : : }
5938 : :
5939 : : JsonTokenType
1131 alvherre@alvh.no-ip. 5940 : 447 : json_get_first_token(text *json, bool throw_error)
5941 : : {
5942 : : JsonLexContext lex;
5943 : : JsonParseErrorType result;
5944 : :
943 5945 : 447 : makeJsonLexContext(&lex, json, false);
5946 : :
5947 : : /* Lex exactly one token from the input and check its type. */
5948 : 447 : result = json_lex(&lex);
5949 : :
1131 5950 [ + + ]: 447 : if (result == JSON_SUCCESS)
943 5951 : 435 : return lex.token_type;
5952 : :
1131 5953 [ - + ]: 12 : if (throw_error)
943 alvherre@alvh.no-ip. 5954 :UBC 0 : json_errsave_error(result, &lex, NULL);
5955 : :
1131 alvherre@alvh.no-ip. 5956 :CBC 12 : return JSON_TOKEN_INVALID; /* invalid json */
5957 : : }
5958 : :
5959 : : /*
5960 : : * Determine how we want to print values of a given type in datum_to_json(b).
5961 : : *
5962 : : * Given the datatype OID, return its JsonTypeCategory, as well as the type's
5963 : : * output function OID. If the returned category is JSONTYPE_CAST, we return
5964 : : * the OID of the type->JSON cast function instead.
5965 : : */
5966 : : void
1020 amitlan@postgresql.o 5967 : 6730 : json_categorize_type(Oid typoid, bool is_jsonb,
5968 : : JsonTypeCategory *tcategory, Oid *outfuncoid)
5969 : : {
5970 : : bool typisvarlena;
5971 : :
5972 : : /* Look through any domain */
5973 : 6730 : typoid = getBaseType(typoid);
5974 : :
5975 : 6730 : *outfuncoid = InvalidOid;
5976 : :
5977 [ + + + + : 6730 : switch (typoid)
+ + + + ]
5978 : : {
5979 : 81 : case BOOLOID:
5980 : 81 : *outfuncoid = F_BOOLOUT;
5981 : 81 : *tcategory = JSONTYPE_BOOL;
5982 : 81 : break;
5983 : :
5984 : 2534 : case INT2OID:
5985 : : case INT4OID:
5986 : : case INT8OID:
5987 : : case FLOAT4OID:
5988 : : case FLOAT8OID:
5989 : : case NUMERICOID:
5990 : 2534 : getTypeOutputInfo(typoid, outfuncoid, &typisvarlena);
5991 : 2534 : *tcategory = JSONTYPE_NUMERIC;
5992 : 2534 : break;
5993 : :
5994 : 76 : case DATEOID:
5995 : 76 : *outfuncoid = F_DATE_OUT;
5996 : 76 : *tcategory = JSONTYPE_DATE;
5997 : 76 : break;
5998 : :
5999 : 77 : case TIMESTAMPOID:
6000 : 77 : *outfuncoid = F_TIMESTAMP_OUT;
6001 : 77 : *tcategory = JSONTYPE_TIMESTAMP;
6002 : 77 : break;
6003 : :
6004 : 116 : case TIMESTAMPTZOID:
6005 : 116 : *outfuncoid = F_TIMESTAMPTZ_OUT;
6006 : 116 : *tcategory = JSONTYPE_TIMESTAMPTZ;
6007 : 116 : break;
6008 : :
6009 : 170 : case JSONOID:
6010 : 170 : getTypeOutputInfo(typoid, outfuncoid, &typisvarlena);
6011 : 170 : *tcategory = JSONTYPE_JSON;
6012 : 170 : break;
6013 : :
6014 : 342 : case JSONBOID:
6015 : 342 : getTypeOutputInfo(typoid, outfuncoid, &typisvarlena);
6016 [ + + ]: 342 : *tcategory = is_jsonb ? JSONTYPE_JSONB : JSONTYPE_JSON;
6017 : 342 : break;
6018 : :
6019 : 3334 : default:
6020 : : /* Check for arrays and composites */
6021 [ + + + + ]: 3334 : if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID
6022 [ + - - + ]: 2990 : || typoid == ANYCOMPATIBLEARRAYOID || typoid == RECORDARRAYOID)
6023 : : {
6024 : 344 : *outfuncoid = F_ARRAY_OUT;
6025 : 344 : *tcategory = JSONTYPE_ARRAY;
6026 : : }
6027 [ + + ]: 2990 : else if (type_is_rowtype(typoid)) /* includes RECORDOID */
6028 : : {
6029 : 226 : *outfuncoid = F_RECORD_OUT;
6030 : 226 : *tcategory = JSONTYPE_COMPOSITE;
6031 : : }
6032 : : else
6033 : : {
6034 : : /*
6035 : : * It's probably the general case. But let's look for a cast
6036 : : * to json (note: not to jsonb even if is_jsonb is true), if
6037 : : * it's not built-in.
6038 : : */
6039 : 2764 : *tcategory = JSONTYPE_OTHER;
6040 [ + + ]: 2764 : if (typoid >= FirstNormalObjectId)
6041 : : {
6042 : : Oid castfunc;
6043 : : CoercionPathType ctype;
6044 : :
6045 : 6 : ctype = find_coercion_pathway(JSONOID, typoid,
6046 : : COERCION_EXPLICIT,
6047 : : &castfunc);
6048 [ + - + - ]: 6 : if (ctype == COERCION_PATH_FUNC && OidIsValid(castfunc))
6049 : : {
6050 : 6 : *outfuncoid = castfunc;
6051 : 6 : *tcategory = JSONTYPE_CAST;
6052 : : }
6053 : : else
6054 : : {
6055 : : /* non builtin type with no cast */
1020 amitlan@postgresql.o 6056 :UBC 0 : getTypeOutputInfo(typoid, outfuncoid, &typisvarlena);
6057 : : }
6058 : : }
6059 : : else
6060 : : {
6061 : : /* any other builtin type */
1020 amitlan@postgresql.o 6062 :CBC 2758 : getTypeOutputInfo(typoid, outfuncoid, &typisvarlena);
6063 : : }
6064 : : }
6065 : 3334 : break;
6066 : : }
6067 : 6730 : }
6068 : :
6069 : : /*
6070 : : * Check whether a type conversion to JSON or JSONB involves any mutable
6071 : : * functions. This recurses into container types (arrays, composites,
6072 : : * ranges, multiranges, domains) to check their element/sub types.
6073 : : *
6074 : : * The caller must initialize *has_mutable to false before calling.
6075 : : * If any mutable function is found, *has_mutable is set to true.
6076 : : */
6077 : : void
56 andrew@dunslane.net 6078 :GNC 592 : json_check_mutability(Oid typoid, bool is_jsonb, bool *has_mutable)
6079 : : {
6080 : 592 : char att_typtype = get_typtype(typoid);
6081 : : JsonTypeCategory tcategory;
6082 : : Oid outfuncoid;
6083 : :
6084 : : /* since this function recurses, it could be driven to stack overflow */
6085 : 592 : check_stack_depth();
6086 : :
6087 [ - + ]: 592 : Assert(has_mutable != NULL);
6088 : :
6089 [ - + ]: 592 : if (*has_mutable)
6090 : 280 : return;
6091 : :
6092 [ + + ]: 592 : if (att_typtype == TYPTYPE_DOMAIN)
6093 : : {
6094 : 64 : json_check_mutability(getBaseType(typoid), is_jsonb, has_mutable);
6095 : 64 : return;
6096 : : }
6097 [ + + ]: 528 : else if (att_typtype == TYPTYPE_COMPOSITE)
6098 : : {
6099 : : /*
6100 : : * For a composite type, recurse into its attributes. Use the
6101 : : * typcache to avoid opening the relation directly.
6102 : : */
6103 : 48 : TupleDesc tupdesc = lookup_rowtype_tupdesc(typoid, -1);
6104 : :
6105 [ + - ]: 96 : for (int i = 0; i < tupdesc->natts; i++)
6106 : : {
6107 : 96 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
6108 : :
6109 [ - + ]: 96 : if (attr->attisdropped)
56 andrew@dunslane.net 6110 :UNC 0 : continue;
6111 : :
56 andrew@dunslane.net 6112 :GNC 96 : json_check_mutability(attr->atttypid, is_jsonb, has_mutable);
6113 [ + + ]: 96 : if (*has_mutable)
6114 : 48 : break;
6115 : : }
6116 [ + - ]: 48 : ReleaseTupleDesc(tupdesc);
6117 : 48 : return;
6118 : : }
6119 [ + + ]: 480 : else if (att_typtype == TYPTYPE_RANGE)
6120 : : {
6121 : 64 : json_check_mutability(get_range_subtype(typoid), is_jsonb,
6122 : : has_mutable);
6123 : 64 : return;
6124 : : }
6125 [ + + ]: 416 : else if (att_typtype == TYPTYPE_MULTIRANGE)
6126 : : {
6127 : 24 : json_check_mutability(get_multirange_range(typoid), is_jsonb,
6128 : : has_mutable);
6129 : 24 : return;
6130 : : }
6131 : : else
6132 : : {
6133 : 392 : Oid att_typelem = get_element_type(typoid);
6134 : :
6135 [ + + ]: 392 : if (OidIsValid(att_typelem))
6136 : : {
6137 : : /* recurse into array element type */
6138 : 80 : json_check_mutability(att_typelem, is_jsonb, has_mutable);
6139 : 80 : return;
6140 : : }
6141 : : }
6142 : :
6143 : 312 : json_categorize_type(typoid, is_jsonb, &tcategory, &outfuncoid);
6144 : :
6145 [ + + - + : 312 : switch (tcategory)
- ]
6146 : : {
6147 : 64 : case JSONTYPE_NULL:
6148 : : case JSONTYPE_BOOL:
6149 : : case JSONTYPE_NUMERIC:
6150 : 64 : break;
6151 : :
6152 : 144 : case JSONTYPE_DATE:
6153 : : case JSONTYPE_TIMESTAMP:
6154 : : case JSONTYPE_TIMESTAMPTZ:
6155 : 144 : *has_mutable = true;
6156 : 144 : break;
6157 : :
56 andrew@dunslane.net 6158 :UNC 0 : case JSONTYPE_JSON:
6159 : : case JSONTYPE_JSONB:
6160 : : case JSONTYPE_ARRAY:
6161 : : case JSONTYPE_COMPOSITE:
6162 : 0 : break;
6163 : :
56 andrew@dunslane.net 6164 :GNC 104 : case JSONTYPE_CAST:
6165 : : case JSONTYPE_OTHER:
6166 [ - + ]: 104 : if (func_volatile(outfuncoid) != PROVOLATILE_IMMUTABLE)
56 andrew@dunslane.net 6167 :UNC 0 : *has_mutable = true;
56 andrew@dunslane.net 6168 :GNC 104 : break;
6169 : : }
6170 : : }
|