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