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