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