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