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