Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * jsonpath_exec.c
4 : : * Routines for SQL/JSON path execution.
5 : : *
6 : : * Jsonpath is executed in the global context stored in JsonPathExecContext,
7 : : * which is passed to almost every function involved into execution. Entry
8 : : * point for jsonpath execution is executeJsonPath() function, which
9 : : * initializes execution context including initial JsonPathItem and JsonbValue,
10 : : * flags, stack for calculation of @ in filters.
11 : : *
12 : : * The result of jsonpath query execution is enum JsonPathExecResult and
13 : : * if succeeded sequence of JsonbValue, written to JsonValueList *found, which
14 : : * is passed through the jsonpath items. When found == NULL, we're inside
15 : : * exists-query and we're interested only in whether result is empty. In this
16 : : * case execution is stopped once first result item is found, and the only
17 : : * execution result is JsonPathExecResult. The values of JsonPathExecResult
18 : : * are following:
19 : : * - jperOk -- result sequence is not empty
20 : : * - jperNotFound -- result sequence is empty
21 : : * - jperError -- error occurred during execution
22 : : *
23 : : * Jsonpath is executed recursively (see executeItem()) starting form the
24 : : * first path item (which in turn might be, for instance, an arithmetic
25 : : * expression evaluated separately). On each step single JsonbValue obtained
26 : : * from previous path item is processed. The result of processing is a
27 : : * sequence of JsonbValue (probably empty), which is passed to the next path
28 : : * item one by one. When there is no next path item, then JsonbValue is added
29 : : * to the 'found' list. When found == NULL, then execution functions just
30 : : * return jperOk (see executeNextItem()).
31 : : *
32 : : * Many of jsonpath operations require automatic unwrapping of arrays in lax
33 : : * mode. So, if input value is array, then corresponding operation is
34 : : * processed not on array itself, but on all of its members one by one.
35 : : * executeItemOptUnwrapTarget() function have 'unwrap' argument, which indicates
36 : : * whether unwrapping of array is needed. When unwrap == true, each of array
37 : : * members is passed to executeItemOptUnwrapTarget() again but with unwrap == false
38 : : * in order to avoid subsequent array unwrapping.
39 : : *
40 : : * All boolean expressions (predicates) are evaluated by executeBoolItem()
41 : : * function, which returns tri-state JsonPathBool. When error is occurred
42 : : * during predicate execution, it returns jpbUnknown. According to standard
43 : : * predicates can be only inside filters. But we support their usage as
44 : : * jsonpath expression. This helps us to implement @@ operator. In this case
45 : : * resulting JsonPathBool is transformed into jsonb bool or null.
46 : : *
47 : : * Arithmetic and boolean expression are evaluated recursively from expression
48 : : * tree top down to the leaves. Therefore, for binary arithmetic expressions
49 : : * we calculate operands first. Then we check that results are numeric
50 : : * singleton lists, calculate the result and pass it to the next path item.
51 : : *
52 : : * Copyright (c) 2019-2026, PostgreSQL Global Development Group
53 : : *
54 : : * IDENTIFICATION
55 : : * src/backend/utils/adt/jsonpath_exec.c
56 : : *
57 : : *-------------------------------------------------------------------------
58 : : */
59 : :
60 : : #include "postgres.h"
61 : :
62 : : #include "catalog/pg_collation.h"
63 : : #include "catalog/pg_type.h"
64 : : #include "funcapi.h"
65 : : #include "miscadmin.h"
66 : : #include "nodes/miscnodes.h"
67 : : #include "nodes/nodeFuncs.h"
68 : : #include "regex/regex.h"
69 : : #include "utils/builtins.h"
70 : : #include "utils/date.h"
71 : : #include "utils/datetime.h"
72 : : #include "utils/float.h"
73 : : #include "utils/formatting.h"
74 : : #include "utils/json.h"
75 : : #include "utils/jsonpath.h"
76 : : #include "utils/memutils.h"
77 : : #include "utils/timestamp.h"
78 : :
79 : : /*
80 : : * Represents "base object" and its "id" for .keyvalue() evaluation.
81 : : */
82 : : typedef struct JsonBaseObjectInfo
83 : : {
84 : : JsonbContainer *jbc;
85 : : int id;
86 : : } JsonBaseObjectInfo;
87 : :
88 : : /* Callbacks for executeJsonPath() */
89 : : typedef JsonbValue *(*JsonPathGetVarCallback) (void *vars, char *varName, int varNameLen,
90 : : JsonbValue *baseObject, int *baseObjectId);
91 : : typedef int (*JsonPathCountVarsCallback) (void *vars);
92 : :
93 : : /*
94 : : * Context of jsonpath execution.
95 : : */
96 : : typedef struct JsonPathExecContext
97 : : {
98 : : void *vars; /* variables to substitute into jsonpath */
99 : : JsonPathGetVarCallback getVar; /* callback to extract a given variable
100 : : * from 'vars' */
101 : : JsonbValue *root; /* for $ evaluation */
102 : : JsonbValue *current; /* for @ evaluation */
103 : : JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue()
104 : : * evaluation */
105 : : int lastGeneratedObjectId; /* "id" counter for .keyvalue()
106 : : * evaluation */
107 : : int innermostArraySize; /* for LAST array index evaluation */
108 : : bool laxMode; /* true for "lax" mode, false for "strict"
109 : : * mode */
110 : : bool ignoreStructuralErrors; /* with "true" structural errors such
111 : : * as absence of required json item or
112 : : * unexpected json item type are
113 : : * ignored */
114 : : bool throwErrors; /* with "false" all suppressible errors are
115 : : * suppressed */
116 : : bool useTz;
117 : : } JsonPathExecContext;
118 : :
119 : : /* Context for LIKE_REGEX execution. */
120 : : typedef struct JsonLikeRegexContext
121 : : {
122 : : text *regex;
123 : : int cflags;
124 : : } JsonLikeRegexContext;
125 : :
126 : : /* Result of jsonpath predicate evaluation */
127 : : typedef enum JsonPathBool
128 : : {
129 : : jpbFalse = 0,
130 : : jpbTrue = 1,
131 : : jpbUnknown = 2
132 : : } JsonPathBool;
133 : :
134 : : /* Result of jsonpath expression evaluation */
135 : : typedef enum JsonPathExecResult
136 : : {
137 : : jperOk = 0,
138 : : jperNotFound = 1,
139 : : jperError = 2
140 : : } JsonPathExecResult;
141 : :
142 : : #define jperIsError(jper) ((jper) == jperError)
143 : :
144 : : /*
145 : : * List (or really array) of JsonbValues. This is the output representation
146 : : * of jsonpath evaluation.
147 : : *
148 : : * The initial or "base" chunk of a list is typically a local variable in
149 : : * a calling function. If we need more entries than will fit in the base
150 : : * chunk, we palloc more chunks. For notational simplicity, those are also
151 : : * treated as being of type JsonValueList, although they will have items[]
152 : : * arrays that are larger than BASE_JVL_ITEMS.
153 : : *
154 : : * Callers *must* initialize the base chunk with JsonValueListInit().
155 : : * Typically they should free any extra chunks when done, using
156 : : * JsonValueListClear(), although some top-level functions skip that
157 : : * on the assumption that the caller's context will be reset soon.
158 : : *
159 : : * Note that most types of JsonbValue include pointers to external data, which
160 : : * will not be managed by the JsonValueList functions. We expect that such
161 : : * data is part of the input to the jsonpath operation, and the caller will
162 : : * see to it that it holds still for the duration of the operation.
163 : : *
164 : : * Most lists are short, though some can be quite long. So we set
165 : : * BASE_JVL_ITEMS small to conserve stack space, but grow the extra
166 : : * chunks aggressively.
167 : : */
168 : : #define BASE_JVL_ITEMS 2 /* number of items a base chunk holds */
169 : : #define MIN_EXTRA_JVL_ITEMS 16 /* min number of items an extra chunk holds */
170 : :
171 : : typedef struct JsonValueList
172 : : {
173 : : int nitems; /* number of items stored in this chunk */
174 : : int maxitems; /* allocated length of items[] */
175 : : struct JsonValueList *next; /* => next chunk, if any */
176 : : struct JsonValueList *last; /* => last chunk (only valid in base chunk) */
177 : : union
178 : : {
179 : : /*
180 : : * To satisfy -fstrict-flex-arrays=1, we need one union member that
181 : : * makes space for the base chunk and one member that is effectively
182 : : * declared as a flexible-array member, otherwise you'd get
183 : : * -fsanitize=undefined errors. Note that we can't declare items as
184 : : * [FLEXIBLE_ARRAY_MEMBER], because JsonValueList is used in a
185 : : * non-last field of JsonTablePlanState, and that's not allowed with
186 : : * flexible-array members. But using [1] works as long as we don't go
187 : : * higher than -fstrict-flex-arrays=1.
188 : : */
189 : : JsonbValue base_items[BASE_JVL_ITEMS];
190 : : JsonbValue items[1];
191 : : };
192 : : } JsonValueList;
193 : :
194 : : /* State data for iterating through a JsonValueList */
195 : : typedef struct JsonValueListIterator
196 : : {
197 : : JsonValueList *chunk; /* current chunk of list */
198 : : int nextitem; /* index of next value to return in chunk */
199 : : } JsonValueListIterator;
200 : :
201 : : /* Structures for JSON_TABLE execution */
202 : :
203 : : /*
204 : : * Struct holding the result of jsonpath evaluation, to be used as source row
205 : : * for JsonTableGetValue() which in turn computes the values of individual
206 : : * JSON_TABLE columns.
207 : : */
208 : : typedef struct JsonTablePlanRowSource
209 : : {
210 : : Datum value;
211 : : bool isnull;
212 : : } JsonTablePlanRowSource;
213 : :
214 : : /*
215 : : * State of evaluation of row pattern derived by applying jsonpath given in
216 : : * a JsonTablePlan to an input document given in the parent TableFunc.
217 : : */
218 : : typedef struct JsonTablePlanState
219 : : {
220 : : /* Original plan */
221 : : JsonTablePlan *plan;
222 : :
223 : : /* The following fields are only valid for JsonTablePathScan plans */
224 : :
225 : : /* jsonpath to evaluate against the input doc to get the row pattern */
226 : : JsonPath *path;
227 : :
228 : : /*
229 : : * Memory context to use when evaluating the row pattern from the jsonpath
230 : : */
231 : : MemoryContext mcxt;
232 : :
233 : : /* PASSING arguments passed to jsonpath executor */
234 : : List *args;
235 : :
236 : : /* List and iterator of jsonpath result values */
237 : : JsonValueList found;
238 : : JsonValueListIterator iter;
239 : :
240 : : /* Currently selected row for JsonTableGetValue() to use */
241 : : JsonTablePlanRowSource current;
242 : :
243 : : /* Counter for ORDINAL columns */
244 : : int ordinal;
245 : :
246 : : /* Nested plan, if any */
247 : : struct JsonTablePlanState *nested;
248 : :
249 : : /* Left sibling, if any */
250 : : struct JsonTablePlanState *left;
251 : :
252 : : /* Right sibling, if any */
253 : : struct JsonTablePlanState *right;
254 : :
255 : : /* Parent plan, if this is a nested plan */
256 : : struct JsonTablePlanState *parent;
257 : :
258 : : /* Join type */
259 : : bool cross;
260 : : bool outerJoin;
261 : : /* Planning control fields */
262 : : bool advanceNested;
263 : : bool advanceRight;
264 : : bool reset;
265 : : } JsonTablePlanState;
266 : :
267 : : /* Random number to identify JsonTableExecContext for sanity checking */
268 : : #define JSON_TABLE_EXEC_CONTEXT_MAGIC 418352867
269 : :
270 : : typedef struct JsonTableExecContext
271 : : {
272 : : int magic;
273 : :
274 : : /* State of the plan providing a row evaluated from "root" jsonpath */
275 : : JsonTablePlanState *rootplanstate;
276 : :
277 : : /*
278 : : * Per-column JsonTablePlanStates for all columns including the nested
279 : : * ones.
280 : : */
281 : : JsonTablePlanState **colplanstates;
282 : : } JsonTableExecContext;
283 : :
284 : : /* strict/lax flags is decomposed into four [un]wrap/error flags */
285 : : #define jspStrictAbsenceOfErrors(cxt) (!(cxt)->laxMode)
286 : : #define jspAutoUnwrap(cxt) ((cxt)->laxMode)
287 : : #define jspAutoWrap(cxt) ((cxt)->laxMode)
288 : : #define jspIgnoreStructuralErrors(cxt) ((cxt)->ignoreStructuralErrors)
289 : : #define jspThrowErrors(cxt) ((cxt)->throwErrors)
290 : :
291 : : /* Convenience macro: return or throw error depending on context */
292 : : #define RETURN_ERROR(throw_error) \
293 : : do { \
294 : : if (jspThrowErrors(cxt)) \
295 : : throw_error; \
296 : : else \
297 : : return jperError; \
298 : : } while (0)
299 : :
300 : : typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
301 : : JsonbValue *larg,
302 : : JsonbValue *rarg,
303 : : void *param);
304 : : typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2,
305 : : Node *escontext);
306 : :
307 : : static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars,
308 : : JsonPathGetVarCallback getVar,
309 : : JsonPathCountVarsCallback countVars,
310 : : Jsonb *json, bool throwErrors,
311 : : JsonValueList *result, bool useTz);
312 : : static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
313 : : JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
314 : : static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
315 : : JsonPathItem *jsp, JsonbValue *jb,
316 : : JsonValueList *found, bool unwrap);
317 : : static JsonPathExecResult executeItemUnwrapTargetArray(JsonPathExecContext *cxt,
318 : : JsonPathItem *jsp, JsonbValue *jb,
319 : : JsonValueList *found, bool unwrapElements);
320 : : static JsonPathExecResult executeNextItem(JsonPathExecContext *cxt,
321 : : JsonPathItem *cur, JsonPathItem *next,
322 : : JsonbValue *v, JsonValueList *found);
323 : : static JsonPathExecResult executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
324 : : bool unwrap, JsonValueList *found);
325 : : static JsonPathExecResult executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt, JsonPathItem *jsp,
326 : : JsonbValue *jb, bool unwrap, JsonValueList *found);
327 : : static JsonPathBool executeBoolItem(JsonPathExecContext *cxt,
328 : : JsonPathItem *jsp, JsonbValue *jb, bool canHaveNext);
329 : : static JsonPathBool executeNestedBoolItem(JsonPathExecContext *cxt,
330 : : JsonPathItem *jsp, JsonbValue *jb);
331 : : static JsonPathExecResult executeAnyItem(JsonPathExecContext *cxt,
332 : : JsonPathItem *jsp, JsonbContainer *jbc, JsonValueList *found,
333 : : uint32 level, uint32 first, uint32 last,
334 : : bool ignoreStructuralErrors, bool unwrapNext);
335 : : static JsonPathBool executePredicate(JsonPathExecContext *cxt,
336 : : JsonPathItem *pred, JsonPathItem *larg, JsonPathItem *rarg,
337 : : JsonbValue *jb, bool unwrapRightArg,
338 : : JsonPathPredicateCallback exec, void *param);
339 : : static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt,
340 : : JsonPathItem *jsp, JsonbValue *jb,
341 : : BinaryArithmFunc func, JsonValueList *found);
342 : : static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt,
343 : : JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
344 : : JsonValueList *found);
345 : : static JsonPathBool executeStartsWith(JsonPathItem *jsp,
346 : : JsonbValue *whole, JsonbValue *initial, void *param);
347 : : static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
348 : : JsonbValue *rarg, void *param);
349 : : static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
350 : : JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
351 : : JsonValueList *found);
352 : : static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
353 : : JsonbValue *jb, JsonValueList *found);
354 : : static JsonPathExecResult executeStringInternalMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
355 : : JsonbValue *jb, JsonValueList *found);
356 : : static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
357 : : JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
358 : : static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
359 : : JsonPathItem *jsp, JsonValueList *found, JsonPathBool res);
360 : : static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
361 : : JsonbValue *value);
362 : : static JsonbValue *GetJsonPathVar(void *cxt, char *varName, int varNameLen,
363 : : JsonbValue *baseObject, int *baseObjectId);
364 : : static int CountJsonPathVars(void *cxt);
365 : : static void JsonItemFromDatum(Datum val, Oid typid, int32 typmod,
366 : : JsonbValue *res);
367 : : static void JsonbValueInitNumericDatum(JsonbValue *jbv, Datum num);
368 : : static void getJsonPathVariable(JsonPathExecContext *cxt,
369 : : JsonPathItem *variable, JsonbValue *value);
370 : : static int countVariablesFromJsonb(void *varsJsonb);
371 : : static JsonbValue *getJsonPathVariableFromJsonb(void *varsJsonb, char *varName,
372 : : int varNameLength,
373 : : JsonbValue *baseObject,
374 : : int *baseObjectId);
375 : : static int JsonbArraySize(JsonbValue *jb);
376 : : static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
377 : : JsonbValue *rv, void *p);
378 : : static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
379 : : bool useTz);
380 : : static int compareNumeric(Numeric a, Numeric b);
381 : : static JsonbValue *copyJsonbValue(JsonbValue *src);
382 : : static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
383 : : JsonPathItem *jsp, JsonbValue *jb, int32 *index);
384 : : static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt,
385 : : JsonbValue *jbv, int32 id);
386 : : static void JsonValueListInit(JsonValueList *jvl);
387 : : static void JsonValueListClear(JsonValueList *jvl);
388 : : static void JsonValueListAppend(JsonValueList *jvl, const JsonbValue *jbv);
389 : : static bool JsonValueListIsEmpty(const JsonValueList *jvl);
390 : : static bool JsonValueListIsSingleton(const JsonValueList *jvl);
391 : : static bool JsonValueListHasMultipleItems(const JsonValueList *jvl);
392 : : static JsonbValue *JsonValueListHead(JsonValueList *jvl);
393 : : static void JsonValueListInitIterator(JsonValueList *jvl,
394 : : JsonValueListIterator *it);
395 : : static JsonbValue *JsonValueListNext(JsonValueListIterator *it);
396 : : static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
397 : : static int JsonbType(JsonbValue *jb);
398 : : static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
399 : : static JsonbValue *wrapItemsInArray(JsonValueList *items);
400 : : static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
401 : : bool useTz, bool *cast_error);
402 : : static void checkTimezoneIsUsedForCast(bool useTz, const char *type1,
403 : : const char *type2);
404 : :
405 : : static void JsonTableInitOpaque(TableFuncScanState *tscanstate, int natts);
406 : : static JsonTablePlanState *JsonTableInitPlan(JsonTableExecContext *cxt,
407 : : JsonTablePlan *plan,
408 : : JsonTablePlanState *parentstate,
409 : : List *args,
410 : : MemoryContext mcxt);
411 : : static void JsonTableSetDocument(TableFuncScanState *state, Datum value);
412 : : static void JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item);
413 : : static void JsonTableRescan(JsonTablePlanState *planstate);
414 : : static bool JsonTableFetchRow(TableFuncScanState *state);
415 : : static Datum JsonTableGetValue(TableFuncScanState *state, int colnum,
416 : : Oid typid, int32 typmod, bool *isnull);
417 : : static void JsonTableDestroyOpaque(TableFuncScanState *state);
418 : : static bool JsonTablePlanScanNextRow(JsonTablePlanState *planstate);
419 : : static void JsonTableResetNestedPlan(JsonTablePlanState *planstate);
420 : : static bool JsonTablePlanNextRow(JsonTablePlanState *planstate);
421 : :
422 : : const TableFuncRoutine JsonbTableRoutine =
423 : : {
424 : : .InitOpaque = JsonTableInitOpaque,
425 : : .SetDocument = JsonTableSetDocument,
426 : : .SetNamespace = NULL,
427 : : .SetRowFilter = NULL,
428 : : .SetColumnFilter = NULL,
429 : : .FetchRow = JsonTableFetchRow,
430 : : .GetValue = JsonTableGetValue,
431 : : .DestroyOpaque = JsonTableDestroyOpaque
432 : : };
433 : :
434 : : /****************** User interface to JsonPath executor ********************/
435 : :
436 : : /*
437 : : * jsonb_path_exists
438 : : * Returns true if jsonpath returns at least one item for the specified
439 : : * jsonb value. This function and jsonb_path_match() are used to
440 : : * implement @? and @@ operators, which in turn are intended to have an
441 : : * index support. Thus, it's desirable to make it easier to achieve
442 : : * consistency between index scan results and sequential scan results.
443 : : * So, we throw as few errors as possible. Regarding this function,
444 : : * such behavior also matches behavior of JSON_EXISTS() clause of
445 : : * SQL/JSON. Regarding jsonb_path_match(), this function doesn't have
446 : : * an analogy in SQL/JSON, so we define its behavior on our own.
447 : : */
448 : : static Datum
2598 akorotkov@postgresql 449 :CBC 57483 : jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
450 : : {
2791 451 : 57483 : Jsonb *jb = PG_GETARG_JSONB_P(0);
452 : 57483 : JsonPath *jp = PG_GETARG_JSONPATH_P(1);
453 : : JsonPathExecResult res;
454 : 57483 : Jsonb *vars = NULL;
455 : 57483 : bool silent = true;
456 : :
457 [ + + ]: 57483 : if (PG_NARGS() == 4)
458 : : {
459 : 43 : vars = PG_GETARG_JSONB_P(2);
460 : 43 : silent = PG_GETARG_BOOL(3);
461 : : }
462 : :
1016 amitlan@postgresql.o 463 : 57483 : res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
464 : : countVariablesFromJsonb,
465 : 57483 : jb, !silent, NULL, tz);
466 : :
2791 akorotkov@postgresql 467 [ + + ]: 57475 : PG_FREE_IF_COPY(jb, 0);
468 [ - + ]: 57475 : PG_FREE_IF_COPY(jp, 1);
469 : :
470 [ + + ]: 57475 : if (jperIsError(res))
471 : 55 : PG_RETURN_NULL();
472 : :
473 : 57420 : PG_RETURN_BOOL(res == jperOk);
474 : : }
475 : :
476 : : Datum
2598 477 : 43 : jsonb_path_exists(PG_FUNCTION_ARGS)
478 : : {
479 : 43 : return jsonb_path_exists_internal(fcinfo, false);
480 : : }
481 : :
482 : : Datum
2598 akorotkov@postgresql 483 :UBC 0 : jsonb_path_exists_tz(PG_FUNCTION_ARGS)
484 : : {
485 : 0 : return jsonb_path_exists_internal(fcinfo, true);
486 : : }
487 : :
488 : : /*
489 : : * jsonb_path_exists_opr
490 : : * Implementation of operator "jsonb @? jsonpath" (2-argument version of
491 : : * jsonb_path_exists()).
492 : : */
493 : : Datum
2791 akorotkov@postgresql 494 :CBC 57440 : jsonb_path_exists_opr(PG_FUNCTION_ARGS)
495 : : {
496 : : /* just call the other one -- it can handle both cases */
2598 497 : 57440 : return jsonb_path_exists_internal(fcinfo, false);
498 : : }
499 : :
500 : : /*
501 : : * jsonb_path_match
502 : : * Returns jsonpath predicate result item for the specified jsonb value.
503 : : * See jsonb_path_exists() comment for details regarding error handling.
504 : : */
505 : : static Datum
506 : 85547 : jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
507 : : {
2791 508 : 85547 : Jsonb *jb = PG_GETARG_JSONB_P(0);
509 : 85547 : JsonPath *jp = PG_GETARG_JSONPATH_P(1);
510 : 85547 : Jsonb *vars = NULL;
511 : 85547 : bool silent = true;
512 : : JsonValueList found;
513 : :
514 [ + + ]: 85547 : if (PG_NARGS() == 4)
515 : : {
516 : 97 : vars = PG_GETARG_JSONB_P(2);
517 : 97 : silent = PG_GETARG_BOOL(3);
518 : : }
519 : :
231 tgl@sss.pgh.pa.us 520 : 85547 : JsonValueListInit(&found);
521 : :
1016 amitlan@postgresql.o 522 : 85547 : (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
523 : : countVariablesFromJsonb,
524 : 85547 : jb, !silent, &found, tz);
525 : :
2791 akorotkov@postgresql 526 [ + + ]: 85539 : PG_FREE_IF_COPY(jb, 0);
527 [ - + ]: 85539 : PG_FREE_IF_COPY(jp, 1);
528 : :
231 tgl@sss.pgh.pa.us 529 [ + + ]: 85539 : if (JsonValueListIsSingleton(&found))
530 : : {
2775 akorotkov@postgresql 531 : 85516 : JsonbValue *jbv = JsonValueListHead(&found);
532 : :
533 [ + + ]: 85516 : if (jbv->type == jbvBool)
534 : 84164 : PG_RETURN_BOOL(jbv->val.boolean);
535 : :
536 [ + + ]: 1352 : if (jbv->type == jbvNull)
537 : 1316 : PG_RETURN_NULL();
538 : : }
539 : :
540 [ + + ]: 59 : if (!silent)
541 [ + - ]: 24 : ereport(ERROR,
542 : : (errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED),
543 : : errmsg("single boolean result is expected")));
544 : :
545 : 35 : PG_RETURN_NULL();
546 : : }
547 : :
548 : : Datum
2598 549 : 97 : jsonb_path_match(PG_FUNCTION_ARGS)
550 : : {
551 : 97 : return jsonb_path_match_internal(fcinfo, false);
552 : : }
553 : :
554 : : Datum
2598 akorotkov@postgresql 555 :UBC 0 : jsonb_path_match_tz(PG_FUNCTION_ARGS)
556 : : {
557 : 0 : return jsonb_path_match_internal(fcinfo, true);
558 : : }
559 : :
560 : : /*
561 : : * jsonb_path_match_opr
562 : : * Implementation of operator "jsonb @@ jsonpath" (2-argument version of
563 : : * jsonb_path_match()).
564 : : */
565 : : Datum
2791 akorotkov@postgresql 566 :CBC 85450 : jsonb_path_match_opr(PG_FUNCTION_ARGS)
567 : : {
568 : : /* just call the other one -- it can handle both cases */
2598 569 : 85450 : return jsonb_path_match_internal(fcinfo, false);
570 : : }
571 : :
572 : : /*
573 : : * jsonb_path_query
574 : : * Executes jsonpath for given jsonb document and returns result as
575 : : * rowset.
576 : : */
577 : : static Datum
578 : 5432 : jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
579 : : {
580 : : FuncCallContext *funcctx;
581 : : JsonValueListIterator *iter;
582 : : JsonbValue *v;
583 : :
2791 584 [ + + ]: 5432 : if (SRF_IS_FIRSTCALL())
585 : : {
586 : : JsonPath *jp;
587 : : Jsonb *jb;
588 : : Jsonb *vars;
589 : : bool silent;
590 : : MemoryContext oldcontext;
591 : : JsonValueList *found;
592 : :
593 : 3076 : funcctx = SRF_FIRSTCALL_INIT();
594 : 3076 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
595 : :
596 : 3076 : jb = PG_GETARG_JSONB_P_COPY(0);
597 : 3076 : jp = PG_GETARG_JSONPATH_P_COPY(1);
598 : 3076 : vars = PG_GETARG_JSONB_P_COPY(2);
599 : 3076 : silent = PG_GETARG_BOOL(3);
600 : :
231 tgl@sss.pgh.pa.us 601 : 3076 : found = palloc_object(JsonValueList);
602 : 3076 : JsonValueListInit(found);
603 : :
1016 amitlan@postgresql.o 604 : 3076 : (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
605 : : countVariablesFromJsonb,
231 tgl@sss.pgh.pa.us 606 : 3076 : jb, !silent, found, tz);
607 : :
608 : 2040 : iter = palloc_object(JsonValueListIterator);
609 : 2040 : JsonValueListInitIterator(found, iter);
610 : :
611 : 2040 : funcctx->user_fctx = iter;
612 : :
2791 akorotkov@postgresql 613 : 2040 : MemoryContextSwitchTo(oldcontext);
614 : : }
615 : :
616 : 4396 : funcctx = SRF_PERCALL_SETUP();
231 tgl@sss.pgh.pa.us 617 : 4396 : iter = funcctx->user_fctx;
618 : :
619 : 4396 : v = JsonValueListNext(iter);
620 : :
621 [ + + ]: 4396 : if (v == NULL)
2791 akorotkov@postgresql 622 : 2040 : SRF_RETURN_DONE(funcctx);
623 : :
624 : 2356 : SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
625 : : }
626 : :
627 : : Datum
2598 628 : 4428 : jsonb_path_query(PG_FUNCTION_ARGS)
629 : : {
630 : 4428 : return jsonb_path_query_internal(fcinfo, false);
631 : : }
632 : :
633 : : Datum
634 : 1004 : jsonb_path_query_tz(PG_FUNCTION_ARGS)
635 : : {
636 : 1004 : return jsonb_path_query_internal(fcinfo, true);
637 : : }
638 : :
639 : : /*
640 : : * jsonb_path_query_array
641 : : * Executes jsonpath for given jsonb document and returns result as
642 : : * jsonb array.
643 : : */
644 : : static Datum
645 : 82 : jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
646 : : {
2791 647 : 82 : Jsonb *jb = PG_GETARG_JSONB_P(0);
648 : 82 : JsonPath *jp = PG_GETARG_JSONPATH_P(1);
649 : 82 : Jsonb *vars = PG_GETARG_JSONB_P(2);
650 : 82 : bool silent = PG_GETARG_BOOL(3);
651 : : JsonValueList found;
652 : :
231 tgl@sss.pgh.pa.us 653 : 82 : JsonValueListInit(&found);
654 : :
1016 amitlan@postgresql.o 655 : 82 : (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
656 : : countVariablesFromJsonb,
657 : 82 : jb, !silent, &found, tz);
658 : :
2791 akorotkov@postgresql 659 : 78 : PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
660 : : }
661 : :
662 : : Datum
2598 663 : 82 : jsonb_path_query_array(PG_FUNCTION_ARGS)
664 : : {
665 : 82 : return jsonb_path_query_array_internal(fcinfo, false);
666 : : }
667 : :
668 : : Datum
2598 akorotkov@postgresql 669 :UBC 0 : jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
670 : : {
671 : 0 : return jsonb_path_query_array_internal(fcinfo, true);
672 : : }
673 : :
674 : : /*
675 : : * jsonb_path_query_first
676 : : * Executes jsonpath for given jsonb document and returns first result
677 : : * item. If there are no items, NULL returned.
678 : : */
679 : : static Datum
2598 akorotkov@postgresql 680 :CBC 2923 : jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
681 : : {
2791 682 : 2923 : Jsonb *jb = PG_GETARG_JSONB_P(0);
683 : 2923 : JsonPath *jp = PG_GETARG_JSONPATH_P(1);
684 : 2923 : Jsonb *vars = PG_GETARG_JSONB_P(2);
685 : 2923 : bool silent = PG_GETARG_BOOL(3);
686 : : JsonValueList found;
687 : :
231 tgl@sss.pgh.pa.us 688 : 2923 : JsonValueListInit(&found);
689 : :
1016 amitlan@postgresql.o 690 : 2923 : (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
691 : : countVariablesFromJsonb,
692 : 2923 : jb, !silent, &found, tz);
693 : :
231 tgl@sss.pgh.pa.us 694 [ + + ]: 2915 : if (!JsonValueListIsEmpty(&found))
2791 akorotkov@postgresql 695 : 2905 : PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
696 : : else
697 : 10 : PG_RETURN_NULL();
698 : : }
699 : :
700 : : Datum
2598 701 : 2923 : jsonb_path_query_first(PG_FUNCTION_ARGS)
702 : : {
703 : 2923 : return jsonb_path_query_first_internal(fcinfo, false);
704 : : }
705 : :
706 : : Datum
2598 akorotkov@postgresql 707 :UBC 0 : jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
708 : : {
709 : 0 : return jsonb_path_query_first_internal(fcinfo, true);
710 : : }
711 : :
712 : : /********************Execute functions for JsonPath**************************/
713 : :
714 : : /*
715 : : * Interface to jsonpath executor
716 : : *
717 : : * 'path' - jsonpath to be executed
718 : : * 'vars' - variables to be substituted to jsonpath
719 : : * 'getVar' - callback used by getJsonPathVariable() to extract variables from
720 : : * 'vars'
721 : : * 'countVars' - callback to count the number of jsonpath variables in 'vars'
722 : : * 'json' - target document for jsonpath evaluation
723 : : * 'throwErrors' - whether we should throw suppressible errors
724 : : * 'result' - list to store result items into
725 : : *
726 : : * Returns an error if a recoverable error happens during processing, or NULL
727 : : * on no error.
728 : : *
729 : : * Note, jsonb and jsonpath values should be available and untoasted during
730 : : * work because JsonPathItem, JsonbValue and result item could have pointers
731 : : * into input values. If caller needs to just check if document matches
732 : : * jsonpath, then it doesn't provide a result arg. In this case executor
733 : : * works till first positive result and does not check the rest if possible.
734 : : * In other case it tries to find all the satisfied result items.
735 : : */
736 : : static JsonPathExecResult
1016 amitlan@postgresql.o 737 :CBC 155349 : executeJsonPath(JsonPath *path, void *vars, JsonPathGetVarCallback getVar,
738 : : JsonPathCountVarsCallback countVars,
739 : : Jsonb *json, bool throwErrors, JsonValueList *result,
740 : : bool useTz)
741 : : {
742 : : JsonPathExecContext cxt;
743 : : JsonPathExecResult res;
744 : : JsonPathItem jsp;
745 : : JsonbValue jbv;
746 : :
2791 akorotkov@postgresql 747 : 155349 : jspInit(&jsp, path);
748 : :
749 [ + + ]: 155349 : if (!JsonbExtractScalar(&json->root, &jbv))
750 : 150246 : JsonbInitBinary(&jbv, json);
751 : :
752 : 155349 : cxt.vars = vars;
1016 amitlan@postgresql.o 753 : 155349 : cxt.getVar = getVar;
2791 akorotkov@postgresql 754 : 155349 : cxt.laxMode = (path->header & JSONPATH_LAX) != 0;
755 : 155349 : cxt.ignoreStructuralErrors = cxt.laxMode;
756 : 155349 : cxt.root = &jbv;
757 : 155349 : cxt.current = &jbv;
758 : 155349 : cxt.baseObject.jbc = NULL;
759 : 155349 : cxt.baseObject.id = 0;
760 : : /* 1 + number of base objects in vars */
1016 amitlan@postgresql.o 761 : 155349 : cxt.lastGeneratedObjectId = 1 + countVars(vars);
2791 akorotkov@postgresql 762 : 155341 : cxt.innermostArraySize = -1;
763 : 155341 : cxt.throwErrors = throwErrors;
2598 764 : 155341 : cxt.useTz = useTz;
765 : :
1038 rhaas@postgresql.org 766 [ + + + + ]: 155341 : if (jspStrictAbsenceOfErrors(&cxt) && !result)
767 : : {
768 : : /*
769 : : * In strict mode we must get a complete list of values to check that
770 : : * there are no errors at all.
771 : : */
772 : : JsonValueList vals;
773 : : bool isempty;
774 : :
231 tgl@sss.pgh.pa.us 775 : 185 : JsonValueListInit(&vals);
776 : :
2791 akorotkov@postgresql 777 : 185 : res = executeItem(&cxt, &jsp, &jbv, &vals);
778 : :
231 tgl@sss.pgh.pa.us 779 : 177 : isempty = JsonValueListIsEmpty(&vals);
780 : 177 : JsonValueListClear(&vals);
781 : :
2791 akorotkov@postgresql 782 [ + + ]: 177 : if (jperIsError(res))
783 : 160 : return res;
784 : :
231 tgl@sss.pgh.pa.us 785 : 17 : return isempty ? jperNotFound : jperOk;
786 : : }
787 : :
2791 akorotkov@postgresql 788 : 155156 : res = executeItem(&cxt, &jsp, &jbv, result);
789 : :
790 [ + + - + ]: 154080 : Assert(!throwErrors || !jperIsError(res));
791 : :
792 : 154080 : return res;
793 : : }
794 : :
795 : : /*
796 : : * Execute jsonpath with automatic unwrapping of current item in lax mode.
797 : : */
798 : : static JsonPathExecResult
799 : 517251 : executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
800 : : JsonbValue *jb, JsonValueList *found)
801 : : {
802 : 517251 : return executeItemOptUnwrapTarget(cxt, jsp, jb, found, jspAutoUnwrap(cxt));
803 : : }
804 : :
805 : : /*
806 : : * Main jsonpath executor function: walks on jsonpath structure, finds
807 : : * relevant parts of jsonb and evaluates expressions over them.
808 : : * When 'unwrap' is true current SQL/JSON item is unwrapped if it is an array.
809 : : */
810 : : static JsonPathExecResult
811 : 523015 : executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
812 : : JsonbValue *jb, JsonValueList *found, bool unwrap)
813 : : {
814 : : JsonPathItem elem;
815 : 523015 : JsonPathExecResult res = jperNotFound;
816 : : JsonBaseObjectInfo baseObject;
817 : :
818 : 523015 : check_stack_depth();
819 [ - + ]: 523015 : CHECK_FOR_INTERRUPTS();
820 : :
821 [ + + + + : 523015 : switch (jsp->type)
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + - ]
822 : : {
1037 peter@eisentraut.org 823 : 68495 : case jpiNull:
824 : : case jpiBool:
825 : : case jpiNumeric:
826 : : case jpiString:
827 : : case jpiVariable:
828 : : {
829 : : JsonbValue v;
830 : 68495 : bool hasNext = jspGetNext(jsp, &elem);
831 : :
832 [ + + + + : 68495 : if (!hasNext && !found && jsp->type != jpiVariable)
+ + ]
833 : : {
834 : : /*
835 : : * Skip evaluation, but not for variables. We must
836 : : * trigger an error for the missing variable.
837 : : */
838 : 10 : res = jperOk;
839 : 10 : break;
840 : : }
841 : :
842 : 68485 : baseObject = cxt->baseObject;
231 tgl@sss.pgh.pa.us 843 : 68485 : getJsonPathItem(cxt, jsp, &v);
844 : :
1037 peter@eisentraut.org 845 : 68453 : res = executeNextItem(cxt, jsp, &elem,
846 : : &v, found);
847 : 68453 : cxt->baseObject = baseObject;
848 : : }
849 : 68453 : break;
850 : :
851 : : /* all boolean item types: */
2791 akorotkov@postgresql 852 : 88387 : case jpiAnd:
853 : : case jpiOr:
854 : : case jpiNot:
855 : : case jpiIsUnknown:
856 : : case jpiEqual:
857 : : case jpiNotEqual:
858 : : case jpiLess:
859 : : case jpiGreater:
860 : : case jpiLessOrEqual:
861 : : case jpiGreaterOrEqual:
862 : : case jpiExists:
863 : : case jpiStartsWith:
864 : : case jpiLikeRegex:
865 : : {
866 : 88387 : JsonPathBool st = executeBoolItem(cxt, jsp, jb, true);
867 : :
868 : 88387 : res = appendBoolResult(cxt, jsp, found, st);
869 : 88387 : break;
870 : : }
871 : :
1037 peter@eisentraut.org 872 : 242 : case jpiAdd:
873 : 242 : return executeBinaryArithmExpr(cxt, jsp, jb,
874 : : numeric_add_safe, found);
875 : :
876 : 118 : case jpiSub:
877 : 118 : return executeBinaryArithmExpr(cxt, jsp, jb,
878 : : numeric_sub_safe, found);
879 : :
880 : 40 : case jpiMul:
881 : 40 : return executeBinaryArithmExpr(cxt, jsp, jb,
882 : : numeric_mul_safe, found);
883 : :
884 : 44 : case jpiDiv:
885 : 44 : return executeBinaryArithmExpr(cxt, jsp, jb,
886 : : numeric_div_safe, found);
887 : :
888 : 8 : case jpiMod:
889 : 8 : return executeBinaryArithmExpr(cxt, jsp, jb,
890 : : numeric_mod_safe, found);
891 : :
892 : 48 : case jpiPlus:
893 : 48 : return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
894 : :
895 : 100 : case jpiMinus:
896 : 100 : return executeUnaryArithmExpr(cxt, jsp, jb, numeric_uminus,
897 : : found);
898 : :
2791 akorotkov@postgresql 899 : 2549 : case jpiAnyArray:
900 [ + + ]: 2549 : if (JsonbType(jb) == jbvArray)
901 : : {
902 : 2209 : bool hasNext = jspGetNext(jsp, &elem);
903 : :
904 : 2209 : res = executeItemUnwrapTargetArray(cxt, hasNext ? &elem : NULL,
905 [ + + ]: 2209 : jb, found, jspAutoUnwrap(cxt));
906 : : }
907 [ + + ]: 340 : else if (jspAutoWrap(cxt))
231 tgl@sss.pgh.pa.us 908 : 172 : res = executeNextItem(cxt, jsp, NULL, jb, found);
2791 akorotkov@postgresql 909 [ + - ]: 168 : else if (!jspIgnoreStructuralErrors(cxt))
910 [ + + + - ]: 168 : RETURN_ERROR(ereport(ERROR,
911 : : (errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND),
912 : : errmsg("jsonpath wildcard array accessor can only be applied to an array"))));
913 : 2221 : break;
914 : :
1037 peter@eisentraut.org 915 : 194 : case jpiAnyKey:
916 [ + + ]: 194 : if (JsonbType(jb) == jbvObject)
917 : : {
918 : 97 : bool hasNext = jspGetNext(jsp, &elem);
919 : :
920 [ - + ]: 97 : if (jb->type != jbvBinary)
1037 peter@eisentraut.org 921 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonb object type: %d", jb->type);
922 : :
1037 peter@eisentraut.org 923 :CBC 97 : return executeAnyItem
924 : : (cxt, hasNext ? &elem : NULL,
925 : : jb->val.binary.data, found, 1, 1, 1,
926 [ + + ]: 97 : false, jspAutoUnwrap(cxt));
927 : : }
928 [ + + + + ]: 97 : else if (unwrap && JsonbType(jb) == jbvArray)
929 : 18 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
930 [ + + ]: 79 : else if (!jspIgnoreStructuralErrors(cxt))
931 : : {
932 [ - + ]: 21 : Assert(found);
933 [ + + + - ]: 21 : RETURN_ERROR(ereport(ERROR,
934 : : (errcode(ERRCODE_SQL_JSON_OBJECT_NOT_FOUND),
935 : : errmsg("jsonpath wildcard member accessor can only be applied to an object"))));
936 : : }
937 : 58 : break;
938 : :
2791 akorotkov@postgresql 939 : 341 : case jpiIndexArray:
940 [ + + + + ]: 341 : if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
941 : 259 : {
942 : 333 : int innermostArraySize = cxt->innermostArraySize;
943 : : int i;
944 : 333 : int size = JsonbArraySize(jb);
945 : 333 : bool singleton = size < 0;
946 : 333 : bool hasNext = jspGetNext(jsp, &elem);
947 : :
948 [ + + ]: 333 : if (singleton)
949 : 4 : size = 1;
950 : :
951 : 333 : cxt->innermostArraySize = size; /* for LAST evaluation */
952 : :
953 [ + + ]: 577 : for (i = 0; i < jsp->content.array.nelems; i++)
954 : : {
955 : : JsonPathItem from;
956 : : JsonPathItem to;
957 : : int32 index;
958 : : int32 index_from;
959 : : int32 index_to;
960 : 341 : bool range = jspGetArraySubscript(jsp, &from,
961 : : &to, i);
962 : :
963 : 341 : res = getArrayIndex(cxt, &from, jb, &index_from);
964 : :
965 [ + + ]: 325 : if (jperIsError(res))
966 : 23 : break;
967 : :
968 [ + + ]: 307 : if (range)
969 : : {
970 : 21 : res = getArrayIndex(cxt, &to, jb, &index_to);
971 : :
972 [ - + ]: 17 : if (jperIsError(res))
2791 akorotkov@postgresql 973 :UBC 0 : break;
974 : : }
975 : : else
2791 akorotkov@postgresql 976 :CBC 286 : index_to = index_from;
977 : :
978 [ + + ]: 303 : if (!jspIgnoreStructuralErrors(cxt) &&
979 [ + + ]: 64 : (index_from < 0 ||
980 [ + - ]: 56 : index_from > index_to ||
981 [ + + ]: 56 : index_to >= size))
982 [ + + + - ]: 54 : RETURN_ERROR(ereport(ERROR,
983 : : (errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT),
984 : : errmsg("jsonpath array subscript is out of bounds"))));
985 : :
986 [ + + ]: 269 : if (index_from < 0)
987 : 8 : index_from = 0;
988 : :
989 [ + + ]: 269 : if (index_to >= size)
990 : 22 : index_to = size - 1;
991 : :
992 : 269 : res = jperNotFound;
993 : :
994 [ + + ]: 516 : for (index = index_from; index <= index_to; index++)
995 : : {
996 : : JsonbValue *v;
997 : :
998 [ + + ]: 272 : if (singleton)
999 : : {
1000 : 4 : v = jb;
1001 : : }
1002 : : else
1003 : : {
1004 : 268 : v = getIthJsonbValueFromContainer(jb->val.binary.data,
1005 : : (uint32) index);
1006 : :
1007 [ - + ]: 268 : if (v == NULL)
2791 akorotkov@postgresql 1008 :UBC 0 : continue;
1009 : : }
1010 : :
2791 akorotkov@postgresql 1011 [ + + + + ]:CBC 272 : if (!hasNext && !found)
1012 : 20 : return jperOk;
1013 : :
231 tgl@sss.pgh.pa.us 1014 : 252 : res = executeNextItem(cxt, jsp, &elem, v, found);
1015 : :
2791 akorotkov@postgresql 1016 [ - + ]: 252 : if (jperIsError(res))
2791 akorotkov@postgresql 1017 :UBC 0 : break;
1018 : :
2791 akorotkov@postgresql 1019 [ + + + + ]:CBC 252 : if (res == jperOk && !found)
1020 : 5 : break;
1021 : : }
1022 : :
1023 [ - + ]: 249 : if (jperIsError(res))
2791 akorotkov@postgresql 1024 :UBC 0 : break;
1025 : :
2791 akorotkov@postgresql 1026 [ + + + + ]:CBC 249 : if (res == jperOk && !found)
1027 : 5 : break;
1028 : : }
1029 : :
1030 : 259 : cxt->innermostArraySize = innermostArraySize;
1031 : : }
1032 [ + - ]: 8 : else if (!jspIgnoreStructuralErrors(cxt))
1033 : : {
1034 [ + + + - ]: 8 : RETURN_ERROR(ereport(ERROR,
1035 : : (errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND),
1036 : : errmsg("jsonpath array accessor can only be applied to an array"))));
1037 : : }
1038 : 259 : break;
1039 : :
1037 peter@eisentraut.org 1040 : 229 : case jpiAny:
1041 : : {
2791 akorotkov@postgresql 1042 : 229 : bool hasNext = jspGetNext(jsp, &elem);
1043 : :
1044 : : /* first try without any intermediate steps */
1037 peter@eisentraut.org 1045 [ + + ]: 229 : if (jsp->content.anybounds.first == 0)
1046 : : {
1047 : : bool savedIgnoreStructuralErrors;
1048 : :
1049 : 127 : savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors;
1050 : 127 : cxt->ignoreStructuralErrors = true;
1051 : 127 : res = executeNextItem(cxt, jsp, &elem,
1052 : : jb, found);
1053 : 127 : cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors;
1054 : :
1055 [ + + + + ]: 127 : if (res == jperOk && !found)
1056 : 5 : break;
1057 : : }
1058 : :
1059 [ + - ]: 224 : if (jb->type == jbvBinary)
1060 : 224 : res = executeAnyItem
1061 : : (cxt, hasNext ? &elem : NULL,
1062 : : jb->val.binary.data, found,
1063 : : 1,
1064 : : jsp->content.anybounds.first,
1065 : : jsp->content.anybounds.last,
1066 [ + + ]: 224 : true, jspAutoUnwrap(cxt));
1067 : 224 : break;
1068 : : }
1069 : :
1070 : 140171 : case jpiKey:
2791 akorotkov@postgresql 1071 [ + + ]: 140171 : if (JsonbType(jb) == jbvObject)
1072 : : {
1073 : : JsonbValue *v;
1074 : : JsonbValue key;
1075 : :
1037 peter@eisentraut.org 1076 : 139503 : key.type = jbvString;
1077 : 139503 : key.val.string.val = jspGetString(jsp, &key.val.string.len);
1078 : :
1079 : 139503 : v = findJsonbValueFromContainer(jb->val.binary.data,
1080 : : JB_FOBJECT, &key);
1081 : :
1082 [ + + ]: 139503 : if (v != NULL)
1083 : : {
1084 : 21862 : res = executeNextItem(cxt, jsp, NULL,
1085 : : v, found);
231 tgl@sss.pgh.pa.us 1086 : 21862 : pfree(v);
1087 : : }
1037 peter@eisentraut.org 1088 [ + + ]: 117641 : else if (!jspIgnoreStructuralErrors(cxt))
1089 : : {
1090 [ - + ]: 59 : Assert(found);
1091 : :
1092 [ + + ]: 59 : if (!jspThrowErrors(cxt))
1093 : 43 : return jperError;
1094 : :
1095 [ + - ]: 16 : ereport(ERROR,
1096 : : (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND), \
1097 : : errmsg("JSON object does not contain key \"%s\"",
1098 : : pnstrdup(key.val.string.val,
1099 : : key.val.string.len))));
1100 : : }
1101 : : }
2791 akorotkov@postgresql 1102 [ + + + + ]: 668 : else if (unwrap && JsonbType(jb) == jbvArray)
1103 : 24 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
1104 [ + + ]: 644 : else if (!jspIgnoreStructuralErrors(cxt))
1105 : : {
1106 [ - + ]: 211 : Assert(found);
1107 [ + + + - ]: 211 : RETURN_ERROR(ereport(ERROR,
1108 : : (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND),
1109 : : errmsg("jsonpath member accessor can only be applied to an object"))));
1110 : : }
1111 : 139877 : break;
1112 : :
1037 peter@eisentraut.org 1113 : 16241 : case jpiCurrent:
231 tgl@sss.pgh.pa.us 1114 : 16241 : res = executeNextItem(cxt, jsp, NULL, cxt->current, found);
1037 peter@eisentraut.org 1115 : 16241 : break;
1116 : :
1117 : 170662 : case jpiRoot:
1118 : 170662 : jb = cxt->root;
1119 : 170662 : baseObject = setBaseObject(cxt, jb, 0);
231 tgl@sss.pgh.pa.us 1120 : 170662 : res = executeNextItem(cxt, jsp, NULL, jb, found);
1037 peter@eisentraut.org 1121 : 169638 : cxt->baseObject = baseObject;
1122 : 169638 : break;
1123 : :
2791 akorotkov@postgresql 1124 : 15306 : case jpiFilter:
1125 : : {
1126 : : JsonPathBool st;
1127 : :
1128 [ + + + + ]: 15306 : if (unwrap && JsonbType(jb) == jbvArray)
1129 : 88 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
1130 : : false);
1131 : :
1132 : 15218 : jspGetArg(jsp, &elem);
1133 : 15218 : st = executeNestedBoolItem(cxt, &elem, jb);
1134 [ + + ]: 15154 : if (st != jpbTrue)
1135 : 12692 : res = jperNotFound;
1136 : : else
1137 : 2462 : res = executeNextItem(cxt, jsp, NULL,
1138 : : jb, found);
1139 : 15154 : break;
1140 : : }
1141 : :
1142 : 236 : case jpiType:
1143 : : {
1144 : : JsonbValue jbv;
1145 : :
231 tgl@sss.pgh.pa.us 1146 : 236 : jbv.type = jbvString;
1147 : 236 : jbv.val.string.val = pstrdup(JsonbTypeName(jb));
1148 : 236 : jbv.val.string.len = strlen(jbv.val.string.val);
1149 : :
1150 : 236 : res = executeNextItem(cxt, jsp, NULL, &jbv, found);
1151 : : }
2791 akorotkov@postgresql 1152 : 236 : break;
1153 : :
1154 : 48 : case jpiSize:
1155 : : {
1156 : 48 : int size = JsonbArraySize(jb);
1157 : : JsonbValue jbv;
1158 : :
1159 [ + + ]: 48 : if (size < 0)
1160 : : {
1161 [ + + ]: 32 : if (!jspAutoWrap(cxt))
1162 : : {
1163 [ + - ]: 8 : if (!jspIgnoreStructuralErrors(cxt))
1164 [ + + + - ]: 8 : RETURN_ERROR(ereport(ERROR,
1165 : : (errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND),
1166 : : errmsg("jsonpath item method .%s() can only be applied to an array",
1167 : : jspOperationName(jsp->type)))));
2791 akorotkov@postgresql 1168 :UBC 0 : break;
1169 : : }
1170 : :
2791 akorotkov@postgresql 1171 :CBC 24 : size = 1;
1172 : : }
1173 : :
231 tgl@sss.pgh.pa.us 1174 : 40 : jbv.type = jbvNumeric;
1175 : 40 : jbv.val.numeric = int64_to_numeric(size);
1176 : :
1177 : 40 : res = executeNextItem(cxt, jsp, NULL, &jbv, found);
1178 : : }
2791 akorotkov@postgresql 1179 : 40 : break;
1180 : :
1037 peter@eisentraut.org 1181 : 72 : case jpiAbs:
1182 : 72 : return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_abs,
1183 : : found);
1184 : :
1185 : 32 : case jpiFloor:
1186 : 32 : return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_floor,
1187 : : found);
1188 : :
1189 : 68 : case jpiCeiling:
1190 : 68 : return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_ceil,
1191 : : found);
1192 : :
2791 akorotkov@postgresql 1193 : 12220 : case jpiDouble:
1194 : : {
1195 : : JsonbValue jbv;
1196 : :
1197 [ + + + + ]: 12220 : if (unwrap && JsonbType(jb) == jbvArray)
1198 : 28 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
1199 : : false);
1200 : :
1201 [ + + ]: 12216 : if (jb->type == jbvNumeric)
1202 : : {
1203 : 8 : char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
1204 : : NumericGetDatum(jb->val.numeric)));
1205 : : double val;
1427 tgl@sss.pgh.pa.us 1206 : 8 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1207 : :
1208 : 8 : val = float8in_internal(tmp,
1209 : : NULL,
1210 : : "double precision",
1211 : : tmp,
1212 : : (Node *) &escontext);
1213 : :
982 andrew@dunslane.net 1214 [ + + ]: 8 : if (escontext.error_occurred)
1215 [ + - + - ]: 4 : RETURN_ERROR(ereport(ERROR,
1216 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1217 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1218 : : tmp, jspOperationName(jsp->type), "double precision"))));
1219 [ + - - + ]: 4 : if (isinf(val) || isnan(val))
2791 akorotkov@postgresql 1220 [ # # # # ]:UBC 0 : RETURN_ERROR(ereport(ERROR,
1221 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1222 : : errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
1223 : : jspOperationName(jsp->type)))));
2791 akorotkov@postgresql 1224 :CBC 4 : res = jperOk;
1225 : : }
1226 [ + + ]: 12208 : else if (jb->type == jbvString)
1227 : : {
1228 : : /* cast string as double */
1229 : : double val;
1230 : 12176 : char *tmp = pnstrdup(jb->val.string.val,
1231 : 12176 : jb->val.string.len);
1427 tgl@sss.pgh.pa.us 1232 : 12176 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1233 : :
1234 : 12176 : val = float8in_internal(tmp,
1235 : : NULL,
1236 : : "double precision",
1237 : : tmp,
1238 : : (Node *) &escontext);
1239 : :
982 andrew@dunslane.net 1240 [ + + ]: 12176 : if (escontext.error_occurred)
2791 akorotkov@postgresql 1241 [ + - + - ]: 12 : RETURN_ERROR(ereport(ERROR,
1242 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1243 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1244 : : tmp, jspOperationName(jsp->type), "double precision"))));
982 andrew@dunslane.net 1245 [ + + + + ]: 12172 : if (isinf(val) || isnan(val))
1246 [ + + + - ]: 24 : RETURN_ERROR(ereport(ERROR,
1247 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1248 : : errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
1249 : : jspOperationName(jsp->type)))));
1250 : :
2791 akorotkov@postgresql 1251 : 12148 : jb = &jbv;
1252 : 12148 : jb->type = jbvNumeric;
1253 : 12148 : jb->val.numeric = DatumGetNumeric(DirectFunctionCall1(float8_numeric,
1254 : : Float8GetDatum(val)));
1255 : 12148 : res = jperOk;
1256 : : }
1257 : :
1258 [ + + ]: 12184 : if (res == jperNotFound)
1259 [ + + + - ]: 32 : RETURN_ERROR(ereport(ERROR,
1260 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1261 : : errmsg("jsonpath item method .%s() can only be applied to a string or numeric value",
1262 : : jspOperationName(jsp->type)))));
1263 : :
231 tgl@sss.pgh.pa.us 1264 : 12152 : res = executeNextItem(cxt, jsp, NULL, jb, found);
1265 : : }
2791 akorotkov@postgresql 1266 : 12152 : break;
1267 : :
2598 1268 : 5686 : case jpiDatetime:
1269 : : case jpiDate:
1270 : : case jpiTime:
1271 : : case jpiTimeTz:
1272 : : case jpiTimestamp:
1273 : : case jpiTimestampTz:
1274 [ + + + + ]: 5686 : if (unwrap && JsonbType(jb) == jbvArray)
1275 : 24 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
1276 : :
1277 : 5662 : return executeDateTimeMethod(cxt, jsp, jb, found);
1278 : :
2791 1279 : 62 : case jpiKeyValue:
1280 [ + + + + ]: 62 : if (unwrap && JsonbType(jb) == jbvArray)
1281 : 4 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
1282 : :
1283 : 58 : return executeKeyValueMethod(cxt, jsp, jb, found);
1284 : :
1037 peter@eisentraut.org 1285 : 44 : case jpiLast:
1286 : : {
1287 : : JsonbValue jbv;
1288 : : int last;
1289 : 44 : bool hasNext = jspGetNext(jsp, &elem);
1290 : :
1291 [ - + ]: 44 : if (cxt->innermostArraySize < 0)
1037 peter@eisentraut.org 1292 [ # # ]:UBC 0 : elog(ERROR, "evaluating jsonpath LAST outside of array subscript");
1293 : :
1037 peter@eisentraut.org 1294 [ + + + + ]:CBC 44 : if (!hasNext && !found)
1295 : : {
1296 : 4 : res = jperOk;
1297 : 4 : break;
1298 : : }
1299 : :
1300 : 40 : last = cxt->innermostArraySize - 1;
1301 : :
231 tgl@sss.pgh.pa.us 1302 : 40 : jbv.type = jbvNumeric;
1303 : 40 : jbv.val.numeric = int64_to_numeric(last);
1304 : :
1037 peter@eisentraut.org 1305 : 40 : res = executeNextItem(cxt, jsp, &elem,
1306 : : &jbv, found);
1307 : : }
1308 : 40 : break;
1309 : :
1015 andrew@dunslane.net 1310 : 120 : case jpiBigint:
1311 : : {
1312 : : JsonbValue jbv;
1313 : : Datum datum;
1314 : :
1315 [ + + + + ]: 120 : if (unwrap && JsonbType(jb) == jbvArray)
1316 : 28 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
1317 : : false);
1318 : :
1319 [ + + ]: 116 : if (jb->type == jbvNumeric)
1320 : : {
426 michael@paquier.xyz 1321 : 32 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1322 : : int64 val;
1323 : :
1324 : 32 : val = numeric_int8_safe(jb->val.numeric,
1325 : : (Node *) &escontext);
1326 [ + + ]: 32 : if (escontext.error_occurred)
1015 andrew@dunslane.net 1327 [ + - + - ]: 8 : RETURN_ERROR(ereport(ERROR,
1328 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1329 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1330 : : DatumGetCString(DirectFunctionCall1(numeric_out,
1331 : : NumericGetDatum(jb->val.numeric))),
1332 : : jspOperationName(jsp->type),
1333 : : "bigint"))));
1334 : :
1335 : 24 : datum = Int64GetDatum(val);
1336 : 24 : res = jperOk;
1337 : : }
1338 [ + + ]: 84 : else if (jb->type == jbvString)
1339 : : {
1340 : : /* cast string as bigint */
1341 : 52 : char *tmp = pnstrdup(jb->val.string.val,
1342 : 52 : jb->val.string.len);
1343 : 52 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1344 : : bool noerr;
1345 : :
1346 : 52 : noerr = DirectInputFunctionCallSafe(int8in, tmp,
1347 : : InvalidOid, -1,
1348 : : (Node *) &escontext,
1349 : : &datum);
1350 : :
1351 [ + + - + ]: 52 : if (!noerr || escontext.error_occurred)
1352 [ + + + - ]: 36 : RETURN_ERROR(ereport(ERROR,
1353 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1354 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1355 : : tmp, jspOperationName(jsp->type), "bigint"))));
1356 : 16 : res = jperOk;
1357 : : }
1358 : :
1359 [ + + ]: 72 : if (res == jperNotFound)
1360 [ + + + - ]: 32 : RETURN_ERROR(ereport(ERROR,
1361 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1362 : : errmsg("jsonpath item method .%s() can only be applied to a string or numeric value",
1363 : : jspOperationName(jsp->type)))));
1364 : :
231 tgl@sss.pgh.pa.us 1365 : 40 : jbv.type = jbvNumeric;
1366 : 40 : jbv.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
1367 : : datum));
1368 : :
1369 : 40 : res = executeNextItem(cxt, jsp, NULL, &jbv, found);
1370 : : }
1015 andrew@dunslane.net 1371 : 40 : break;
1372 : :
1373 : 171 : case jpiBoolean:
1374 : : {
1375 : : JsonbValue jbv;
1376 : : bool bval;
1377 : :
1378 [ + + + + ]: 171 : if (unwrap && JsonbType(jb) == jbvArray)
1379 : 24 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
1380 : : false);
1381 : :
1382 [ + + ]: 167 : if (jb->type == jbvBool)
1383 : : {
1384 : 17 : bval = jb->val.boolean;
1385 : :
1386 : 17 : res = jperOk;
1387 : : }
1388 [ + + ]: 150 : else if (jb->type == jbvNumeric)
1389 : : {
1390 : : int ival;
1391 : : Datum datum;
1392 : : bool noerr;
1393 : 33 : char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
1394 : : NumericGetDatum(jb->val.numeric)));
1395 : 33 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1396 : :
1397 : 33 : noerr = DirectInputFunctionCallSafe(int4in, tmp,
1398 : : InvalidOid, -1,
1399 : : (Node *) &escontext,
1400 : : &datum);
1401 : :
1402 [ + + - + ]: 33 : if (!noerr || escontext.error_occurred)
1403 [ + - + - ]: 8 : RETURN_ERROR(ereport(ERROR,
1404 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1405 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1406 : : tmp, jspOperationName(jsp->type), "boolean"))));
1407 : :
1408 : 25 : ival = DatumGetInt32(datum);
1409 [ + + ]: 25 : if (ival == 0)
1410 : 4 : bval = false;
1411 : : else
1412 : 21 : bval = true;
1413 : :
1414 : 25 : res = jperOk;
1415 : : }
1416 [ + + ]: 117 : else if (jb->type == jbvString)
1417 : : {
1418 : : /* cast string as boolean */
1419 : 93 : char *tmp = pnstrdup(jb->val.string.val,
1420 : 93 : jb->val.string.len);
1421 : :
1422 [ + + ]: 93 : if (!parse_bool(tmp, &bval))
1423 [ + + + - ]: 36 : RETURN_ERROR(ereport(ERROR,
1424 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1425 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1426 : : tmp, jspOperationName(jsp->type), "boolean"))));
1427 : :
1428 : 57 : res = jperOk;
1429 : : }
1430 : :
1431 [ + + ]: 123 : if (res == jperNotFound)
1432 [ + + + - ]: 24 : RETURN_ERROR(ereport(ERROR,
1433 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1434 : : errmsg("jsonpath item method .%s() can only be applied to a boolean, string, or numeric value",
1435 : : jspOperationName(jsp->type)))));
1436 : :
231 tgl@sss.pgh.pa.us 1437 : 99 : jbv.type = jbvBool;
1438 : 99 : jbv.val.boolean = bval;
1439 : :
1440 : 99 : res = executeNextItem(cxt, jsp, NULL, &jbv, found);
1441 : : }
1015 andrew@dunslane.net 1442 : 99 : break;
1443 : :
1444 : 309 : case jpiDecimal:
1445 : : case jpiNumber:
1446 : : {
1447 : : JsonbValue jbv;
1448 : : Numeric num;
1449 : 309 : char *numstr = NULL;
1450 : :
1451 [ + + + + ]: 309 : if (unwrap && JsonbType(jb) == jbvArray)
1452 : 81 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
1453 : : false);
1454 : :
1455 [ + + ]: 301 : if (jb->type == jbvNumeric)
1456 : : {
1457 : 141 : num = jb->val.numeric;
1458 [ + - - + ]: 141 : if (numeric_is_nan(num) || numeric_is_inf(num))
1015 andrew@dunslane.net 1459 [ # # # # ]:UBC 0 : RETURN_ERROR(ereport(ERROR,
1460 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1461 : : errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
1462 : : jspOperationName(jsp->type)))));
1463 : :
1015 andrew@dunslane.net 1464 [ + + ]:CBC 141 : if (jsp->type == jpiDecimal)
1465 : 117 : numstr = DatumGetCString(DirectFunctionCall1(numeric_out,
1466 : : NumericGetDatum(num)));
1467 : 141 : res = jperOk;
1468 : : }
1469 [ + + ]: 160 : else if (jb->type == jbvString)
1470 : : {
1471 : : /* cast string as number */
1472 : : Datum datum;
1473 : : bool noerr;
1474 : 96 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1475 : :
1476 : 96 : numstr = pnstrdup(jb->val.string.val, jb->val.string.len);
1477 : :
1478 : 96 : noerr = DirectInputFunctionCallSafe(numeric_in, numstr,
1479 : : InvalidOid, -1,
1480 : : (Node *) &escontext,
1481 : : &datum);
1482 : :
1483 [ + + - + ]: 96 : if (!noerr || escontext.error_occurred)
1484 [ + - + - ]: 24 : RETURN_ERROR(ereport(ERROR,
1485 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1486 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1487 : : numstr, jspOperationName(jsp->type), "numeric"))));
1488 : :
1489 : 88 : num = DatumGetNumeric(datum);
1490 [ + + + + ]: 88 : if (numeric_is_nan(num) || numeric_is_inf(num))
1491 [ + + + - ]: 48 : RETURN_ERROR(ereport(ERROR,
1492 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1493 : : errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
1494 : : jspOperationName(jsp->type)))));
1495 : :
1496 : 40 : res = jperOk;
1497 : : }
1498 : :
1499 [ + + ]: 245 : if (res == jperNotFound)
1500 [ + + + - ]: 64 : RETURN_ERROR(ereport(ERROR,
1501 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1502 : : errmsg("jsonpath item method .%s() can only be applied to a string or numeric value",
1503 : : jspOperationName(jsp->type)))));
1504 : :
1505 : : /*
1506 : : * If we have arguments, then they must be the precision and
1507 : : * optional scale used in .decimal(). Convert them to the
1508 : : * typmod equivalent and then truncate the numeric value per
1509 : : * this typmod details.
1510 : : */
1511 [ + + + + ]: 181 : if (jsp->type == jpiDecimal && jsp->content.args.left)
1512 : : {
1513 : : Datum numdatum;
1514 : : int32 dtypmod;
1515 : : int32 precision;
1516 : 93 : int32 scale = 0;
1517 : : bool noerr;
1518 : 93 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1519 : :
1520 : 93 : jspGetLeftArg(jsp, &elem);
1521 [ - + ]: 93 : if (elem.type != jpiNumeric)
1015 andrew@dunslane.net 1522 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonpath item type for .decimal() precision");
1523 : :
426 michael@paquier.xyz 1524 :CBC 93 : precision = numeric_int4_safe(jspGetNumeric(&elem),
1525 : : (Node *) &escontext);
1526 [ + + ]: 93 : if (escontext.error_occurred)
1015 andrew@dunslane.net 1527 [ + - + - ]: 29 : RETURN_ERROR(ereport(ERROR,
1528 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1529 : : errmsg("precision of jsonpath item method .%s() is out of range for type integer",
1530 : : jspOperationName(jsp->type)))));
1531 : :
1532 [ + + ]: 89 : if (jsp->content.args.right)
1533 : : {
1534 : 84 : jspGetRightArg(jsp, &elem);
1535 [ - + ]: 84 : if (elem.type != jpiNumeric)
1015 andrew@dunslane.net 1536 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonpath item type for .decimal() scale");
1537 : :
426 michael@paquier.xyz 1538 :CBC 84 : scale = numeric_int4_safe(jspGetNumeric(&elem),
1539 : : (Node *) &escontext);
1540 [ + + ]: 84 : if (escontext.error_occurred)
1015 andrew@dunslane.net 1541 [ + - + - ]: 4 : RETURN_ERROR(ereport(ERROR,
1542 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1543 : : errmsg("scale of jsonpath item method .%s() is out of range for type integer",
1544 : : jspOperationName(jsp->type)))));
1545 : : }
1546 : :
1547 : : /* Pack the precision and scale into a numeric typmod */
126 michael@paquier.xyz 1548 : 85 : dtypmod = make_numeric_typmod_safe(precision, scale,
1549 [ + + ]: 85 : jspThrowErrors(cxt) ? NULL : (Node *) &escontext);
1550 [ + + ]: 65 : if (escontext.error_occurred)
1551 : 25 : return jperError;
1552 : :
1553 : : /* Convert numstr to Numeric with typmod */
1015 andrew@dunslane.net 1554 [ - + ]: 40 : Assert(numstr != NULL);
1555 : 40 : noerr = DirectInputFunctionCallSafe(numeric_in, numstr,
1556 : : InvalidOid, dtypmod,
1557 : : (Node *) &escontext,
1558 : : &numdatum);
1559 : :
1560 [ + + - + ]: 40 : if (!noerr || escontext.error_occurred)
1561 [ + - + - ]: 8 : RETURN_ERROR(ereport(ERROR,
1562 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1563 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1564 : : numstr, jspOperationName(jsp->type), "numeric"))));
1565 : :
1566 : 32 : num = DatumGetNumeric(numdatum);
1567 : : }
1568 : :
231 tgl@sss.pgh.pa.us 1569 : 120 : jbv.type = jbvNumeric;
1570 : 120 : jbv.val.numeric = num;
1571 : :
1572 : 120 : res = executeNextItem(cxt, jsp, NULL, &jbv, found);
1573 : : }
1015 andrew@dunslane.net 1574 : 120 : break;
1575 : :
1576 : 112 : case jpiInteger:
1577 : : {
1578 : : JsonbValue jbv;
1579 : : Datum datum;
1580 : :
1581 [ + + + + ]: 112 : if (unwrap && JsonbType(jb) == jbvArray)
1582 : 28 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
1583 : : false);
1584 : :
1585 [ + + ]: 108 : if (jb->type == jbvNumeric)
1586 : : {
1587 : : int32 val;
426 michael@paquier.xyz 1588 : 28 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1589 : :
1590 : 28 : val = numeric_int4_safe(jb->val.numeric,
1591 : : (Node *) &escontext);
1592 [ + + ]: 28 : if (escontext.error_occurred)
1015 andrew@dunslane.net 1593 [ + - + - ]: 8 : RETURN_ERROR(ereport(ERROR,
1594 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1595 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1596 : : DatumGetCString(DirectFunctionCall1(numeric_out,
1597 : : NumericGetDatum(jb->val.numeric))),
1598 : : jspOperationName(jsp->type), "integer"))));
1599 : :
1600 : 20 : datum = Int32GetDatum(val);
1601 : 20 : res = jperOk;
1602 : : }
1603 [ + + ]: 80 : else if (jb->type == jbvString)
1604 : : {
1605 : : /* cast string as integer */
1606 : 48 : char *tmp = pnstrdup(jb->val.string.val,
1607 : 48 : jb->val.string.len);
1608 : 48 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1609 : : bool noerr;
1610 : :
1611 : 48 : noerr = DirectInputFunctionCallSafe(int4in, tmp,
1612 : : InvalidOid, -1,
1613 : : (Node *) &escontext,
1614 : : &datum);
1615 : :
1616 [ + + - + ]: 48 : if (!noerr || escontext.error_occurred)
1617 [ + + + - ]: 36 : RETURN_ERROR(ereport(ERROR,
1618 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1619 : : errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
1620 : : tmp, jspOperationName(jsp->type), "integer"))));
1621 : 12 : res = jperOk;
1622 : : }
1623 : :
1624 [ + + ]: 64 : if (res == jperNotFound)
1625 [ + + + - ]: 32 : RETURN_ERROR(ereport(ERROR,
1626 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1627 : : errmsg("jsonpath item method .%s() can only be applied to a string or numeric value",
1628 : : jspOperationName(jsp->type)))));
1629 : :
231 tgl@sss.pgh.pa.us 1630 : 32 : jbv.type = jbvNumeric;
1631 : 32 : jbv.val.numeric = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
1632 : : datum));
1633 : :
1634 : 32 : res = executeNextItem(cxt, jsp, NULL, &jbv, found);
1635 : : }
1015 andrew@dunslane.net 1636 : 32 : break;
1637 : :
1638 : 134 : case jpiStringFunc:
1639 : : {
1640 : : JsonbValue jbv;
1641 : 134 : char *tmp = NULL;
1642 : :
871 1643 [ + + + + ]: 134 : if (unwrap && JsonbType(jb) == jbvArray)
1644 : 20 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
1645 : :
1015 1646 [ + + + + : 126 : switch (JsonbType(jb))
+ - ]
1647 : : {
1648 : 18 : case jbvString:
1649 : :
1650 : : /*
1651 : : * Value is not necessarily null-terminated, so we do
1652 : : * pnstrdup() here.
1653 : : */
1654 : 18 : tmp = pnstrdup(jb->val.string.val,
1655 : 18 : jb->val.string.len);
1656 : 18 : break;
1657 : 26 : case jbvNumeric:
1658 : 26 : tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
1659 : : NumericGetDatum(jb->val.numeric)));
1660 : 26 : break;
1661 : 18 : case jbvBool:
1662 [ + + ]: 18 : tmp = (jb->val.boolean) ? "true" : "false";
1663 : 18 : break;
1664 : 40 : case jbvDatetime:
1665 : : {
1666 : : char buf[MAXDATELEN + 1];
1667 : :
784 tgl@sss.pgh.pa.us 1668 : 40 : JsonEncodeDateTime(buf,
1669 : : jb->val.datetime.value,
1670 : : jb->val.datetime.typid,
1671 : 40 : &jb->val.datetime.tz);
1672 : 40 : tmp = pstrdup(buf);
1673 : : }
1015 andrew@dunslane.net 1674 : 40 : break;
1675 : 24 : case jbvNull:
1676 : : case jbvArray:
1677 : : case jbvObject:
1678 : : case jbvBinary:
1679 [ + + + - ]: 24 : RETURN_ERROR(ereport(ERROR,
1680 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
1681 : : errmsg("jsonpath item method .%s() can only be applied to a boolean, string, numeric, or datetime value",
1682 : : jspOperationName(jsp->type)))));
1683 : : break;
1684 : : }
1685 : :
1686 [ - + ]: 102 : Assert(tmp != NULL); /* We must have set tmp above */
231 tgl@sss.pgh.pa.us 1687 : 102 : jbv.val.string.val = tmp;
1688 : 102 : jbv.val.string.len = strlen(jbv.val.string.val);
1689 : 102 : jbv.type = jbvString;
1690 : :
1691 : 102 : res = executeNextItem(cxt, jsp, NULL, &jbv, found);
1692 : : }
1015 andrew@dunslane.net 1693 : 102 : break;
1694 : :
217 1695 : 526 : case jpiStrReplace:
1696 : : case jpiStrLower:
1697 : : case jpiStrUpper:
1698 : : case jpiStrLtrim:
1699 : : case jpiStrRtrim:
1700 : : case jpiStrBtrim:
1701 : : case jpiStrInitcap:
1702 : : case jpiStrSplitPart:
1703 : : {
1704 [ + + + + ]: 526 : if (unwrap && JsonbType(jb) == jbvArray)
1705 : 40 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
1706 : :
1707 : 486 : return executeStringInternalMethod(cxt, jsp, jb, found);
1708 : : }
1709 : : break;
1710 : :
2791 akorotkov@postgresql 1711 :UBC 0 : default:
1712 [ # # ]: 0 : elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
1713 : : }
1714 : :
2791 akorotkov@postgresql 1715 :CBC 513392 : return res;
1716 : : }
1717 : :
1718 : : /*
1719 : : * Unwrap current array item and execute jsonpath for each of its elements.
1720 : : */
1721 : : static JsonPathExecResult
1722 : 2475 : executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
1723 : : JsonbValue *jb, JsonValueList *found,
1724 : : bool unwrapElements)
1725 : : {
1726 [ - + ]: 2475 : if (jb->type != jbvBinary)
1727 : : {
2791 akorotkov@postgresql 1728 [ # # ]:UBC 0 : Assert(jb->type != jbvArray);
1729 [ # # ]: 0 : elog(ERROR, "invalid jsonb array value type: %d", jb->type);
1730 : : }
1731 : :
2791 akorotkov@postgresql 1732 :CBC 2475 : return executeAnyItem
1733 : : (cxt, jsp, jb->val.binary.data, found, 1, 1, 1,
1734 : : false, unwrapElements);
1735 : : }
1736 : :
1737 : : /*
1738 : : * Execute next jsonpath item if exists. Otherwise put "v" to the "found"
1739 : : * list if provided.
1740 : : */
1741 : : static JsonPathExecResult
1742 : 387621 : executeNextItem(JsonPathExecContext *cxt,
1743 : : JsonPathItem *cur, JsonPathItem *next,
1744 : : JsonbValue *v, JsonValueList *found)
1745 : : {
1746 : : JsonPathItem elem;
1747 : : bool hasNext;
1748 : :
1749 [ - + ]: 387621 : if (!cur)
2791 akorotkov@postgresql 1750 :UBC 0 : hasNext = next != NULL;
2791 akorotkov@postgresql 1751 [ + + ]:CBC 387621 : else if (next)
1752 : 163401 : hasNext = jspHasNext(cur);
1753 : : else
1754 : : {
1755 : 224220 : next = &elem;
1756 : 224220 : hasNext = jspGetNext(cur, next);
1757 : : }
1758 : :
1759 [ + + ]: 387621 : if (hasNext)
1760 : 172822 : return executeItem(cxt, next, v, found);
1761 : :
1762 [ + + ]: 214799 : if (found)
231 tgl@sss.pgh.pa.us 1763 : 181316 : JsonValueListAppend(found, v);
1764 : :
2791 akorotkov@postgresql 1765 : 214799 : return jperOk;
1766 : : }
1767 : :
1768 : : /*
1769 : : * Same as executeItem(), but when "unwrap == true" automatically unwraps
1770 : : * each array item from the resulting sequence in lax mode.
1771 : : */
1772 : : static JsonPathExecResult
1773 : 188726 : executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
1774 : : JsonbValue *jb, bool unwrap,
1775 : : JsonValueList *found)
1776 : : {
1777 [ + + + + ]: 188726 : if (unwrap && jspAutoUnwrap(cxt))
1778 : : {
1779 : : JsonValueList seq;
1780 : : JsonValueListIterator it;
1781 : : JsonPathExecResult res;
1782 : : JsonbValue *item;
1783 : :
231 tgl@sss.pgh.pa.us 1784 : 135038 : JsonValueListInit(&seq);
1785 : :
1786 : 135038 : res = executeItem(cxt, jsp, jb, &seq);
1787 : :
2791 akorotkov@postgresql 1788 [ + + ]: 135022 : if (jperIsError(res))
1789 : : {
231 tgl@sss.pgh.pa.us 1790 : 47 : JsonValueListClear(&seq);
2791 akorotkov@postgresql 1791 : 47 : return res;
1792 : : }
1793 : :
1794 : 134975 : JsonValueListInitIterator(&seq, &it);
231 tgl@sss.pgh.pa.us 1795 [ + + ]: 219914 : while ((item = JsonValueListNext(&it)))
1796 : : {
2791 akorotkov@postgresql 1797 [ - + ]: 84939 : Assert(item->type != jbvArray);
1798 : :
1799 [ + + ]: 84939 : if (JsonbType(item) == jbvArray)
1800 : 36 : executeItemUnwrapTargetArray(cxt, NULL, item, found, false);
1801 : : else
1802 : 84903 : JsonValueListAppend(found, item);
1803 : : }
1804 : :
231 tgl@sss.pgh.pa.us 1805 : 134975 : JsonValueListClear(&seq);
1806 : :
2791 akorotkov@postgresql 1807 : 134975 : return jperOk;
1808 : : }
1809 : :
1810 : 53688 : return executeItem(cxt, jsp, jb, found);
1811 : : }
1812 : :
1813 : : /*
1814 : : * Same as executeItemOptUnwrapResult(), but with error suppression.
1815 : : */
1816 : : static JsonPathExecResult
1817 : 187678 : executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt,
1818 : : JsonPathItem *jsp,
1819 : : JsonbValue *jb, bool unwrap,
1820 : : JsonValueList *found)
1821 : : {
1822 : : JsonPathExecResult res;
1823 : 187678 : bool throwErrors = cxt->throwErrors;
1824 : :
1825 : 187678 : cxt->throwErrors = false;
1826 : 187678 : res = executeItemOptUnwrapResult(cxt, jsp, jb, unwrap, found);
1827 : 187674 : cxt->throwErrors = throwErrors;
1828 : :
1829 : 187674 : return res;
1830 : : }
1831 : :
1832 : : /* Execute boolean-valued jsonpath expression. */
1833 : : static JsonPathBool
1834 : 171167 : executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
1835 : : JsonbValue *jb, bool canHaveNext)
1836 : : {
1837 : : JsonPathItem larg;
1838 : : JsonPathItem rarg;
1839 : : JsonPathBool res;
1840 : : JsonPathBool res2;
1841 : :
1842 : : /* since this function recurses, it could be driven to stack overflow */
993 1843 : 171167 : check_stack_depth();
1844 : :
2791 1845 [ + + - + ]: 171167 : if (!canHaveNext && jspHasNext(jsp))
2791 akorotkov@postgresql 1846 [ # # ]:UBC 0 : elog(ERROR, "boolean jsonpath item cannot have next item");
1847 : :
2791 akorotkov@postgresql 1848 [ + + + + :CBC 171167 : switch (jsp->type)
+ + + +
- ]
1849 : : {
1850 : 25750 : case jpiAnd:
1851 : 25750 : jspGetLeftArg(jsp, &larg);
1852 : 25750 : res = executeBoolItem(cxt, &larg, jb, false);
1853 : :
1854 [ + + ]: 25750 : if (res == jpbFalse)
1855 : 15956 : return jpbFalse;
1856 : :
1857 : : /*
1858 : : * SQL/JSON says that we should check second arg in case of
1859 : : * jperError
1860 : : */
1861 : :
1862 : 9794 : jspGetRightArg(jsp, &rarg);
1863 : 9794 : res2 = executeBoolItem(cxt, &rarg, jb, false);
1864 : :
1865 [ + + ]: 9794 : return res2 == jpbTrue ? res : res2;
1866 : :
1867 : 8636 : case jpiOr:
1868 : 8636 : jspGetLeftArg(jsp, &larg);
1869 : 8636 : res = executeBoolItem(cxt, &larg, jb, false);
1870 : :
1871 [ + + ]: 8636 : if (res == jpbTrue)
1872 : 1660 : return jpbTrue;
1873 : :
1874 : 6976 : jspGetRightArg(jsp, &rarg);
1875 : 6976 : res2 = executeBoolItem(cxt, &rarg, jb, false);
1876 : :
1877 [ + + ]: 6976 : return res2 == jpbFalse ? res : res2;
1878 : :
1879 : 16264 : case jpiNot:
1880 : 16264 : jspGetArg(jsp, &larg);
1881 : :
1882 : 16264 : res = executeBoolItem(cxt, &larg, jb, false);
1883 : :
1884 [ + + ]: 16264 : if (res == jpbUnknown)
1885 : 1320 : return jpbUnknown;
1886 : :
1887 : 14944 : return res == jpbTrue ? jpbFalse : jpbTrue;
1888 : :
1889 : 142 : case jpiIsUnknown:
1890 : 142 : jspGetArg(jsp, &larg);
1891 : 142 : res = executeBoolItem(cxt, &larg, jb, false);
1892 : 142 : return res == jpbUnknown ? jpbTrue : jpbFalse;
1893 : :
1894 : 67247 : case jpiEqual:
1895 : : case jpiNotEqual:
1896 : : case jpiLess:
1897 : : case jpiGreater:
1898 : : case jpiLessOrEqual:
1899 : : case jpiGreaterOrEqual:
1900 : 67247 : jspGetLeftArg(jsp, &larg);
1901 : 67247 : jspGetRightArg(jsp, &rarg);
1902 : 67247 : return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
1903 : : executeComparison, cxt);
1904 : :
1905 : 68 : case jpiStartsWith: /* 'whole STARTS WITH initial' */
1906 : 68 : jspGetLeftArg(jsp, &larg); /* 'whole' */
1907 : 68 : jspGetRightArg(jsp, &rarg); /* 'initial' */
1908 : 68 : return executePredicate(cxt, jsp, &larg, &rarg, jb, false,
1909 : : executeStartsWith, NULL);
1910 : :
1911 : 264 : case jpiLikeRegex: /* 'expr LIKE_REGEX pattern FLAGS flags' */
1912 : : {
1913 : : /*
1914 : : * 'expr' is a sequence-returning expression. 'pattern' is a
1915 : : * regex string literal. SQL/JSON standard requires XQuery
1916 : : * regexes, but we use Postgres regexes here. 'flags' is a
1917 : : * string literal converted to integer flags at compile-time.
1918 : : */
2789 1919 : 264 : JsonLikeRegexContext lrcxt = {0};
1920 : :
2791 1921 : 264 : jspInitByBuffer(&larg, jsp->base,
1922 : : jsp->content.like_regex.expr);
1923 : :
1924 : 264 : return executePredicate(cxt, jsp, &larg, NULL, jb, false,
1925 : : executeLikeRegex, &lrcxt);
1926 : : }
1927 : :
1928 : 52796 : case jpiExists:
1929 : 52796 : jspGetArg(jsp, &larg);
1930 : :
1038 rhaas@postgresql.org 1931 [ + + ]: 52796 : if (jspStrictAbsenceOfErrors(cxt))
1932 : : {
1933 : : /*
1934 : : * In strict mode we must get a complete list of values to
1935 : : * check that there are no errors at all.
1936 : : */
1937 : : JsonValueList vals;
1938 : : JsonPathExecResult jper;
1939 : : bool isempty;
1940 : :
231 tgl@sss.pgh.pa.us 1941 : 34 : JsonValueListInit(&vals);
1942 : :
58 peter@eisentraut.org 1943 :GNC 34 : jper = executeItemOptUnwrapResultNoThrow(cxt, &larg, jb,
1944 : : false, &vals);
1945 : :
231 tgl@sss.pgh.pa.us 1946 :CBC 34 : isempty = JsonValueListIsEmpty(&vals);
1947 : 34 : JsonValueListClear(&vals);
1948 : :
58 peter@eisentraut.org 1949 [ + + ]:GNC 34 : if (jperIsError(jper))
2791 akorotkov@postgresql 1950 :CBC 26 : return jpbUnknown;
1951 : :
231 tgl@sss.pgh.pa.us 1952 : 8 : return isempty ? jpbFalse : jpbTrue;
1953 : : }
1954 : : else
1955 : : {
1956 : : JsonPathExecResult jper =
1266 1957 : 52762 : executeItemOptUnwrapResultNoThrow(cxt, &larg, jb,
1958 : : false, NULL);
1959 : :
58 peter@eisentraut.org 1960 [ + + ]:GNC 52762 : if (jperIsError(jper))
2791 akorotkov@postgresql 1961 :CBC 16 : return jpbUnknown;
1962 : :
58 peter@eisentraut.org 1963 :GNC 52746 : return jper == jperOk ? jpbTrue : jpbFalse;
1964 : : }
1965 : :
2791 akorotkov@postgresql 1966 :UBC 0 : default:
1967 [ # # ]: 0 : elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
1968 : : return jpbUnknown;
1969 : : }
1970 : : }
1971 : :
1972 : : /*
1973 : : * Execute nested (filters etc.) boolean expression pushing current SQL/JSON
1974 : : * item onto the stack.
1975 : : */
1976 : : static JsonPathBool
2791 akorotkov@postgresql 1977 :CBC 15218 : executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
1978 : : JsonbValue *jb)
1979 : : {
1980 : : JsonbValue *prev;
1981 : : JsonPathBool res;
1982 : :
1983 : 15218 : prev = cxt->current;
1984 : 15218 : cxt->current = jb;
1985 : 15218 : res = executeBoolItem(cxt, jsp, jb, false);
1986 : 15154 : cxt->current = prev;
1987 : :
1988 : 15154 : return res;
1989 : : }
1990 : :
1991 : : /*
1992 : : * Implementation of several jsonpath nodes:
1993 : : * - jpiAny (.** accessor),
1994 : : * - jpiAnyKey (.* accessor),
1995 : : * - jpiAnyArray ([*] accessor)
1996 : : */
1997 : : static JsonPathExecResult
1998 : 2927 : executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc,
1999 : : JsonValueList *found, uint32 level, uint32 first, uint32 last,
2000 : : bool ignoreStructuralErrors, bool unwrapNext)
2001 : : {
2002 : 2927 : JsonPathExecResult res = jperNotFound;
2003 : : JsonbIterator *it;
2004 : : int32 r;
2005 : : JsonbValue v;
2006 : :
2007 : 2927 : check_stack_depth();
2008 : :
2009 [ + + ]: 2927 : if (level > last)
2010 : 22 : return res;
2011 : :
2012 : 2905 : it = JsonbIteratorInit(jbc);
2013 : :
2014 : : /*
2015 : : * Recursively iterate over jsonb objects/arrays
2016 : : */
2017 [ + + ]: 16068 : while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
2018 : : {
2019 [ + + ]: 13711 : if (r == WJB_KEY)
2020 : : {
2021 : 477 : r = JsonbIteratorNext(&it, &v, true);
2022 [ - + ]: 477 : Assert(r == WJB_VALUE);
2023 : : }
2024 : :
2025 [ + + + + ]: 13711 : if (r == WJB_VALUE || r == WJB_ELEM)
2026 : : {
2027 : :
2028 [ + + + + ]: 8449 : if (level >= first ||
2029 [ + - ]: 8 : (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
2030 [ + + ]: 8 : v.type != jbvBinary)) /* leaves only requested */
2031 : : {
2032 : : /* check expression */
2033 [ + + ]: 8405 : if (jsp)
2034 : : {
2035 [ + + ]: 5764 : if (ignoreStructuralErrors)
2036 : : {
2037 : : bool savedIgnoreStructuralErrors;
2038 : :
2039 : 255 : savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors;
2040 : 255 : cxt->ignoreStructuralErrors = true;
2041 : 255 : res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext);
2042 : 255 : cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors;
2043 : : }
2044 : : else
2045 : 5509 : res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext);
2046 : :
2047 [ + + ]: 5588 : if (jperIsError(res))
2048 : 49 : break;
2049 : :
2050 [ + + + + ]: 5539 : if (res == jperOk && !found)
2051 : 263 : break;
2052 : : }
2053 [ + + ]: 2641 : else if (found)
231 tgl@sss.pgh.pa.us 2054 : 2611 : JsonValueListAppend(found, &v);
2055 : : else
2791 akorotkov@postgresql 2056 : 30 : return jperOk;
2057 : : }
2058 : :
2059 [ + + + + ]: 7931 : if (level < last && v.type == jbvBinary)
2060 : : {
2061 : 131 : res = executeAnyItem
2062 : : (cxt, jsp, v.val.binary.data, found,
2063 : : level + 1, first, last,
2064 : : ignoreStructuralErrors, unwrapNext);
2065 : :
2066 [ - + ]: 131 : if (jperIsError(res))
2791 akorotkov@postgresql 2067 :UBC 0 : break;
2068 : :
2791 akorotkov@postgresql 2069 [ + + + + ]:CBC 131 : if (res == jperOk && found == NULL)
2070 : 30 : break;
2071 : : }
2072 : : }
2073 : : }
2074 : :
2075 : 2699 : return res;
2076 : : }
2077 : :
2078 : : /*
2079 : : * Execute unary or binary predicate.
2080 : : *
2081 : : * Predicates have existence semantics, because their operands are item
2082 : : * sequences. Pairs of items from the left and right operand's sequences are
2083 : : * checked. TRUE returned only if any pair satisfying the condition is found.
2084 : : * In strict mode, even if the desired pair has already been found, all pairs
2085 : : * still need to be examined to check the absence of errors. If any error
2086 : : * occurs, UNKNOWN (analogous to SQL NULL) is returned.
2087 : : */
2088 : : static JsonPathBool
2089 : 67579 : executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
2090 : : JsonPathItem *larg, JsonPathItem *rarg, JsonbValue *jb,
2091 : : bool unwrapRightArg, JsonPathPredicateCallback exec,
2092 : : void *param)
2093 : : {
2094 : : JsonPathExecResult jper;
2095 : : JsonValueListIterator lseqit;
2096 : : JsonValueList lseq;
2097 : : JsonValueList rseq;
2098 : : JsonbValue *lval;
2099 : 67579 : bool error = false;
2100 : 67579 : bool found = false;
2101 : :
231 tgl@sss.pgh.pa.us 2102 : 67579 : JsonValueListInit(&lseq);
2103 : 67579 : JsonValueListInit(&rseq);
2104 : :
2105 : : /* Left argument is always auto-unwrapped. */
58 peter@eisentraut.org 2106 :GNC 67579 : jper = executeItemOptUnwrapResultNoThrow(cxt, larg, jb, true, &lseq);
2107 [ + + ]: 67579 : if (jperIsError(jper))
2108 : : {
231 tgl@sss.pgh.pa.us 2109 :CBC 12 : error = true;
2110 : 12 : goto exit;
2111 : : }
2112 : :
2791 akorotkov@postgresql 2113 [ + + ]: 67567 : if (rarg)
2114 : : {
2115 : : /* Right argument is conditionally auto-unwrapped. */
58 peter@eisentraut.org 2116 :GNC 67303 : jper = executeItemOptUnwrapResultNoThrow(cxt, rarg, jb,
2117 : : unwrapRightArg, &rseq);
2118 [ + + ]: 67299 : if (jperIsError(jper))
2119 : : {
231 tgl@sss.pgh.pa.us 2120 :CBC 39 : error = true;
2121 : 39 : goto exit;
2122 : : }
2123 : : }
2124 : :
2791 akorotkov@postgresql 2125 : 67524 : JsonValueListInitIterator(&lseq, &lseqit);
231 tgl@sss.pgh.pa.us 2126 [ + + ]: 83099 : while ((lval = JsonValueListNext(&lseqit)))
2127 : : {
2128 : : JsonValueListIterator rseqit;
2129 : : JsonbValue *rval;
2791 akorotkov@postgresql 2130 : 21113 : bool first = true;
2131 : :
2790 2132 : 21113 : JsonValueListInitIterator(&rseq, &rseqit);
2791 2133 [ + + ]: 21113 : if (rarg)
231 tgl@sss.pgh.pa.us 2134 : 20849 : rval = JsonValueListNext(&rseqit);
2135 : : else
2791 akorotkov@postgresql 2136 : 264 : rval = NULL;
2137 : :
2138 : : /* Loop over right arg sequence or do single pass otherwise */
2139 [ + + + + ]: 33412 : while (rarg ? (rval != NULL) : first)
2140 : : {
2141 : 17837 : JsonPathBool res = exec(pred, lval, rval, param);
2142 : :
2143 [ + + ]: 17777 : if (res == jpbUnknown)
2144 : : {
2145 : 1798 : error = true;
231 tgl@sss.pgh.pa.us 2146 [ + + ]: 1798 : if (jspStrictAbsenceOfErrors(cxt))
2147 : : {
2148 : 17 : found = false; /* return unknown, not success */
2149 : 5478 : goto exit;
2150 : : }
2151 : : }
2791 akorotkov@postgresql 2152 [ + + ]: 15979 : else if (res == jpbTrue)
2153 : : {
2154 : 5679 : found = true;
231 tgl@sss.pgh.pa.us 2155 [ + + ]: 5679 : if (!jspStrictAbsenceOfErrors(cxt))
2156 : 5461 : goto exit;
2157 : : }
2158 : :
2791 akorotkov@postgresql 2159 : 12299 : first = false;
2160 [ + + ]: 12299 : if (rarg)
231 tgl@sss.pgh.pa.us 2161 : 12103 : rval = JsonValueListNext(&rseqit);
2162 : : }
2163 : : }
2164 : :
2165 : 61986 : exit:
2166 : 67515 : JsonValueListClear(&lseq);
2167 : 67515 : JsonValueListClear(&rseq);
2168 : :
2791 akorotkov@postgresql 2169 [ + + ]: 67515 : if (found) /* possible only in strict mode */
2170 : 5611 : return jpbTrue;
2171 : :
2172 [ + + ]: 61904 : if (error) /* possible only in lax mode */
2173 : 1827 : return jpbUnknown;
2174 : :
2175 : 60077 : return jpbFalse;
2176 : : }
2177 : :
2178 : : /*
2179 : : * Execute binary arithmetic expression on singleton numeric operands.
2180 : : * Array operands are automatically unwrapped in lax mode.
2181 : : */
2182 : : static JsonPathExecResult
2183 : 452 : executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
2184 : : JsonbValue *jb, BinaryArithmFunc func,
2185 : : JsonValueList *found)
2186 : : {
2187 : : JsonPathExecResult jper;
2188 : : JsonPathItem elem;
2189 : : JsonValueList lseq;
2190 : : JsonValueList rseq;
2191 : : JsonbValue *lval;
2192 : : JsonbValue *rval;
2193 : : JsonbValue resval;
2194 : : Numeric res;
2195 : :
231 tgl@sss.pgh.pa.us 2196 : 452 : JsonValueListInit(&lseq);
2197 : 452 : JsonValueListInit(&rseq);
2198 : :
2791 akorotkov@postgresql 2199 : 452 : jspGetLeftArg(jsp, &elem);
2200 : :
2201 : : /*
2202 : : * XXX: By standard only operands of multiplicative expressions are
2203 : : * unwrapped. We extend it to other binary arithmetic expressions too.
2204 : : */
2205 : 452 : jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &lseq);
2206 [ - + ]: 448 : if (jperIsError(jper))
2207 : : {
231 tgl@sss.pgh.pa.us 2208 :UBC 0 : JsonValueListClear(&lseq);
2209 : 0 : JsonValueListClear(&rseq);
2791 akorotkov@postgresql 2210 : 0 : return jper;
2211 : : }
2212 : :
2791 akorotkov@postgresql 2213 :CBC 448 : jspGetRightArg(jsp, &elem);
2214 : :
2215 : 448 : jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &rseq);
2216 [ - + ]: 444 : if (jperIsError(jper))
2217 : : {
231 tgl@sss.pgh.pa.us 2218 :UBC 0 : JsonValueListClear(&lseq);
2219 : 0 : JsonValueListClear(&rseq);
2791 akorotkov@postgresql 2220 : 0 : return jper;
2221 : : }
2222 : :
231 tgl@sss.pgh.pa.us 2223 [ + + - + ]:CBC 843 : if (!JsonValueListIsSingleton(&lseq) ||
2791 akorotkov@postgresql 2224 : 399 : !(lval = getScalar(JsonValueListHead(&lseq), jbvNumeric)))
2225 : : {
231 tgl@sss.pgh.pa.us 2226 : 45 : JsonValueListClear(&lseq);
2227 : 45 : JsonValueListClear(&rseq);
2791 akorotkov@postgresql 2228 [ + + + - ]: 45 : RETURN_ERROR(ereport(ERROR,
2229 : : (errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED),
2230 : : errmsg("left operand of jsonpath operator %s is not a single numeric value",
2231 : : jspOperationName(jsp->type)))));
2232 : : }
2233 : :
231 tgl@sss.pgh.pa.us 2234 [ + + + + ]: 775 : if (!JsonValueListIsSingleton(&rseq) ||
2791 akorotkov@postgresql 2235 : 376 : !(rval = getScalar(JsonValueListHead(&rseq), jbvNumeric)))
2236 : : {
231 tgl@sss.pgh.pa.us 2237 : 39 : JsonValueListClear(&lseq);
2238 : 39 : JsonValueListClear(&rseq);
2791 akorotkov@postgresql 2239 [ + + + - ]: 39 : RETURN_ERROR(ereport(ERROR,
2240 : : (errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED),
2241 : : errmsg("right operand of jsonpath operator %s is not a single numeric value",
2242 : : jspOperationName(jsp->type)))));
2243 : : }
2244 : :
2245 [ + + ]: 360 : if (jspThrowErrors(cxt))
2246 : : {
2247 : 68 : res = func(lval->val.numeric, rval->val.numeric, NULL);
2248 : : }
2249 : : else
2250 : : {
426 michael@paquier.xyz 2251 : 292 : ErrorSaveContext escontext = {T_ErrorSaveContext};
2252 : :
2253 : 292 : res = func(lval->val.numeric, rval->val.numeric, (Node *) &escontext);
2254 : :
2255 [ + + ]: 292 : if (escontext.error_occurred)
2256 : : {
231 tgl@sss.pgh.pa.us 2257 : 8 : JsonValueListClear(&lseq);
2258 : 8 : JsonValueListClear(&rseq);
2791 akorotkov@postgresql 2259 : 8 : return jperError;
2260 : : }
2261 : : }
2262 : :
231 tgl@sss.pgh.pa.us 2263 : 336 : JsonValueListClear(&lseq);
2264 : 336 : JsonValueListClear(&rseq);
2265 : :
2791 akorotkov@postgresql 2266 [ + + + + ]: 336 : if (!jspGetNext(jsp, &elem) && !found)
2267 : 5 : return jperOk;
2268 : :
231 tgl@sss.pgh.pa.us 2269 : 331 : resval.type = jbvNumeric;
2270 : 331 : resval.val.numeric = res;
2271 : :
2272 : 331 : return executeNextItem(cxt, jsp, &elem, &resval, found);
2273 : : }
2274 : :
2275 : : /*
2276 : : * Execute unary arithmetic expression for each numeric item in its operand's
2277 : : * sequence. Array operand is automatically unwrapped in lax mode.
2278 : : */
2279 : : static JsonPathExecResult
2791 akorotkov@postgresql 2280 : 148 : executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
2281 : : JsonbValue *jb, PGFunction func, JsonValueList *found)
2282 : : {
2283 : : JsonPathExecResult jper;
2284 : : JsonPathExecResult jper2;
2285 : : JsonPathItem elem;
2286 : : JsonValueList seq;
2287 : : JsonValueListIterator it;
2288 : : JsonbValue *val;
2289 : : bool hasNext;
2290 : :
231 tgl@sss.pgh.pa.us 2291 : 148 : JsonValueListInit(&seq);
2292 : :
2791 akorotkov@postgresql 2293 : 148 : jspGetArg(jsp, &elem);
2294 : 148 : jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &seq);
2295 : :
2296 [ - + ]: 144 : if (jperIsError(jper))
231 tgl@sss.pgh.pa.us 2297 :UBC 0 : goto exit;
2298 : :
2791 akorotkov@postgresql 2299 :CBC 144 : jper = jperNotFound;
2300 : :
2301 : 144 : hasNext = jspGetNext(jsp, &elem);
2302 : :
2303 : 144 : JsonValueListInitIterator(&seq, &it);
231 tgl@sss.pgh.pa.us 2304 [ + + ]: 258 : while ((val = JsonValueListNext(&it)))
2305 : : {
2791 akorotkov@postgresql 2306 [ + + ]: 150 : if ((val = getScalar(val, jbvNumeric)))
2307 : : {
2308 [ + + + - ]: 119 : if (!found && !hasNext)
2309 : : {
231 tgl@sss.pgh.pa.us 2310 : 10 : jper = jperOk;
2311 : 10 : goto exit;
2312 : : }
2313 : : }
2314 : : else
2315 : : {
2791 akorotkov@postgresql 2316 [ + + + - ]: 31 : if (!found && !hasNext)
2317 : 5 : continue; /* skip non-numerics processing */
2318 : :
231 tgl@sss.pgh.pa.us 2319 : 26 : JsonValueListClear(&seq);
2791 akorotkov@postgresql 2320 [ + + + - ]: 26 : RETURN_ERROR(ereport(ERROR,
2321 : : (errcode(ERRCODE_SQL_JSON_NUMBER_NOT_FOUND),
2322 : : errmsg("operand of unary jsonpath operator %s is not a numeric value",
2323 : : jspOperationName(jsp->type)))));
2324 : : }
2325 : :
2326 [ + + ]: 109 : if (func)
2327 : 66 : val->val.numeric =
2328 : 66 : DatumGetNumeric(DirectFunctionCall1(func,
2329 : : NumericGetDatum(val->val.numeric)));
2330 : :
231 tgl@sss.pgh.pa.us 2331 : 109 : jper2 = executeNextItem(cxt, jsp, &elem, val, found);
2332 : :
2791 akorotkov@postgresql 2333 [ - + ]: 109 : if (jperIsError(jper2))
2334 : : {
231 tgl@sss.pgh.pa.us 2335 :UBC 0 : jper = jper2;
2336 : 0 : goto exit;
2337 : : }
2338 : :
2791 akorotkov@postgresql 2339 [ + - ]:CBC 109 : if (jper2 == jperOk)
2340 : : {
2341 : 109 : jper = jperOk;
231 tgl@sss.pgh.pa.us 2342 [ - + ]: 109 : if (!found)
231 tgl@sss.pgh.pa.us 2343 :UBC 0 : goto exit;
2344 : : }
2345 : : }
2346 : :
231 tgl@sss.pgh.pa.us 2347 :CBC 108 : exit:
2348 : 118 : JsonValueListClear(&seq);
2349 : :
2791 akorotkov@postgresql 2350 : 118 : return jper;
2351 : : }
2352 : :
2353 : : /*
2354 : : * STARTS_WITH predicate callback.
2355 : : *
2356 : : * Check if the 'whole' string starts from 'initial' string.
2357 : : */
2358 : : static JsonPathBool
2359 : 128 : executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial,
2360 : : void *param)
2361 : : {
2362 [ + + ]: 128 : if (!(whole = getScalar(whole, jbvString)))
2363 : 32 : return jpbUnknown; /* error */
2364 : :
2365 [ - + ]: 96 : if (!(initial = getScalar(initial, jbvString)))
2791 akorotkov@postgresql 2366 :UBC 0 : return jpbUnknown; /* error */
2367 : :
2791 akorotkov@postgresql 2368 [ + + ]:CBC 96 : if (whole->val.string.len >= initial->val.string.len &&
2369 : 72 : !memcmp(whole->val.string.val,
2370 : 72 : initial->val.string.val,
2371 [ + + ]: 72 : initial->val.string.len))
2372 : 48 : return jpbTrue;
2373 : :
2374 : 48 : return jpbFalse;
2375 : : }
2376 : :
2377 : : /*
2378 : : * LIKE_REGEX predicate callback.
2379 : : *
2380 : : * Check if the string matches regex pattern.
2381 : : */
2382 : : static JsonPathBool
2383 : 264 : executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg,
2384 : : void *param)
2385 : : {
2386 : 264 : JsonLikeRegexContext *cxt = param;
2387 : :
2388 [ + + ]: 264 : if (!(str = getScalar(str, jbvString)))
2389 : 80 : return jpbUnknown;
2390 : :
2391 : : /* Cache regex text and converted flags. */
2392 [ + - ]: 184 : if (!cxt->regex)
2393 : : {
2394 : 184 : cxt->regex =
2395 : 184 : cstring_to_text_with_len(jsp->content.like_regex.pattern,
2396 : : jsp->content.like_regex.patternlen);
1412 andrew@dunslane.net 2397 : 184 : (void) jspConvertRegexFlags(jsp->content.like_regex.flags,
2398 : : &(cxt->cflags), NULL);
2399 : : }
2400 : :
2791 akorotkov@postgresql 2401 [ + + ]: 184 : if (RE_compile_and_execute(cxt->regex, str->val.string.val,
2402 : : str->val.string.len,
2403 : : cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL))
2404 : 68 : return jpbTrue;
2405 : :
2406 : 116 : return jpbFalse;
2407 : : }
2408 : :
2409 : : /*
2410 : : * Execute numeric item methods (.abs(), .floor(), .ceil()) using the specified
2411 : : * user function 'func'.
2412 : : */
2413 : : static JsonPathExecResult
2414 : 172 : executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
2415 : : JsonbValue *jb, bool unwrap, PGFunction func,
2416 : : JsonValueList *found)
2417 : : {
2418 : : JsonPathItem next;
2419 : : Datum datum;
2420 : : JsonbValue jbv;
2421 : :
2422 [ + - - + ]: 172 : if (unwrap && JsonbType(jb) == jbvArray)
2791 akorotkov@postgresql 2423 :UBC 0 : return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
2424 : :
2791 akorotkov@postgresql 2425 [ + + ]:CBC 172 : if (!(jb = getScalar(jb, jbvNumeric)))
2426 [ + + + - ]: 24 : RETURN_ERROR(ereport(ERROR,
2427 : : (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
2428 : : errmsg("jsonpath item method .%s() can only be applied to a numeric value",
2429 : : jspOperationName(jsp->type)))));
2430 : :
2748 tgl@sss.pgh.pa.us 2431 : 148 : datum = DirectFunctionCall1(func, NumericGetDatum(jb->val.numeric));
2432 : :
2791 akorotkov@postgresql 2433 [ + + - + ]: 148 : if (!jspGetNext(jsp, &next) && !found)
2791 akorotkov@postgresql 2434 :UBC 0 : return jperOk;
2435 : :
231 tgl@sss.pgh.pa.us 2436 :CBC 148 : jbv.type = jbvNumeric;
2437 : 148 : jbv.val.numeric = DatumGetNumeric(datum);
2438 : :
2439 : 148 : return executeNextItem(cxt, jsp, &next, &jbv, found);
2440 : : }
2441 : :
2442 : : /*
2443 : : * Implementation of the .datetime() and related methods.
2444 : : *
2445 : : * Converts a string into a date/time value. The actual type is determined at
2446 : : * run time.
2447 : : * If an argument is provided, this argument is used as a template string.
2448 : : * Otherwise, the first fitting ISO format is selected.
2449 : : *
2450 : : * .date(), .time(), .time_tz(), .timestamp(), .timestamp_tz() methods don't
2451 : : * have a format, so ISO format is used. However, except for .date(), they all
2452 : : * take an optional time precision.
2453 : : */
2454 : : static JsonPathExecResult
2598 akorotkov@postgresql 2455 : 5662 : executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
2456 : : JsonbValue *jb, JsonValueList *found)
2457 : : {
2458 : : JsonbValue jbv;
2459 : : Datum value;
2460 : : text *datetime;
2461 : : Oid collid;
2462 : : Oid typid;
2463 : 5662 : int32 typmod = -1;
2464 : 5662 : int tz = 0;
2465 : : bool hasNext;
2466 : 5662 : JsonPathExecResult res = jperNotFound;
2467 : : JsonPathItem elem;
1015 andrew@dunslane.net 2468 : 5662 : int32 time_precision = -1;
2469 : :
2598 akorotkov@postgresql 2470 [ + + ]: 5662 : if (!(jb = getScalar(jb, jbvString)))
2471 [ + - + - ]: 120 : RETURN_ERROR(ereport(ERROR,
2472 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2473 : : errmsg("jsonpath item method .%s() can only be applied to a string",
2474 : : jspOperationName(jsp->type)))));
2475 : :
2476 : 5542 : datetime = cstring_to_text_with_len(jb->val.string.val,
2477 : : jb->val.string.len);
2478 : :
2479 : : /*
2480 : : * At some point we might wish to have callers supply the collation to
2481 : : * use, but right now it's unclear that they'd be able to do better than
2482 : : * DEFAULT_COLLATION_OID anyway.
2483 : : */
2438 tgl@sss.pgh.pa.us 2484 : 5542 : collid = DEFAULT_COLLATION_OID;
2485 : :
2486 : : /*
2487 : : * .datetime(template) has an argument, the rest of the methods don't have
2488 : : * an argument. So we handle that separately.
2489 : : */
1015 andrew@dunslane.net 2490 [ + + + + ]: 5542 : if (jsp->type == jpiDatetime && jsp->content.arg)
2598 akorotkov@postgresql 2491 : 1061 : {
2492 : : text *template;
2493 : : char *template_str;
2494 : : int template_len;
1427 tgl@sss.pgh.pa.us 2495 : 1101 : ErrorSaveContext escontext = {T_ErrorSaveContext};
2496 : :
2598 akorotkov@postgresql 2497 : 1101 : jspGetArg(jsp, &elem);
2498 : :
2499 [ - + ]: 1101 : if (elem.type != jpiString)
2598 akorotkov@postgresql 2500 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonpath item type for .datetime() argument");
2501 : :
2598 akorotkov@postgresql 2502 :CBC 1101 : template_str = jspGetString(&elem, &template_len);
2503 : :
2504 : 1101 : template = cstring_to_text_with_len(template_str,
2505 : : template_len);
2506 : :
2438 tgl@sss.pgh.pa.us 2507 : 1101 : value = parse_datetime(datetime, template, collid, true,
2508 : : &typid, &typmod, &tz,
1427 2509 [ + + ]: 1101 : jspThrowErrors(cxt) ? NULL : (Node *) &escontext);
2510 : :
2511 [ - + ]: 1061 : if (escontext.error_occurred)
2598 akorotkov@postgresql 2512 :UBC 0 : res = jperError;
2513 : : else
2598 akorotkov@postgresql 2514 :CBC 1061 : res = jperOk;
2515 : : }
2516 : : else
2517 : : {
2518 : : /*
2519 : : * According to SQL/JSON standard enumerate ISO formats for: date,
2520 : : * timetz, time, timestamptz, timestamp.
2521 : : *
2522 : : * We also support ISO 8601 format (with "T") for timestamps, because
2523 : : * to_json[b]() functions use this format.
2524 : : */
2525 : : static const char *fmt_str[] =
2526 : : {
2527 : : "yyyy-mm-dd", /* date */
2528 : : "HH24:MI:SS.USTZ", /* timetz */
2529 : : "HH24:MI:SSTZ",
2530 : : "HH24:MI:SS.US", /* time without tz */
2531 : : "HH24:MI:SS",
2532 : : "yyyy-mm-dd HH24:MI:SS.USTZ", /* timestamptz */
2533 : : "yyyy-mm-dd HH24:MI:SSTZ",
2534 : : "yyyy-mm-dd\"T\"HH24:MI:SS.USTZ",
2535 : : "yyyy-mm-dd\"T\"HH24:MI:SSTZ",
2536 : : "yyyy-mm-dd HH24:MI:SS.US", /* timestamp without tz */
2537 : : "yyyy-mm-dd HH24:MI:SS",
2538 : : "yyyy-mm-dd\"T\"HH24:MI:SS.US",
2539 : : "yyyy-mm-dd\"T\"HH24:MI:SS"
2540 : : };
2541 : :
2542 : : /* cache for format texts */
2543 : : static text *fmt_txt[lengthof(fmt_str)] = {0};
2544 : :
2545 : : /*
2546 : : * Check for optional precision for methods other than .datetime() and
2547 : : * .date()
2548 : : */
1015 andrew@dunslane.net 2549 [ + + + + ]: 4441 : if (jsp->type != jpiDatetime && jsp->type != jpiDate &&
2550 [ + + ]: 2468 : jsp->content.arg)
2551 : : {
426 michael@paquier.xyz 2552 : 520 : ErrorSaveContext escontext = {T_ErrorSaveContext};
2553 : :
1015 andrew@dunslane.net 2554 : 520 : jspGetArg(jsp, &elem);
2555 : :
2556 [ - + ]: 520 : if (elem.type != jpiNumeric)
1015 andrew@dunslane.net 2557 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonpath item type for %s argument",
2558 : : jspOperationName(jsp->type));
2559 : :
426 michael@paquier.xyz 2560 :CBC 520 : time_precision = numeric_int4_safe(jspGetNumeric(&elem),
2561 : : (Node *) &escontext);
2562 [ + + ]: 520 : if (escontext.error_occurred)
1015 andrew@dunslane.net 2563 [ + - + - ]: 16 : RETURN_ERROR(ereport(ERROR,
2564 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2565 : : errmsg("time precision of jsonpath item method .%s() is out of range for type integer",
2566 : : jspOperationName(jsp->type)))));
2567 : : }
2568 : :
2569 : : /* loop until datetime format fits */
117 peter@eisentraut.org 2570 [ + + ]:GNC 25175 : for (size_t i = 0; i < lengthof(fmt_str); i++)
2571 : : {
1427 tgl@sss.pgh.pa.us 2572 :CBC 25143 : ErrorSaveContext escontext = {T_ErrorSaveContext};
2573 : :
2598 akorotkov@postgresql 2574 [ + + ]: 25143 : if (!fmt_txt[i])
2575 : : {
2576 : : MemoryContext oldcxt =
1266 tgl@sss.pgh.pa.us 2577 : 52 : MemoryContextSwitchTo(TopMemoryContext);
2578 : :
2598 akorotkov@postgresql 2579 : 52 : fmt_txt[i] = cstring_to_text(fmt_str[i]);
2580 : 52 : MemoryContextSwitchTo(oldcxt);
2581 : : }
2582 : :
2438 tgl@sss.pgh.pa.us 2583 : 25143 : value = parse_datetime(datetime, fmt_txt[i], collid, true,
2584 : : &typid, &typmod, &tz,
2585 : : (Node *) &escontext);
2586 : :
1427 2587 [ + + ]: 25143 : if (!escontext.error_occurred)
2588 : : {
2598 akorotkov@postgresql 2589 : 4393 : res = jperOk;
2590 : 4393 : break;
2591 : : }
2592 : : }
2593 : :
2594 [ + + ]: 4425 : if (res == jperNotFound)
2595 : : {
1015 andrew@dunslane.net 2596 [ + + ]: 32 : if (jsp->type == jpiDatetime)
2597 [ + - + - ]: 12 : RETURN_ERROR(ereport(ERROR,
2598 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2599 : : errmsg("%s format is not recognized: \"%s\"",
2600 : : "datetime", text_to_cstring(datetime)),
2601 : : errhint("Use a datetime template argument to specify the input data format."))));
2602 : : else
2603 [ + - + - ]: 20 : RETURN_ERROR(ereport(ERROR,
2604 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2605 : : errmsg("%s format is not recognized: \"%s\"",
2606 : : jspOperationName(jsp->type), text_to_cstring(datetime)))));
2607 : :
2608 : : }
2609 : : }
2610 : :
2611 : : /*
2612 : : * parse_datetime() processes the entire input string per the template or
2613 : : * ISO format and returns the Datum in best fitted datetime type. So, if
2614 : : * this call is for a specific datatype, then we do the conversion here.
2615 : : * Throw an error for incompatible types.
2616 : : */
2617 [ + + + + : 5454 : switch (jsp->type)
+ + - ]
2618 : : {
2619 : 2597 : case jpiDatetime: /* Nothing to do for DATETIME */
2620 : 2597 : break;
2621 : 421 : case jpiDate:
2622 : : {
2623 : : /* Convert result type to date */
2624 [ + + + + : 421 : switch (typid)
- ]
2625 : : {
2626 : 317 : case DATEOID: /* Nothing to do for DATE */
2627 : 317 : break;
2628 : 8 : case TIMEOID:
2629 : : case TIMETZOID:
2630 [ + - + - ]: 8 : RETURN_ERROR(ereport(ERROR,
2631 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2632 : : errmsg("%s format is not recognized: \"%s\"",
2633 : : "date", text_to_cstring(datetime)))));
2634 : : break;
2635 : 52 : case TIMESTAMPOID:
2636 : 52 : value = DirectFunctionCall1(timestamp_date,
2637 : : value);
2638 : 52 : break;
2639 : 44 : case TIMESTAMPTZOID:
999 2640 : 44 : checkTimezoneIsUsedForCast(cxt->useTz,
2641 : : "timestamptz", "date");
1015 2642 : 28 : value = DirectFunctionCall1(timestamptz_date,
2643 : : value);
2644 : 28 : break;
1015 andrew@dunslane.net 2645 :UBC 0 : default:
1010 peter@eisentraut.org 2646 [ # # ]: 0 : elog(ERROR, "type with oid %u not supported", typid);
2647 : : }
2648 : :
1015 andrew@dunslane.net 2649 :CBC 397 : typid = DATEOID;
2650 : : }
2651 : 397 : break;
2652 : 541 : case jpiTime:
2653 : : {
2654 : : /* Convert result type to time without time zone */
2655 [ + + + + : 541 : switch (typid)
+ - ]
2656 : : {
2657 : 4 : case DATEOID:
2658 [ + - + - ]: 4 : RETURN_ERROR(ereport(ERROR,
2659 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2660 : : errmsg("%s format is not recognized: \"%s\"",
2661 : : "time", text_to_cstring(datetime)))));
2662 : : break;
2663 : 405 : case TIMEOID: /* Nothing to do for TIME */
2664 : 405 : break;
2665 : 72 : case TIMETZOID:
999 2666 : 72 : checkTimezoneIsUsedForCast(cxt->useTz,
2667 : : "timetz", "time");
1015 2668 : 52 : value = DirectFunctionCall1(timetz_time,
2669 : : value);
2670 : 52 : break;
2671 : 20 : case TIMESTAMPOID:
2672 : 20 : value = DirectFunctionCall1(timestamp_time,
2673 : : value);
2674 : 20 : break;
2675 : 40 : case TIMESTAMPTZOID:
999 2676 : 40 : checkTimezoneIsUsedForCast(cxt->useTz,
2677 : : "timestamptz", "time");
1015 2678 : 28 : value = DirectFunctionCall1(timestamptz_time,
2679 : : value);
2680 : 28 : break;
1015 andrew@dunslane.net 2681 :UBC 0 : default:
1010 peter@eisentraut.org 2682 [ # # ]: 0 : elog(ERROR, "type with oid %u not supported", typid);
2683 : : }
2684 : :
2685 : : /* Force the user-given time precision, if any */
1015 andrew@dunslane.net 2686 [ + + ]:CBC 505 : if (time_precision != -1)
2687 : : {
2688 : : TimeADT result;
2689 : :
2690 : : /* Get a warning when precision is reduced */
2691 : 108 : time_precision = anytime_typmod_check(false,
2692 : : time_precision);
2693 : 108 : result = DatumGetTimeADT(value);
2694 : 108 : AdjustTimeForTypmod(&result, time_precision);
2695 : 108 : value = TimeADTGetDatum(result);
2696 : :
2697 : : /* Update the typmod value with the user-given precision */
2698 : 108 : typmod = time_precision;
2699 : : }
2700 : :
2701 : 505 : typid = TIMEOID;
2702 : : }
2703 : 505 : break;
2704 : 641 : case jpiTimeTz:
2705 : : {
2706 : : /* Convert result type to time with time zone */
2707 [ + + + + : 641 : switch (typid)
- ]
2708 : : {
2709 : 8 : case DATEOID:
2710 : : case TIMESTAMPOID:
2711 [ + - + - ]: 8 : RETURN_ERROR(ereport(ERROR,
2712 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2713 : : errmsg("%s format is not recognized: \"%s\"",
2714 : : "time_tz", text_to_cstring(datetime)))));
2715 : : break;
2716 : 76 : case TIMEOID:
999 2717 : 76 : checkTimezoneIsUsedForCast(cxt->useTz,
2718 : : "time", "timetz");
1015 2719 : 56 : value = DirectFunctionCall1(time_timetz,
2720 : : value);
2721 : 56 : break;
2722 : 529 : case TIMETZOID: /* Nothing to do for TIMETZ */
2723 : 529 : break;
2724 : 28 : case TIMESTAMPTZOID:
2725 : 28 : value = DirectFunctionCall1(timestamptz_timetz,
2726 : : value);
2727 : 28 : break;
1015 andrew@dunslane.net 2728 :UBC 0 : default:
1010 peter@eisentraut.org 2729 [ # # ]: 0 : elog(ERROR, "type with oid %u not supported", typid);
2730 : : }
2731 : :
2732 : : /* Force the user-given time precision, if any */
1015 andrew@dunslane.net 2733 [ + + ]:CBC 613 : if (time_precision != -1)
2734 : : {
2735 : : TimeTzADT *result;
2736 : :
2737 : : /* Get a warning when precision is reduced */
2738 : 132 : time_precision = anytime_typmod_check(true,
2739 : : time_precision);
2740 : 132 : result = DatumGetTimeTzADTP(value);
2741 : 132 : AdjustTimeForTypmod(&result->time, time_precision);
2742 : 132 : value = TimeTzADTPGetDatum(result);
2743 : :
2744 : : /* Update the typmod value with the user-given precision */
2745 : 132 : typmod = time_precision;
2746 : : }
2747 : :
2748 : 613 : typid = TIMETZOID;
2749 : : }
2750 : 613 : break;
2751 : 549 : case jpiTimestamp:
2752 : : {
2753 : : /* Convert result type to timestamp without time zone */
2754 [ + + + + : 549 : switch (typid)
- ]
2755 : : {
2756 : 36 : case DATEOID:
2757 : 36 : value = DirectFunctionCall1(date_timestamp,
2758 : : value);
2759 : 36 : break;
2760 : 8 : case TIMEOID:
2761 : : case TIMETZOID:
2762 [ + - + - ]: 8 : RETURN_ERROR(ereport(ERROR,
2763 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2764 : : errmsg("%s format is not recognized: \"%s\"",
2765 : : "timestamp", text_to_cstring(datetime)))));
2766 : : break;
2767 : 409 : case TIMESTAMPOID: /* Nothing to do for TIMESTAMP */
2768 : 409 : break;
2769 : 96 : case TIMESTAMPTZOID:
999 2770 : 96 : checkTimezoneIsUsedForCast(cxt->useTz,
2771 : : "timestamptz", "timestamp");
1015 2772 : 64 : value = DirectFunctionCall1(timestamptz_timestamp,
2773 : : value);
2774 : 64 : break;
1015 andrew@dunslane.net 2775 :UBC 0 : default:
1010 peter@eisentraut.org 2776 [ # # ]: 0 : elog(ERROR, "type with oid %u not supported", typid);
2777 : : }
2778 : :
2779 : : /* Force the user-given time precision, if any */
1015 andrew@dunslane.net 2780 [ + + ]:CBC 509 : if (time_precision != -1)
2781 : : {
2782 : : Timestamp result;
2783 : 108 : ErrorSaveContext escontext = {T_ErrorSaveContext};
2784 : :
2785 : : /* Get a warning when precision is reduced */
2786 : 108 : time_precision = anytimestamp_typmod_check(false,
2787 : : time_precision);
2788 : 108 : result = DatumGetTimestamp(value);
2789 : 108 : AdjustTimestampForTypmod(&result, time_precision,
2790 : : (Node *) &escontext);
982 2791 [ - + ]: 108 : if (escontext.error_occurred) /* should not happen */
1015 andrew@dunslane.net 2792 [ # # # # ]:UBC 0 : RETURN_ERROR(ereport(ERROR,
2793 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2794 : : errmsg("time precision of jsonpath item method .%s() is invalid",
2795 : : jspOperationName(jsp->type)))));
1015 andrew@dunslane.net 2796 :CBC 108 : value = TimestampGetDatum(result);
2797 : :
2798 : : /* Update the typmod value with the user-given precision */
2799 : 108 : typmod = time_precision;
2800 : : }
2801 : :
2802 : 509 : typid = TIMESTAMPOID;
2803 : : }
2804 : 509 : break;
2805 : 705 : case jpiTimestampTz:
2806 : : {
2807 : : struct pg_tm tm;
2808 : : fsec_t fsec;
2809 : :
2810 : : /* Convert result type to timestamp with time zone */
2811 [ + + + + : 705 : switch (typid)
- ]
2812 : : {
2813 : 40 : case DATEOID:
999 2814 : 40 : checkTimezoneIsUsedForCast(cxt->useTz,
2815 : : "date", "timestamptz");
2816 : :
2817 : : /*
2818 : : * Get the timezone value explicitly since JsonbValue
2819 : : * keeps that separate.
2820 : : */
828 2821 : 36 : j2date(DatumGetDateADT(value) + POSTGRES_EPOCH_JDATE,
2822 : : &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
2823 : 36 : tm.tm_hour = 0;
2824 : 36 : tm.tm_min = 0;
2825 : 36 : tm.tm_sec = 0;
2826 : 36 : tz = DetermineTimeZoneOffset(&tm, session_timezone);
2827 : :
1015 2828 : 36 : value = DirectFunctionCall1(date_timestamptz,
2829 : : value);
2830 : 36 : break;
2831 : 8 : case TIMEOID:
2832 : : case TIMETZOID:
2833 [ + - + - ]: 8 : RETURN_ERROR(ereport(ERROR,
2834 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2835 : : errmsg("%s format is not recognized: \"%s\"",
2836 : : "timestamp_tz", text_to_cstring(datetime)))));
2837 : : break;
2838 : 88 : case TIMESTAMPOID:
999 2839 : 88 : checkTimezoneIsUsedForCast(cxt->useTz,
2840 : : "timestamp", "timestamptz");
2841 : :
2842 : : /*
2843 : : * Get the timezone value explicitly since JsonbValue
2844 : : * keeps that separate.
2845 : : */
828 2846 [ + - ]: 60 : if (timestamp2tm(DatumGetTimestamp(value), NULL, &tm,
2847 : : &fsec, NULL, NULL) == 0)
2848 : 60 : tz = DetermineTimeZoneOffset(&tm,
2849 : : session_timezone);
2850 : :
1015 2851 : 60 : value = DirectFunctionCall1(timestamp_timestamptz,
2852 : : value);
2853 : 60 : break;
2854 : 569 : case TIMESTAMPTZOID: /* Nothing to do for TIMESTAMPTZ */
2855 : 569 : break;
1015 andrew@dunslane.net 2856 :UBC 0 : default:
1010 peter@eisentraut.org 2857 [ # # ]: 0 : elog(ERROR, "type with oid %u not supported", typid);
2858 : : }
2859 : :
2860 : : /* Force the user-given time precision, if any */
1015 andrew@dunslane.net 2861 [ + + ]:CBC 665 : if (time_precision != -1)
2862 : : {
2863 : : Timestamp result;
2864 : 140 : ErrorSaveContext escontext = {T_ErrorSaveContext};
2865 : :
2866 : : /* Get a warning when precision is reduced */
2867 : 140 : time_precision = anytimestamp_typmod_check(true,
2868 : : time_precision);
2869 : 140 : result = DatumGetTimestampTz(value);
2870 : 140 : AdjustTimestampForTypmod(&result, time_precision,
2871 : : (Node *) &escontext);
982 2872 [ - + ]: 140 : if (escontext.error_occurred) /* should not happen */
1015 andrew@dunslane.net 2873 [ # # # # ]:UBC 0 : RETURN_ERROR(ereport(ERROR,
2874 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
2875 : : errmsg("time precision of jsonpath item method .%s() is invalid",
2876 : : jspOperationName(jsp->type)))));
1015 andrew@dunslane.net 2877 :CBC 140 : value = TimestampTzGetDatum(result);
2878 : :
2879 : : /* Update the typmod value with the user-given precision */
2880 : 140 : typmod = time_precision;
2881 : : }
2882 : :
2883 : 665 : typid = TIMESTAMPTZOID;
2884 : : }
2885 : 665 : break;
1015 andrew@dunslane.net 2886 :UBC 0 : default:
2887 [ # # ]: 0 : elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
2888 : : }
2889 : :
2598 akorotkov@postgresql 2890 :CBC 5286 : pfree(datetime);
2891 : :
2892 [ - + ]: 5286 : if (jperIsError(res))
2598 akorotkov@postgresql 2893 :UBC 0 : return res;
2894 : :
2598 akorotkov@postgresql 2895 :CBC 5286 : hasNext = jspGetNext(jsp, &elem);
2896 : :
2897 [ + + + + ]: 5286 : if (!hasNext && !found)
2898 : 30 : return res;
2899 : :
231 tgl@sss.pgh.pa.us 2900 : 5256 : jbv.type = jbvDatetime;
2901 : 5256 : jbv.val.datetime.value = value;
2902 : 5256 : jbv.val.datetime.typid = typid;
2903 : 5256 : jbv.val.datetime.typmod = typmod;
2904 : 5256 : jbv.val.datetime.tz = tz;
2905 : :
2906 : 5256 : return executeNextItem(cxt, jsp, &elem, &jbv, found);
2907 : : }
2908 : :
2909 : : /*
2910 : : * Implementation of .upper(), .lower() et al. string methods,
2911 : : * that forward their actual implementation to internal functions.
2912 : : */
2913 : : static JsonPathExecResult
217 andrew@dunslane.net 2914 : 486 : executeStringInternalMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
2915 : : JsonbValue *jb, JsonValueList *found)
2916 : : {
2917 : : JsonbValue jbv;
2918 : : bool hasNext;
2919 : 486 : JsonPathExecResult res = jperNotFound;
2920 : : JsonPathItem elem;
2921 : : Datum str; /* Datum representation for the current string
2922 : : * value. The first argument to internal
2923 : : * functions */
2924 : 486 : char *resStr = NULL;
2925 : :
2926 [ + + + + : 486 : Assert(jsp->type == jpiStrReplace ||
+ + + + +
+ + + + +
- + ]
2927 : : jsp->type == jpiStrLower ||
2928 : : jsp->type == jpiStrUpper ||
2929 : : jsp->type == jpiStrLtrim ||
2930 : : jsp->type == jpiStrRtrim ||
2931 : : jsp->type == jpiStrBtrim ||
2932 : : jsp->type == jpiStrInitcap ||
2933 : : jsp->type == jpiStrSplitPart);
2934 : :
2935 [ + + ]: 486 : if (!(jb = getScalar(jb, jbvString)))
2936 [ + + + - ]: 200 : RETURN_ERROR(ereport(ERROR,
2937 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2938 : : errmsg("jsonpath item method .%s() can only be applied to a string",
2939 : : jspOperationName(jsp->type)))));
2940 : :
2941 : 286 : str = PointerGetDatum(cstring_to_text_with_len(jb->val.string.val, jb->val.string.len));
2942 : :
2943 : : /* Dispatch to the appropriate internal string function */
2944 [ + + + + : 286 : switch (jsp->type)
+ + - ]
2945 : : {
2946 : 40 : case jpiStrReplace:
2947 : : {
2948 : : char *from_str,
2949 : : *to_str;
2950 : :
2951 : 40 : jspGetLeftArg(jsp, &elem);
2952 [ - + ]: 40 : if (elem.type != jpiString)
217 andrew@dunslane.net 2953 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonpath item type for .replace() from");
2954 : :
217 andrew@dunslane.net 2955 :CBC 40 : from_str = jspGetString(&elem, NULL);
2956 : :
2957 : 40 : jspGetRightArg(jsp, &elem);
2958 [ - + ]: 40 : if (elem.type != jpiString)
217 andrew@dunslane.net 2959 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonpath item type for .replace() to");
2960 : :
217 andrew@dunslane.net 2961 :CBC 40 : to_str = jspGetString(&elem, NULL);
2962 : :
2963 : 40 : resStr = TextDatumGetCString(DirectFunctionCall3Coll(replace_text,
2964 : : DEFAULT_COLLATION_OID,
2965 : : str,
2966 : : CStringGetTextDatum(from_str),
2967 : : CStringGetTextDatum(to_str)));
2968 : 40 : break;
2969 : : }
2970 : 66 : case jpiStrLower:
2971 : 66 : resStr = TextDatumGetCString(DirectFunctionCall1Coll(lower, DEFAULT_COLLATION_OID, str));
2972 : 66 : break;
2973 : 62 : case jpiStrUpper:
2974 : 62 : resStr = TextDatumGetCString(DirectFunctionCall1Coll(upper, DEFAULT_COLLATION_OID, str));
2975 : 62 : break;
2976 : 78 : case jpiStrLtrim:
2977 : : case jpiStrRtrim:
2978 : : case jpiStrBtrim:
2979 : : {
2980 : 78 : PGFunction func1 = NULL;
2981 : 78 : PGFunction func2 = NULL;
2982 : :
2983 [ + + + - ]: 78 : switch (jsp->type)
2984 : : {
2985 : 50 : case jpiStrLtrim:
2986 : 50 : func1 = ltrim1;
2987 : 50 : func2 = ltrim;
2988 : 50 : break;
2989 : 12 : case jpiStrRtrim:
2990 : 12 : func1 = rtrim1;
2991 : 12 : func2 = rtrim;
2992 : 12 : break;
2993 : 16 : case jpiStrBtrim:
2994 : 16 : func1 = btrim1;
2995 : 16 : func2 = btrim;
2996 : 16 : break;
217 andrew@dunslane.net 2997 :UBC 0 : default:
2998 : 0 : break;
2999 : : }
3000 : :
217 andrew@dunslane.net 3001 [ + + ]:CBC 78 : if (jsp->content.arg)
3002 : : {
3003 : : char *characters_str;
3004 : :
3005 : 24 : jspGetArg(jsp, &elem);
3006 [ - + ]: 24 : if (elem.type != jpiString)
217 andrew@dunslane.net 3007 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonpath item type for .%s() argument",
3008 : : jspOperationName(jsp->type));
3009 : :
217 andrew@dunslane.net 3010 :CBC 24 : characters_str = jspGetString(&elem, NULL);
3011 : 24 : resStr = TextDatumGetCString(DirectFunctionCall2Coll(func2,
3012 : : DEFAULT_COLLATION_OID, str,
3013 : : CStringGetTextDatum(characters_str)));
3014 : : }
3015 : : else
3016 : : {
3017 : 54 : resStr = TextDatumGetCString(DirectFunctionCall1Coll(func1,
3018 : : DEFAULT_COLLATION_OID, str));
3019 : : }
3020 : 78 : break;
3021 : : }
3022 : :
3023 : 16 : case jpiStrInitcap:
3024 : 16 : resStr = TextDatumGetCString(DirectFunctionCall1Coll(initcap, DEFAULT_COLLATION_OID, str));
3025 : 16 : break;
3026 : 24 : case jpiStrSplitPart:
3027 : : {
3028 : : char *from_str;
3029 : : int32 n;
175 michael@paquier.xyz 3030 : 24 : ErrorSaveContext escontext = {T_ErrorSaveContext};
3031 : :
217 andrew@dunslane.net 3032 : 24 : jspGetLeftArg(jsp, &elem);
3033 [ - + ]: 24 : if (elem.type != jpiString)
217 andrew@dunslane.net 3034 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonpath item type for .split_part()");
3035 : :
217 andrew@dunslane.net 3036 :CBC 24 : from_str = jspGetString(&elem, NULL);
3037 : :
3038 : 24 : jspGetRightArg(jsp, &elem);
3039 [ - + ]: 24 : if (elem.type != jpiNumeric)
217 andrew@dunslane.net 3040 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonpath item type for .split_part()");
3041 : :
175 michael@paquier.xyz 3042 :CBC 24 : n = numeric_int4_safe(jspGetNumeric(&elem),
3043 : : (Node *) &escontext);
3044 [ + + ]: 24 : if (escontext.error_occurred)
3045 [ + + + - ]: 12 : RETURN_ERROR(ereport(ERROR,
3046 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3047 : : errmsg("field position of jsonpath item method .%s() is out of range for type integer",
3048 : : jspOperationName(jsp->type))));
3049 : :
3050 [ + + ]: 16 : if (n == 0)
3051 [ + + + - ]: 8 : RETURN_ERROR(ereport(ERROR,
3052 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3053 : : errmsg("field position of jsonpath item method .%s() must not be zero",
3054 : : jspOperationName(jsp->type))));
3055 : :
217 andrew@dunslane.net 3056 : 8 : resStr = TextDatumGetCString(DirectFunctionCall3Coll(split_part,
3057 : : DEFAULT_COLLATION_OID,
3058 : : str,
3059 : : CStringGetTextDatum(from_str),
3060 : : Int32GetDatum(n)));
3061 : 8 : break;
3062 : : }
217 andrew@dunslane.net 3063 :UBC 0 : default:
3064 [ # # ]: 0 : elog(ERROR, "unsupported jsonpath item type: %d", jsp->type);
3065 : : }
3066 : :
217 andrew@dunslane.net 3067 [ + - ]:CBC 270 : if (resStr)
3068 : 270 : res = jperOk;
3069 : :
3070 : 270 : hasNext = jspGetNext(jsp, &elem);
3071 : :
3072 [ + + - + ]: 270 : if (!hasNext && !found)
217 andrew@dunslane.net 3073 :UBC 0 : return res;
3074 : :
217 andrew@dunslane.net 3075 :CBC 270 : jbv.type = jbvString;
3076 : 270 : jbv.val.string.val = resStr;
3077 : 270 : jbv.val.string.len = strlen(resStr);
3078 : :
3079 : 270 : return executeNextItem(cxt, jsp, &elem, &jbv, found);
3080 : : }
3081 : :
3082 : : /*
3083 : : * Implementation of .keyvalue() method.
3084 : : *
3085 : : * .keyvalue() method returns a sequence of object's key-value pairs in the
3086 : : * following format: '{ "key": key, "value": value, "id": id }'.
3087 : : *
3088 : : * "id" field is an object identifier which is constructed from the two parts:
3089 : : * base object id and its binary offset in base object's jsonb:
3090 : : * id = 10000000000 * base_object_id + obj_offset_in_base_object
3091 : : *
3092 : : * 10000000000 (10^10) -- is a first round decimal number greater than 2^32
3093 : : * (maximal offset in jsonb). Decimal multiplier is used here to improve the
3094 : : * readability of identifiers.
3095 : : *
3096 : : * Base object is usually a root object of the path: context item '$' or path
3097 : : * variable '$var', literals can't produce objects for now. But if the path
3098 : : * contains generated objects (.keyvalue() itself, for example), then they
3099 : : * become base object for the subsequent .keyvalue().
3100 : : *
3101 : : * Id of '$' is 0. Id of '$var' is its ordinal (positive) number in the list
3102 : : * of variables (see getJsonPathVariable()). Ids for generated objects
3103 : : * are assigned using global counter JsonPathExecContext.lastGeneratedObjectId.
3104 : : */
3105 : : static JsonPathExecResult
2791 akorotkov@postgresql 3106 : 58 : executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
3107 : : JsonbValue *jb, JsonValueList *found)
3108 : : {
3109 : 58 : JsonPathExecResult res = jperNotFound;
3110 : : JsonPathItem next;
3111 : : JsonbContainer *jbc;
3112 : : JsonbValue key;
3113 : : JsonbValue val;
3114 : : JsonbValue idval;
3115 : : JsonbValue keystr;
3116 : : JsonbValue valstr;
3117 : : JsonbValue idstr;
3118 : : JsonbIterator *it;
3119 : : JsonbIteratorToken tok;
3120 : : int64 id;
3121 : : bool hasNext;
3122 : :
3123 [ + + - + ]: 58 : if (JsonbType(jb) != jbvObject || jb->type != jbvBinary)
3124 [ + + + - ]: 16 : RETURN_ERROR(ereport(ERROR,
3125 : : (errcode(ERRCODE_SQL_JSON_OBJECT_NOT_FOUND),
3126 : : errmsg("jsonpath item method .%s() can only be applied to an object",
3127 : : jspOperationName(jsp->type)))));
3128 : :
3129 : 42 : jbc = jb->val.binary.data;
3130 : :
3131 [ + + ]: 42 : if (!JsonContainerSize(jbc))
3132 : 12 : return jperNotFound; /* no key-value pairs */
3133 : :
3134 : 30 : hasNext = jspGetNext(jsp, &next);
3135 : :
3136 : 30 : keystr.type = jbvString;
3137 : 30 : keystr.val.string.val = "key";
3138 : 30 : keystr.val.string.len = 3;
3139 : :
3140 : 30 : valstr.type = jbvString;
3141 : 30 : valstr.val.string.val = "value";
3142 : 30 : valstr.val.string.len = 5;
3143 : :
3144 : 30 : idstr.type = jbvString;
3145 : 30 : idstr.val.string.val = "id";
3146 : 30 : idstr.val.string.len = 2;
3147 : :
3148 : : /* construct object id from its base object and offset inside that */
3149 [ + - ]: 30 : id = jb->type != jbvBinary ? 0 :
3150 : 30 : (int64) ((char *) jbc - (char *) cxt->baseObject.jbc);
3151 : 30 : id += (int64) cxt->baseObject.id * INT64CONST(10000000000);
3152 : :
3153 : 30 : idval.type = jbvNumeric;
2248 peter@eisentraut.org 3154 : 30 : idval.val.numeric = int64_to_numeric(id);
3155 : :
2791 akorotkov@postgresql 3156 : 30 : it = JsonbIteratorInit(jbc);
3157 : :
3158 [ + + ]: 116 : while ((tok = JsonbIteratorNext(&it, &key, true)) != WJB_DONE)
3159 : : {
3160 : : JsonBaseObjectInfo baseObject;
3161 : : JsonbValue obj;
3162 : : JsonbInState ps;
3163 : : Jsonb *jsonb;
3164 : :
3165 [ + + ]: 96 : if (tok != WJB_KEY)
3166 : 50 : continue;
3167 : :
3168 : 46 : res = jperOk;
3169 : :
3170 [ + + + + ]: 46 : if (!hasNext && !found)
3171 : 10 : break;
3172 : :
3173 : 41 : tok = JsonbIteratorNext(&it, &val, true);
3174 [ - + ]: 41 : Assert(tok == WJB_VALUE);
3175 : :
333 tgl@sss.pgh.pa.us 3176 : 41 : memset(&ps, 0, sizeof(ps));
3177 : :
2791 akorotkov@postgresql 3178 : 41 : pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
3179 : :
3180 : 41 : pushJsonbValue(&ps, WJB_KEY, &keystr);
3181 : 41 : pushJsonbValue(&ps, WJB_VALUE, &key);
3182 : :
3183 : 41 : pushJsonbValue(&ps, WJB_KEY, &valstr);
3184 : 41 : pushJsonbValue(&ps, WJB_VALUE, &val);
3185 : :
3186 : 41 : pushJsonbValue(&ps, WJB_KEY, &idstr);
3187 : 41 : pushJsonbValue(&ps, WJB_VALUE, &idval);
3188 : :
333 tgl@sss.pgh.pa.us 3189 : 41 : pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
3190 : :
3191 : 41 : jsonb = JsonbValueToJsonb(ps.result);
3192 : :
2791 akorotkov@postgresql 3193 : 41 : JsonbInitBinary(&obj, jsonb);
3194 : :
3195 : 41 : baseObject = setBaseObject(cxt, &obj, cxt->lastGeneratedObjectId++);
3196 : :
231 tgl@sss.pgh.pa.us 3197 : 41 : res = executeNextItem(cxt, jsp, &next, &obj, found);
3198 : :
2791 akorotkov@postgresql 3199 : 41 : cxt->baseObject = baseObject;
3200 : :
3201 [ - + ]: 41 : if (jperIsError(res))
2791 akorotkov@postgresql 3202 :UBC 0 : return res;
3203 : :
2791 akorotkov@postgresql 3204 [ + - + + ]:CBC 41 : if (res == jperOk && !found)
3205 : 5 : break;
3206 : : }
3207 : :
3208 : 30 : return res;
3209 : : }
3210 : :
3211 : : /*
3212 : : * Convert boolean execution status 'res' to a boolean JSON item and execute
3213 : : * next jsonpath.
3214 : : */
3215 : : static JsonPathExecResult
3216 : 88387 : appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
3217 : : JsonValueList *found, JsonPathBool res)
3218 : : {
3219 : : JsonPathItem next;
3220 : : JsonbValue jbv;
3221 : :
3222 [ + + + + ]: 88387 : if (!jspGetNext(jsp, &next) && !found)
3223 : 13 : return jperOk; /* found singleton boolean value */
3224 : :
3225 [ + + ]: 88374 : if (res == jpbUnknown)
3226 : : {
3227 : 1319 : jbv.type = jbvNull;
3228 : : }
3229 : : else
3230 : : {
3231 : 87055 : jbv.type = jbvBool;
3232 : 87055 : jbv.val.boolean = res == jpbTrue;
3233 : : }
3234 : :
231 tgl@sss.pgh.pa.us 3235 : 88374 : return executeNextItem(cxt, jsp, &next, &jbv, found);
3236 : : }
3237 : :
3238 : : /*
3239 : : * Convert jsonpath's scalar or variable node to actual jsonb value.
3240 : : *
3241 : : * If node is a variable then its id returned, otherwise 0 returned.
3242 : : */
3243 : : static void
2791 akorotkov@postgresql 3244 : 68485 : getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
3245 : : JsonbValue *value)
3246 : : {
3247 [ + + + + : 68485 : switch (item->type)
+ - ]
3248 : : {
3249 : 5036 : case jpiNull:
3250 : 5036 : value->type = jbvNull;
3251 : 5036 : break;
3252 : 9046 : case jpiBool:
3253 : 9046 : value->type = jbvBool;
3254 : 9046 : value->val.boolean = jspGetBool(item);
3255 : 9046 : break;
3256 : 20688 : case jpiNumeric:
3257 : 20688 : value->type = jbvNumeric;
3258 : 20688 : value->val.numeric = jspGetNumeric(item);
3259 : 20688 : break;
3260 : 28275 : case jpiString:
3261 : 28275 : value->type = jbvString;
3262 : 56550 : value->val.string.val = jspGetString(item,
3263 : 28275 : &value->val.string.len);
3264 : 28275 : break;
3265 : 5440 : case jpiVariable:
1016 amitlan@postgresql.o 3266 : 5440 : getJsonPathVariable(cxt, item, value);
2791 akorotkov@postgresql 3267 : 5408 : return;
2791 akorotkov@postgresql 3268 :UBC 0 : default:
3269 [ # # ]: 0 : elog(ERROR, "unexpected jsonpath item type");
3270 : : }
3271 : : }
3272 : :
3273 : : /*
3274 : : * Returns the computed value of a JSON path variable with given name.
3275 : : */
3276 : : static JsonbValue *
959 amitlan@postgresql.o 3277 :CBC 1904 : GetJsonPathVar(void *cxt, char *varName, int varNameLen,
3278 : : JsonbValue *baseObject, int *baseObjectId)
3279 : : {
3280 : 1904 : JsonPathVariable *var = NULL;
3281 : 1904 : List *vars = cxt;
3282 : : ListCell *lc;
3283 : : JsonbValue *result;
3284 : 1904 : int id = 1;
3285 : :
3286 [ + - + + : 2828 : foreach(lc, vars)
+ + ]
3287 : : {
3288 : 2816 : JsonPathVariable *curvar = lfirst(lc);
3289 : :
869 3290 [ + + ]: 2816 : if (curvar->namelen == varNameLen &&
3291 [ + + ]: 2736 : strncmp(curvar->name, varName, varNameLen) == 0)
3292 : : {
959 3293 : 1892 : var = curvar;
3294 : 1892 : break;
3295 : : }
3296 : :
3297 : 924 : id++;
3298 : : }
3299 : :
3300 [ + + ]: 1904 : if (var == NULL)
3301 : : {
3302 : 12 : *baseObjectId = -1;
3303 : 12 : return NULL;
3304 : : }
3305 : :
330 michael@paquier.xyz 3306 : 1892 : result = palloc_object(JsonbValue);
959 amitlan@postgresql.o 3307 [ - + ]: 1892 : if (var->isnull)
3308 : : {
959 amitlan@postgresql.o 3309 :UBC 0 : *baseObjectId = 0;
3310 : 0 : result->type = jbvNull;
3311 : : }
3312 : : else
959 amitlan@postgresql.o 3313 :CBC 1892 : JsonItemFromDatum(var->value, var->typid, var->typmod, result);
3314 : :
3315 : 1892 : *baseObject = *result;
3316 : 1892 : *baseObjectId = id;
3317 : :
3318 : 1892 : return result;
3319 : : }
3320 : :
3321 : : static int
3322 : 6238 : CountJsonPathVars(void *cxt)
3323 : : {
3324 : 6238 : List *vars = (List *) cxt;
3325 : :
3326 : 6238 : return list_length(vars);
3327 : : }
3328 : :
3329 : :
3330 : : /*
3331 : : * Initialize JsonbValue to pass to jsonpath executor from given
3332 : : * datum value of the specified type.
3333 : : */
3334 : : static void
3335 : 1892 : JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonbValue *res)
3336 : : {
3337 [ + - - + : 1892 : switch (typid)
- - - + +
+ - - ]
3338 : : {
3339 : 48 : case BOOLOID:
3340 : 48 : res->type = jbvBool;
3341 : 48 : res->val.boolean = DatumGetBool(val);
3342 : 48 : break;
959 amitlan@postgresql.o 3343 :UBC 0 : case NUMERICOID:
3344 : 0 : JsonbValueInitNumericDatum(res, val);
3345 : 0 : break;
3346 : 0 : case INT2OID:
3347 : 0 : JsonbValueInitNumericDatum(res, DirectFunctionCall1(int2_numeric, val));
3348 : 0 : break;
959 amitlan@postgresql.o 3349 :CBC 1744 : case INT4OID:
3350 : 1744 : JsonbValueInitNumericDatum(res, DirectFunctionCall1(int4_numeric, val));
3351 : 1744 : break;
959 amitlan@postgresql.o 3352 :UBC 0 : case INT8OID:
3353 : 0 : JsonbValueInitNumericDatum(res, DirectFunctionCall1(int8_numeric, val));
3354 : 0 : break;
3355 : 0 : case FLOAT4OID:
3356 : 0 : JsonbValueInitNumericDatum(res, DirectFunctionCall1(float4_numeric, val));
3357 : 0 : break;
3358 : 0 : case FLOAT8OID:
3359 : 0 : JsonbValueInitNumericDatum(res, DirectFunctionCall1(float8_numeric, val));
3360 : 0 : break;
959 amitlan@postgresql.o 3361 :CBC 16 : case TEXTOID:
3362 : : case VARCHAROID:
3363 : : {
47 michael@paquier.xyz 3364 : 16 : text *txt = DatumGetTextPP(val);
3365 : :
3366 : 16 : res->type = jbvString;
3367 : 16 : res->val.string.val = VARDATA_ANY(txt);
3368 : 16 : res->val.string.len = VARSIZE_ANY_EXHDR(txt);
3369 : 16 : break;
3370 : : }
959 amitlan@postgresql.o 3371 : 48 : case DATEOID:
3372 : : case TIMEOID:
3373 : : case TIMETZOID:
3374 : : case TIMESTAMPOID:
3375 : : case TIMESTAMPTZOID:
3376 : 48 : res->type = jbvDatetime;
3377 : 48 : res->val.datetime.value = val;
3378 : 48 : res->val.datetime.typid = typid;
3379 : 48 : res->val.datetime.typmod = typmod;
3380 : 48 : res->val.datetime.tz = 0;
3381 : 48 : break;
3382 : 36 : case JSONBOID:
3383 : : {
3384 : 36 : JsonbValue *jbv = res;
3385 : 36 : Jsonb *jb = DatumGetJsonbP(val);
3386 : :
3387 [ + + ]: 36 : if (JsonContainerIsScalar(&jb->root))
3388 : : {
3389 : : bool result PG_USED_FOR_ASSERTS_ONLY;
3390 : :
3391 : 12 : result = JsonbExtractScalar(&jb->root, jbv);
3392 [ - + ]: 12 : Assert(result);
3393 : : }
3394 : : else
3395 : 24 : JsonbInitBinary(jbv, jb);
3396 : 36 : break;
3397 : : }
959 amitlan@postgresql.o 3398 :UBC 0 : case JSONOID:
3399 : : {
3400 : 0 : text *txt = DatumGetTextP(val);
3401 : 0 : char *str = text_to_cstring(txt);
3402 : : Jsonb *jb;
3403 : :
3404 : 0 : jb = DatumGetJsonbP(DirectFunctionCall1(jsonb_in,
3405 : : CStringGetDatum(str)));
3406 : 0 : pfree(str);
3407 : :
3408 : 0 : JsonItemFromDatum(JsonbPGetDatum(jb), JSONBOID, -1, res);
3409 : 0 : break;
3410 : : }
3411 : 0 : default:
3412 [ # # ]: 0 : ereport(ERROR,
3413 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3414 : : errmsg("could not convert value of type %s to jsonpath",
3415 : : format_type_be(typid)));
3416 : : }
959 amitlan@postgresql.o 3417 :CBC 1892 : }
3418 : :
3419 : : /* Initialize numeric value from the given datum */
3420 : : static void
3421 : 1744 : JsonbValueInitNumericDatum(JsonbValue *jbv, Datum num)
3422 : : {
3423 : 1744 : jbv->type = jbvNumeric;
3424 : 1744 : jbv->val.numeric = DatumGetNumeric(num);
3425 : 1744 : }
3426 : :
3427 : : /*
3428 : : * Get the value of variable passed to jsonpath executor
3429 : : */
3430 : : static void
2791 akorotkov@postgresql 3431 : 5440 : getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable,
3432 : : JsonbValue *value)
3433 : : {
3434 : : char *varName;
3435 : : int varNameLength;
3436 : : JsonbValue baseObject;
3437 : : int baseObjectId;
3438 : : JsonbValue *v;
3439 : :
1016 amitlan@postgresql.o 3440 [ - + ]: 5440 : Assert(variable->type == jpiVariable);
3441 : 5440 : varName = jspGetString(variable, &varNameLength);
3442 : :
3443 [ + - + + ]: 10880 : if (cxt->vars == NULL ||
3444 : 5440 : (v = cxt->getVar(cxt->vars, varName, varNameLength,
3445 : : &baseObject, &baseObjectId)) == NULL)
3446 [ + - ]: 32 : ereport(ERROR,
3447 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
3448 : : errmsg("could not find jsonpath variable \"%s\"",
3449 : : pnstrdup(varName, varNameLength))));
3450 : :
3451 [ + - ]: 5408 : if (baseObjectId > 0)
3452 : : {
3453 : 5408 : *value = *v;
3454 : 5408 : setBaseObject(cxt, &baseObject, baseObjectId);
3455 : : }
3456 : 5408 : }
3457 : :
3458 : : /*
3459 : : * Definition of JsonPathGetVarCallback for when JsonPathExecContext.vars
3460 : : * is specified as a jsonb value.
3461 : : */
3462 : : static JsonbValue *
3463 : 3536 : getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength,
3464 : : JsonbValue *baseObject, int *baseObjectId)
3465 : : {
3466 : 3536 : Jsonb *vars = varsJsonb;
3467 : : JsonbValue tmp;
3468 : : JsonbValue *result;
3469 : :
2791 akorotkov@postgresql 3470 : 3536 : tmp.type = jbvString;
3471 : 3536 : tmp.val.string.val = varName;
3472 : 3536 : tmp.val.string.len = varNameLength;
3473 : :
1016 amitlan@postgresql.o 3474 : 3536 : result = findJsonbValueFromContainer(&vars->root, JB_FOBJECT, &tmp);
3475 : :
3476 [ + + ]: 3536 : if (result == NULL)
3477 : : {
3478 : 20 : *baseObjectId = -1;
3479 : 20 : return NULL;
3480 : : }
3481 : :
3482 : 3516 : *baseObjectId = 1;
3483 : 3516 : JsonbInitBinary(baseObject, vars);
3484 : :
3485 : 3516 : return result;
3486 : : }
3487 : :
3488 : : /*
3489 : : * Definition of JsonPathCountVarsCallback for when JsonPathExecContext.vars
3490 : : * is specified as a jsonb value.
3491 : : */
3492 : : static int
3493 : 149111 : countVariablesFromJsonb(void *varsJsonb)
3494 : : {
3495 : 149111 : Jsonb *vars = varsJsonb;
3496 : :
3497 [ + + + + ]: 149111 : if (vars && !JsonContainerIsObject(&vars->root))
3498 : : {
1526 andrew@dunslane.net 3499 [ + - ]: 8 : ereport(ERROR,
3500 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3501 : : errmsg("\"vars\" argument is not an object"),
3502 : : errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object."));
3503 : : }
3504 : :
3505 : : /* count of base objects */
1016 amitlan@postgresql.o 3506 : 149103 : return vars != NULL ? 1 : 0;
3507 : : }
3508 : :
3509 : : /**************** Support functions for JsonPath execution *****************/
3510 : :
3511 : : /*
3512 : : * Returns the size of an array item, or -1 if item is not an array.
3513 : : */
3514 : : static int
2791 akorotkov@postgresql 3515 : 381 : JsonbArraySize(JsonbValue *jb)
3516 : : {
3517 [ - + ]: 381 : Assert(jb->type != jbvArray);
3518 : :
3519 [ + + ]: 381 : if (jb->type == jbvBinary)
3520 : : {
3521 : 353 : JsonbContainer *jbc = jb->val.binary.data;
3522 : :
3523 [ + + + - ]: 353 : if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
3524 : 345 : return JsonContainerSize(jbc);
3525 : : }
3526 : :
3527 : 36 : return -1;
3528 : : }
3529 : :
3530 : : /* Comparison predicate callback. */
3531 : : static JsonPathBool
3532 : 17445 : executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
3533 : : {
2598 3534 : 17445 : JsonPathExecContext *cxt = (JsonPathExecContext *) p;
3535 : :
3536 : 17445 : return compareItems(cmp->type, lv, rv, cxt->useTz);
3537 : : }
3538 : :
3539 : : /*
3540 : : * Perform per-byte comparison of two strings.
3541 : : */
3542 : : static int
2643 3543 : 2304 : binaryCompareStrings(const char *s1, int len1,
3544 : : const char *s2, int len2)
3545 : : {
3546 : : int cmp;
3547 : :
3548 : 2304 : cmp = memcmp(s1, s2, Min(len1, len2));
3549 : :
3550 [ + + ]: 2304 : if (cmp != 0)
3551 : 1312 : return cmp;
3552 : :
3553 [ + + ]: 992 : if (len1 == len2)
3554 : 192 : return 0;
3555 : :
3556 [ + + ]: 800 : return len1 < len2 ? -1 : 1;
3557 : : }
3558 : :
3559 : : /*
3560 : : * Compare two strings in the current server encoding using Unicode codepoint
3561 : : * collation.
3562 : : */
3563 : : static int
3564 : 2304 : compareStrings(const char *mbstr1, int mblen1,
3565 : : const char *mbstr2, int mblen2)
3566 : : {
3567 [ + - + - ]: 4608 : if (GetDatabaseEncoding() == PG_SQL_ASCII ||
3568 : 2304 : GetDatabaseEncoding() == PG_UTF8)
3569 : : {
3570 : : /*
3571 : : * It's known property of UTF-8 strings that their per-byte comparison
3572 : : * result matches codepoints comparison result. ASCII can be
3573 : : * considered as special case of UTF-8.
3574 : : */
3575 : 2304 : return binaryCompareStrings(mbstr1, mblen1, mbstr2, mblen2);
3576 : : }
3577 : : else
3578 : : {
3579 : : char *utf8str1,
3580 : : *utf8str2;
3581 : : int cmp,
3582 : : utf8len1,
3583 : : utf8len2;
3584 : :
3585 : : /*
3586 : : * We have to convert other encodings to UTF-8 first, then compare.
3587 : : * Input strings may be not null-terminated and pg_server_to_any() may
3588 : : * return them "as is". So, use strlen() only if there is real
3589 : : * conversion.
3590 : : */
2642 akorotkov@postgresql 3591 :UBC 0 : utf8str1 = pg_server_to_any(mbstr1, mblen1, PG_UTF8);
3592 : 0 : utf8str2 = pg_server_to_any(mbstr2, mblen2, PG_UTF8);
3593 [ # # ]: 0 : utf8len1 = (mbstr1 == utf8str1) ? mblen1 : strlen(utf8str1);
3594 [ # # ]: 0 : utf8len2 = (mbstr2 == utf8str2) ? mblen2 : strlen(utf8str2);
3595 : :
3596 : 0 : cmp = binaryCompareStrings(utf8str1, utf8len1, utf8str2, utf8len2);
3597 : :
3598 : : /*
3599 : : * If pg_server_to_any() did no real conversion, then we actually
3600 : : * compared original strings. So, we already done.
3601 : : */
3602 [ # # # # ]: 0 : if (mbstr1 == utf8str1 && mbstr2 == utf8str2)
3603 : 0 : return cmp;
3604 : :
3605 : : /* Free memory if needed */
3606 [ # # ]: 0 : if (mbstr1 != utf8str1)
3607 : 0 : pfree(utf8str1);
3608 [ # # ]: 0 : if (mbstr2 != utf8str2)
3609 : 0 : pfree(utf8str2);
3610 : :
3611 : : /*
3612 : : * When all Unicode codepoints are equal, return result of binary
3613 : : * comparison. In some edge cases, same characters may have different
3614 : : * representations in encoding. Then our behavior could diverge from
3615 : : * standard. However, that allow us to do simple binary comparison
3616 : : * for "==" operator, which is performance critical in typical cases.
3617 : : * In future to implement strict standard conformance, we can do
3618 : : * normalization of input JSON strings.
3619 : : */
2643 3620 [ # # ]: 0 : if (cmp == 0)
3621 : 0 : return binaryCompareStrings(mbstr1, mblen1, mbstr2, mblen2);
3622 : : else
3623 : 0 : return cmp;
3624 : : }
3625 : : }
3626 : :
3627 : : /*
3628 : : * Compare two SQL/JSON items using comparison operation 'op'.
3629 : : */
3630 : : static JsonPathBool
2598 akorotkov@postgresql 3631 :CBC 17445 : compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
3632 : : {
3633 : : int cmp;
3634 : : bool res;
3635 : :
2791 3636 [ + + ]: 17445 : if (jb1->type != jb2->type)
3637 : : {
3638 [ + + + + ]: 3392 : if (jb1->type == jbvNull || jb2->type == jbvNull)
3639 : :
3640 : : /*
3641 : : * Equality and order comparison of nulls to non-nulls returns
3642 : : * always false, but inequality comparison returns true.
3643 : : */
3644 : 1918 : return op == jpiNotEqual ? jpbTrue : jpbFalse;
3645 : :
3646 : : /* Non-null items of different types are not comparable. */
3647 : 1474 : return jpbUnknown;
3648 : : }
3649 : :
3650 [ + + + + : 14053 : switch (jb1->type)
+ + - ]
3651 : : {
3652 : 124 : case jbvNull:
3653 : 124 : cmp = 0;
3654 : 124 : break;
3655 : 2012 : case jbvBool:
3656 [ + + ]: 2964 : cmp = jb1->val.boolean == jb2->val.boolean ? 0 :
3657 [ + + ]: 952 : jb1->val.boolean ? 1 : -1;
3658 : 2012 : break;
3659 : 2857 : case jbvNumeric:
3660 : 2857 : cmp = compareNumeric(jb1->val.numeric, jb2->val.numeric);
3661 : 2857 : break;
3662 : 6636 : case jbvString:
3663 [ + + ]: 6636 : if (op == jpiEqual)
3664 : 4332 : return jb1->val.string.len != jb2->val.string.len ||
3665 : 2404 : memcmp(jb1->val.string.val,
3666 : 2404 : jb2->val.string.val,
3667 [ + + + + ]: 4332 : jb1->val.string.len) ? jpbFalse : jpbTrue;
3668 : :
2643 3669 : 2304 : cmp = compareStrings(jb1->val.string.val, jb1->val.string.len,
3670 : 2304 : jb2->val.string.val, jb2->val.string.len);
2791 3671 : 2304 : break;
2598 3672 : 2416 : case jbvDatetime:
3673 : : {
3674 : : bool cast_error;
3675 : :
3676 : 2416 : cmp = compareDatetime(jb1->val.datetime.value,
3677 : : jb1->val.datetime.typid,
3678 : : jb2->val.datetime.value,
3679 : : jb2->val.datetime.typid,
3680 : : useTz,
3681 : : &cast_error);
3682 : :
2572 3683 [ + + ]: 2356 : if (cast_error)
2598 3684 : 204 : return jpbUnknown;
3685 : : }
3686 : 2152 : break;
3687 : :
2791 3688 : 8 : case jbvBinary:
3689 : : case jbvArray:
3690 : : case jbvObject:
3691 : 8 : return jpbUnknown; /* non-scalars are not comparable */
3692 : :
2791 akorotkov@postgresql 3693 :UBC 0 : default:
3694 [ # # ]: 0 : elog(ERROR, "invalid jsonb value type %d", jb1->type);
3695 : : }
3696 : :
2791 akorotkov@postgresql 3697 [ + + + + :CBC 9449 : switch (op)
+ + - ]
3698 : : {
3699 : 3290 : case jpiEqual:
3700 : 3290 : res = (cmp == 0);
3701 : 3290 : break;
3702 : 4 : case jpiNotEqual:
3703 : 4 : res = (cmp != 0);
3704 : 4 : break;
3705 : 1442 : case jpiLess:
3706 : 1442 : res = (cmp < 0);
3707 : 1442 : break;
3708 : 1105 : case jpiGreater:
3709 : 1105 : res = (cmp > 0);
3710 : 1105 : break;
3711 : 1361 : case jpiLessOrEqual:
3712 : 1361 : res = (cmp <= 0);
3713 : 1361 : break;
3714 : 2247 : case jpiGreaterOrEqual:
3715 : 2247 : res = (cmp >= 0);
3716 : 2247 : break;
2791 akorotkov@postgresql 3717 :UBC 0 : default:
3718 [ # # ]: 0 : elog(ERROR, "unrecognized jsonpath operation: %d", op);
3719 : : return jpbUnknown;
3720 : : }
3721 : :
2791 akorotkov@postgresql 3722 :CBC 9449 : return res ? jpbTrue : jpbFalse;
3723 : : }
3724 : :
3725 : : /* Compare two numerics */
3726 : : static int
3727 : 2857 : compareNumeric(Numeric a, Numeric b)
3728 : : {
3729 : 2857 : return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
3730 : : NumericGetDatum(a),
3731 : : NumericGetDatum(b)));
3732 : : }
3733 : :
3734 : : static JsonbValue *
3735 : 2406 : copyJsonbValue(JsonbValue *src)
3736 : : {
330 michael@paquier.xyz 3737 : 2406 : JsonbValue *dst = palloc_object(JsonbValue);
3738 : :
2791 akorotkov@postgresql 3739 : 2406 : *dst = *src;
3740 : :
3741 : 2406 : return dst;
3742 : : }
3743 : :
3744 : : /*
3745 : : * Execute array subscript expression and convert resulting numeric item to
3746 : : * the integer type with truncation.
3747 : : */
3748 : : static JsonPathExecResult
3749 : 362 : getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
3750 : : int32 *index)
3751 : : {
3752 : : JsonbValue *jbv;
3753 : : JsonValueList found;
3754 : : JsonPathExecResult res;
3755 : : Datum numeric_index;
426 michael@paquier.xyz 3756 : 362 : ErrorSaveContext escontext = {T_ErrorSaveContext};
3757 : :
231 tgl@sss.pgh.pa.us 3758 : 362 : JsonValueListInit(&found);
3759 : :
3760 : 362 : res = executeItem(cxt, jsp, jb, &found);
3761 : :
2791 akorotkov@postgresql 3762 [ - + ]: 358 : if (jperIsError(res))
3763 : : {
231 tgl@sss.pgh.pa.us 3764 :UBC 0 : JsonValueListClear(&found);
2791 akorotkov@postgresql 3765 : 0 : return res;
3766 : : }
3767 : :
231 tgl@sss.pgh.pa.us 3768 [ + + + + ]:CBC 708 : if (!JsonValueListIsSingleton(&found) ||
2791 akorotkov@postgresql 3769 : 350 : !(jbv = getScalar(JsonValueListHead(&found), jbvNumeric)))
3770 : : {
231 tgl@sss.pgh.pa.us 3771 : 16 : JsonValueListClear(&found);
2791 akorotkov@postgresql 3772 [ + + + - ]: 16 : RETURN_ERROR(ereport(ERROR,
3773 : : (errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT),
3774 : : errmsg("jsonpath array subscript is not a single numeric value"))));
3775 : : }
3776 : :
3777 : 342 : numeric_index = DirectFunctionCall2(numeric_trunc,
3778 : : NumericGetDatum(jbv->val.numeric),
3779 : : Int32GetDatum(0));
3780 : :
426 michael@paquier.xyz 3781 : 342 : *index = numeric_int4_safe(DatumGetNumeric(numeric_index),
3782 : : (Node *) &escontext);
3783 : :
231 tgl@sss.pgh.pa.us 3784 : 342 : JsonValueListClear(&found);
3785 : :
426 michael@paquier.xyz 3786 [ + + ]: 342 : if (escontext.error_occurred)
2791 akorotkov@postgresql 3787 [ + + + - ]: 18 : RETURN_ERROR(ereport(ERROR,
3788 : : (errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT),
3789 : : errmsg("jsonpath array subscript is out of integer range"))));
3790 : :
3791 : 324 : return jperOk;
3792 : : }
3793 : :
3794 : : /* Save base object and its id needed for the execution of .keyvalue(). */
3795 : : static JsonBaseObjectInfo
3796 : 176111 : setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id)
3797 : : {
3798 : 176111 : JsonBaseObjectInfo baseObject = cxt->baseObject;
3799 : :
3800 [ + + ]: 176111 : cxt->baseObject.jbc = jbv->type != jbvBinary ? NULL :
3801 : : (JsonbContainer *) jbv->val.binary.data;
3802 : 176111 : cxt->baseObject.id = id;
3803 : :
3804 : 176111 : return baseObject;
3805 : : }
3806 : :
3807 : : /*
3808 : : * JsonValueList support functions
3809 : : */
3810 : :
3811 : : static void
231 tgl@sss.pgh.pa.us 3812 : 369055 : JsonValueListInit(JsonValueList *jvl)
3813 : : {
3814 : 369055 : jvl->nitems = 0;
3815 : 369055 : jvl->maxitems = BASE_JVL_ITEMS;
3816 : 369055 : jvl->next = NULL;
3817 : 369055 : jvl->last = jvl;
3818 : 369055 : }
3819 : :
3820 : : static void
945 amitlan@postgresql.o 3821 : 272945 : JsonValueListClear(JsonValueList *jvl)
3822 : : {
3823 : : JsonValueList *nxt;
3824 : :
3825 : : /* Release any extra chunks */
231 tgl@sss.pgh.pa.us 3826 [ + + ]: 273481 : for (JsonValueList *chunk = jvl->next; chunk != NULL; chunk = nxt)
3827 : : {
3828 : 536 : nxt = chunk->next;
3829 : 536 : pfree(chunk);
3830 : : }
3831 : : /* ... and reset to empty */
3832 : 272945 : jvl->nitems = 0;
3833 [ - + ]: 272945 : Assert(jvl->maxitems == BASE_JVL_ITEMS);
3834 : 272945 : jvl->next = NULL;
3835 : 272945 : jvl->last = jvl;
945 amitlan@postgresql.o 3836 : 272945 : }
3837 : :
3838 : : static void
231 tgl@sss.pgh.pa.us 3839 : 268830 : JsonValueListAppend(JsonValueList *jvl, const JsonbValue *jbv)
3840 : : {
3841 : 268830 : JsonValueList *last = jvl->last;
3842 : :
3843 [ + + ]: 268830 : if (last->nitems < last->maxitems)
3844 : : {
3845 : : /* there's still room in the last existing chunk */
3846 : 267804 : last->items[last->nitems] = *jbv;
3847 : 267804 : last->nitems++;
3848 : : }
3849 : : else
3850 : : {
3851 : : /* need a new last chunk */
3852 : : JsonValueList *nxt;
3853 : : int nxtsize;
3854 : :
3855 : 1026 : nxtsize = last->maxitems * 2; /* double the size with each chunk */
3856 : 1026 : nxtsize = Max(nxtsize, MIN_EXTRA_JVL_ITEMS); /* but at least this */
3857 : 1026 : nxt = palloc(offsetof(JsonValueList, items) +
3858 : 1026 : nxtsize * sizeof(JsonbValue));
3859 : 1026 : nxt->nitems = 1;
3860 : 1026 : nxt->maxitems = nxtsize;
3861 : 1026 : nxt->next = NULL;
3862 : 1026 : nxt->items[0] = *jbv;
3863 : 1026 : last->next = nxt;
3864 : 1026 : jvl->last = nxt;
3865 : : }
2791 akorotkov@postgresql 3866 : 268830 : }
3867 : :
3868 : : static bool
231 tgl@sss.pgh.pa.us 3869 : 9236 : JsonValueListIsEmpty(const JsonValueList *jvl)
3870 : : {
3871 : : /* We need not examine extra chunks for this */
3872 : 9236 : return (jvl->nitems == 0);
3873 : : }
3874 : :
3875 : : static bool
3876 : 86740 : JsonValueListIsSingleton(const JsonValueList *jvl)
3877 : : {
3878 : : #if BASE_JVL_ITEMS > 1
3879 : : /* We need not examine extra chunks in this case */
3880 : 86740 : return (jvl->nitems == 1);
3881 : : #else
3882 : : return (jvl->nitems == 1 && jvl->next == NULL);
3883 : : #endif
3884 : : }
3885 : :
3886 : : static bool
3887 : 4114 : JsonValueListHasMultipleItems(const JsonValueList *jvl)
3888 : : {
3889 : : #if BASE_JVL_ITEMS > 1
3890 : : /* We need not examine extra chunks in this case */
3891 : 4114 : return (jvl->nitems > 1);
3892 : : #else
3893 : : return (jvl->nitems == 1 && jvl->next != NULL);
3894 : : #endif
3895 : : }
3896 : :
3897 : : static JsonbValue *
3898 : 93332 : JsonValueListHead(JsonValueList *jvl)
3899 : : {
3900 [ - + ]: 93332 : Assert(jvl->nitems > 0);
3901 : 93332 : return &jvl->items[0];
3902 : : }
3903 : :
3904 : : /*
3905 : : * JsonValueListIterator functions
3906 : : */
3907 : :
3908 : : static void
3909 : 228828 : JsonValueListInitIterator(JsonValueList *jvl, JsonValueListIterator *it)
3910 : : {
3911 : 228828 : it->chunk = jvl;
3912 : 228828 : it->nextitem = 0;
2791 akorotkov@postgresql 3913 : 228828 : }
3914 : :
3915 : : /*
3916 : : * Get the next item from the sequence advancing iterator.
3917 : : * Returns NULL if no more items.
3918 : : */
3919 : : static JsonbValue *
231 tgl@sss.pgh.pa.us 3920 : 344501 : JsonValueListNext(JsonValueListIterator *it)
3921 : : {
3922 [ - + ]: 344501 : if (it->chunk == NULL)
231 tgl@sss.pgh.pa.us 3923 :LBC (300) : return NULL;
231 tgl@sss.pgh.pa.us 3924 [ + + ]:CBC 344501 : if (it->nextitem >= it->chunk->nitems)
3925 : : {
3926 : 216951 : it->chunk = it->chunk->next;
3927 [ + + ]: 216951 : if (it->chunk == NULL)
3928 : 215998 : return NULL;
3929 : 953 : it->nextitem = 0;
3930 [ - + ]: 953 : Assert(it->chunk->nitems > 0);
3931 : : }
3932 : 128503 : return &it->chunk->items[it->nextitem++];
3933 : : }
3934 : :
3935 : : /*
3936 : : * Initialize a binary JsonbValue with the given jsonb container.
3937 : : */
3938 : : static JsonbValue *
2791 akorotkov@postgresql 3939 : 153827 : JsonbInitBinary(JsonbValue *jbv, Jsonb *jb)
3940 : : {
3941 : 153827 : jbv->type = jbvBinary;
3942 : 153827 : jbv->val.binary.data = &jb->root;
3943 : 153827 : jbv->val.binary.len = VARSIZE_ANY_EXHDR(jb);
3944 : :
3945 : 153827 : return jbv;
3946 : : }
3947 : :
3948 : : /*
3949 : : * Returns jbv* type of JsonbValue. Note, it never returns jbvBinary as is.
3950 : : */
3951 : : static int
3952 : 262902 : JsonbType(JsonbValue *jb)
3953 : : {
3954 : 262902 : int type = jb->type;
3955 : :
3956 [ + + ]: 262902 : if (jb->type == jbvBinary)
3957 : : {
707 peter@eisentraut.org 3958 : 151191 : JsonbContainer *jbc = jb->val.binary.data;
3959 : :
3960 : : /* Scalars should be always extracted during jsonpath execution. */
2791 akorotkov@postgresql 3961 [ - + ]: 151191 : Assert(!JsonContainerIsScalar(jbc));
3962 : :
3963 [ + + ]: 151191 : if (JsonContainerIsObject(jbc))
3964 : 148296 : type = jbvObject;
3965 [ + - ]: 2895 : else if (JsonContainerIsArray(jbc))
3966 : 2895 : type = jbvArray;
3967 : : else
2791 akorotkov@postgresql 3968 [ # # ]:UBC 0 : elog(ERROR, "invalid jsonb container type: 0x%08x", jbc->header);
3969 : : }
3970 : :
2791 akorotkov@postgresql 3971 :CBC 262902 : return type;
3972 : : }
3973 : :
3974 : : /* Get scalar of given type or NULL on type mismatch */
3975 : : static JsonbValue *
3976 : 8083 : getScalar(JsonbValue *scalar, enum jbvType type)
3977 : : {
3978 : : /* Scalars should be always extracted during jsonpath execution. */
3979 [ + + - + ]: 8083 : Assert(scalar->type != jbvBinary ||
3980 : : !JsonContainerIsScalar(scalar->val.binary.data));
3981 : :
3982 [ + + ]: 8083 : return scalar->type == type ? scalar : NULL;
3983 : : }
3984 : :
3985 : : /* Construct a JSON array from the item list */
3986 : : static JsonbValue *
231 tgl@sss.pgh.pa.us 3987 : 330 : wrapItemsInArray(JsonValueList *items)
3988 : : {
333 3989 : 330 : JsonbInState ps = {0};
3990 : : JsonValueListIterator it;
3991 : : JsonbValue *jbv;
3992 : :
2791 akorotkov@postgresql 3993 : 330 : pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
3994 : :
3995 : 330 : JsonValueListInitIterator(items, &it);
231 tgl@sss.pgh.pa.us 3996 [ + + ]: 910 : while ((jbv = JsonValueListNext(&it)))
2791 akorotkov@postgresql 3997 : 580 : pushJsonbValue(&ps, WJB_ELEM, jbv);
3998 : :
333 tgl@sss.pgh.pa.us 3999 : 330 : pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
4000 : :
4001 : 330 : return ps.result;
4002 : : }
4003 : :
4004 : : /* Check if the timezone required for casting from type1 to type2 is used */
4005 : : static void
2572 akorotkov@postgresql 4006 : 900 : checkTimezoneIsUsedForCast(bool useTz, const char *type1, const char *type2)
4007 : : {
4008 [ + + ]: 900 : if (!useTz)
4009 [ + - ]: 192 : ereport(ERROR,
4010 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4011 : : errmsg("cannot convert value from %s to %s without time zone usage",
4012 : : type1, type2),
4013 : : errhint("Use *_tz() function for time zone support.")));
4014 : 708 : }
4015 : :
4016 : : /* Convert time datum to timetz datum */
4017 : : static Datum
4018 : 168 : castTimeToTimeTz(Datum time, bool useTz)
4019 : : {
4020 : 168 : checkTimezoneIsUsedForCast(useTz, "time", "timetz");
4021 : :
4022 : 144 : return DirectFunctionCall1(time_timetz, time);
4023 : : }
4024 : :
4025 : : /*
4026 : : * Compare date to timestamp.
4027 : : * Note that this doesn't involve any timezone considerations.
4028 : : */
4029 : : static int
4030 : 124 : cmpDateToTimestamp(DateADT date1, Timestamp ts2, bool useTz)
4031 : : {
2220 tgl@sss.pgh.pa.us 4032 : 124 : return date_cmp_timestamp_internal(date1, ts2);
4033 : : }
4034 : :
4035 : : /*
4036 : : * Compare date to timestamptz.
4037 : : */
4038 : : static int
2572 akorotkov@postgresql 4039 : 108 : cmpDateToTimestampTz(DateADT date1, TimestampTz tstz2, bool useTz)
4040 : : {
4041 : 108 : checkTimezoneIsUsedForCast(useTz, "date", "timestamptz");
4042 : :
2220 tgl@sss.pgh.pa.us 4043 : 96 : return date_cmp_timestamptz_internal(date1, tstz2);
4044 : : }
4045 : :
4046 : : /*
4047 : : * Compare timestamp to timestamptz.
4048 : : */
4049 : : static int
2572 akorotkov@postgresql 4050 : 168 : cmpTimestampToTimestampTz(Timestamp ts1, TimestampTz tstz2, bool useTz)
4051 : : {
4052 : 168 : checkTimezoneIsUsedForCast(useTz, "timestamp", "timestamptz");
4053 : :
2220 tgl@sss.pgh.pa.us 4054 : 144 : return timestamp_cmp_timestamptz_internal(ts1, tstz2);
4055 : : }
4056 : :
4057 : : /*
4058 : : * Cross-type comparison of two datetime SQL/JSON items. If items are
4059 : : * uncomparable *cast_error flag is set, otherwise *cast_error is unset.
4060 : : * If the cast requires timezone and it is not used, then explicit error is thrown.
4061 : : */
4062 : : static int
2598 akorotkov@postgresql 4063 : 2416 : compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
4064 : : bool useTz, bool *cast_error)
4065 : : {
4066 : : PGFunction cmpfunc;
4067 : :
2572 4068 : 2416 : *cast_error = false;
4069 : :
2598 4070 [ + + + + : 2416 : switch (typid1)
+ - ]
4071 : : {
4072 : 376 : case DATEOID:
4073 [ + + + + : 376 : switch (typid2)
- ]
4074 : : {
4075 : 252 : case DATEOID:
4076 : 252 : cmpfunc = date_cmp;
4077 : :
4078 : 252 : break;
4079 : :
4080 : 52 : case TIMESTAMPOID:
2572 4081 : 52 : return cmpDateToTimestamp(DatumGetDateADT(val1),
4082 : : DatumGetTimestamp(val2),
4083 : : useTz);
4084 : :
2598 4085 : 48 : case TIMESTAMPTZOID:
2572 4086 : 48 : return cmpDateToTimestampTz(DatumGetDateADT(val1),
4087 : : DatumGetTimestampTz(val2),
4088 : : useTz);
4089 : :
2598 4090 : 24 : case TIMEOID:
4091 : : case TIMETZOID:
2572 4092 : 24 : *cast_error = true; /* uncomparable types */
2598 4093 : 24 : return 0;
4094 : :
2572 akorotkov@postgresql 4095 :UBC 0 : default:
4096 [ # # ]: 0 : elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
4097 : : typid2);
4098 : : }
2598 akorotkov@postgresql 4099 :CBC 252 : break;
4100 : :
4101 : 416 : case TIMEOID:
4102 [ + + + - ]: 416 : switch (typid2)
4103 : : {
4104 : 284 : case TIMEOID:
4105 : 284 : cmpfunc = time_cmp;
4106 : :
4107 : 284 : break;
4108 : :
4109 : 84 : case TIMETZOID:
2572 4110 : 84 : val1 = castTimeToTimeTz(val1, useTz);
2598 4111 : 72 : cmpfunc = timetz_cmp;
4112 : :
4113 : 72 : break;
4114 : :
4115 : 48 : case DATEOID:
4116 : : case TIMESTAMPOID:
4117 : : case TIMESTAMPTZOID:
2572 4118 : 48 : *cast_error = true; /* uncomparable types */
2598 4119 : 48 : return 0;
4120 : :
2572 akorotkov@postgresql 4121 :UBC 0 : default:
4122 [ # # ]: 0 : elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
4123 : : typid2);
4124 : : }
2598 akorotkov@postgresql 4125 :CBC 356 : break;
4126 : :
4127 : 536 : case TIMETZOID:
4128 [ + + + - ]: 536 : switch (typid2)
4129 : : {
4130 : 84 : case TIMEOID:
2572 4131 : 84 : val2 = castTimeToTimeTz(val2, useTz);
2598 4132 : 72 : cmpfunc = timetz_cmp;
4133 : :
4134 : 72 : break;
4135 : :
4136 : 404 : case TIMETZOID:
4137 : 404 : cmpfunc = timetz_cmp;
4138 : :
4139 : 404 : break;
4140 : :
4141 : 48 : case DATEOID:
4142 : : case TIMESTAMPOID:
4143 : : case TIMESTAMPTZOID:
2572 4144 : 48 : *cast_error = true; /* uncomparable types */
2598 4145 : 48 : return 0;
4146 : :
2572 akorotkov@postgresql 4147 :UBC 0 : default:
4148 [ # # ]: 0 : elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
4149 : : typid2);
4150 : : }
2598 akorotkov@postgresql 4151 :CBC 476 : break;
4152 : :
4153 : 476 : case TIMESTAMPOID:
4154 [ + + + + : 476 : switch (typid2)
- ]
4155 : : {
4156 : 72 : case DATEOID:
2572 4157 : 72 : return -cmpDateToTimestamp(DatumGetDateADT(val2),
4158 : : DatumGetTimestamp(val1),
4159 : : useTz);
4160 : :
2598 4161 : 284 : case TIMESTAMPOID:
4162 : 284 : cmpfunc = timestamp_cmp;
4163 : :
4164 : 284 : break;
4165 : :
4166 : 84 : case TIMESTAMPTZOID:
2572 4167 : 84 : return cmpTimestampToTimestampTz(DatumGetTimestamp(val1),
4168 : : DatumGetTimestampTz(val2),
4169 : : useTz);
4170 : :
2598 4171 : 36 : case TIMEOID:
4172 : : case TIMETZOID:
2572 4173 : 36 : *cast_error = true; /* uncomparable types */
2598 4174 : 36 : return 0;
4175 : :
2572 akorotkov@postgresql 4176 :UBC 0 : default:
4177 [ # # ]: 0 : elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
4178 : : typid2);
4179 : : }
2598 akorotkov@postgresql 4180 :CBC 284 : break;
4181 : :
4182 : 612 : case TIMESTAMPTZOID:
4183 [ + + + + : 612 : switch (typid2)
- ]
4184 : : {
4185 : 60 : case DATEOID:
2572 4186 : 60 : return -cmpDateToTimestampTz(DatumGetDateADT(val2),
4187 : : DatumGetTimestampTz(val1),
4188 : : useTz);
4189 : :
2598 4190 : 84 : case TIMESTAMPOID:
2572 4191 : 84 : return -cmpTimestampToTimestampTz(DatumGetTimestamp(val2),
4192 : : DatumGetTimestampTz(val1),
4193 : : useTz);
4194 : :
2598 4195 : 420 : case TIMESTAMPTZOID:
4196 : 420 : cmpfunc = timestamp_cmp;
4197 : :
4198 : 420 : break;
4199 : :
4200 : 48 : case TIMEOID:
4201 : : case TIMETZOID:
2572 4202 : 48 : *cast_error = true; /* uncomparable types */
2598 4203 : 48 : return 0;
4204 : :
2572 akorotkov@postgresql 4205 :UBC 0 : default:
4206 [ # # ]: 0 : elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
4207 : : typid2);
4208 : : }
2598 akorotkov@postgresql 4209 :CBC 420 : break;
4210 : :
2598 akorotkov@postgresql 4211 :UBC 0 : default:
2572 4212 [ # # ]: 0 : elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", typid1);
4213 : : }
4214 : :
2572 akorotkov@postgresql 4215 [ - + ]:CBC 1788 : if (*cast_error)
2572 akorotkov@postgresql 4216 :UBC 0 : return 0; /* cast error */
4217 : :
2598 akorotkov@postgresql 4218 :CBC 1788 : return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
4219 : : }
4220 : :
4221 : : /*
4222 : : * Executor-callable JSON_EXISTS implementation
4223 : : *
4224 : : * Returns NULL instead of throwing errors if 'error' is not NULL, setting
4225 : : * *error to true.
4226 : : */
4227 : : bool
959 amitlan@postgresql.o 4228 : 420 : JsonPathExists(Datum jb, JsonPath *jp, bool *error, List *vars)
4229 : : {
4230 : : JsonPathExecResult res;
4231 : :
4232 : 420 : res = executeJsonPath(jp, vars,
4233 : : GetJsonPathVar, CountJsonPathVars,
4234 : : DatumGetJsonbP(jb), !error, NULL, true);
4235 : :
4236 [ + + - + ]: 416 : Assert(error || !jperIsError(res));
4237 : :
4238 [ + + + + ]: 416 : if (error && jperIsError(res))
4239 : 120 : *error = true;
4240 : :
4241 : 416 : return res == jperOk;
4242 : : }
4243 : :
4244 : : /*
4245 : : * Executor-callable JSON_QUERY implementation
4246 : : *
4247 : : * Returns NULL instead of throwing errors if 'error' is not NULL, setting
4248 : : * *error to true. *empty is set to true if no match is found.
4249 : : */
4250 : : Datum
4251 : 1864 : JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty,
4252 : : bool *error, List *vars,
4253 : : const char *column_name)
4254 : : {
4255 : : bool wrap;
4256 : : JsonValueList found;
4257 : : JsonPathExecResult res;
4258 : :
231 tgl@sss.pgh.pa.us 4259 : 1864 : JsonValueListInit(&found);
4260 : :
959 amitlan@postgresql.o 4261 : 1864 : res = executeJsonPath(jp, vars,
4262 : : GetJsonPathVar, CountJsonPathVars,
4263 : : DatumGetJsonbP(jb), !error, &found, true);
4264 [ + + - + ]: 1852 : Assert(error || !jperIsError(res));
4265 [ + + + + ]: 1852 : if (error && jperIsError(res))
4266 : : {
4267 : 20 : *error = true;
4268 : 20 : *empty = false;
4269 : 20 : return (Datum) 0;
4270 : : }
4271 : :
4272 : : /*
4273 : : * Determine whether to wrap the result in a JSON array or not.
4274 : : *
4275 : : * If the returned JsonValueList is empty, no wrapping is necessary.
4276 : : *
4277 : : * If the wrapper mode is JSW_NONE or JSW_UNSPEC, wrapping is explicitly
4278 : : * disabled. This enforces a WITHOUT WRAPPER clause, which is also the
4279 : : * default when no WRAPPER clause is specified.
4280 : : *
4281 : : * If the mode is JSW_UNCONDITIONAL, wrapping is enforced regardless of
4282 : : * the number of SQL/JSON items, enforcing a WITH WRAPPER or WITH
4283 : : * UNCONDITIONAL WRAPPER clause.
4284 : : *
4285 : : * For JSW_CONDITIONAL, wrapping occurs only if there is more than one
4286 : : * SQL/JSON item in the list, enforcing a WITH CONDITIONAL WRAPPER clause.
4287 : : */
231 tgl@sss.pgh.pa.us 4288 [ + + ]: 1832 : if (JsonValueListIsEmpty(&found))
959 amitlan@postgresql.o 4289 : 160 : wrap = false;
4290 [ + + + + ]: 1672 : else if (wrapper == JSW_NONE || wrapper == JSW_UNSPEC)
4291 : 1344 : wrap = false;
4292 [ + + ]: 328 : else if (wrapper == JSW_UNCONDITIONAL)
4293 : 212 : wrap = true;
4294 [ + - ]: 116 : else if (wrapper == JSW_CONDITIONAL)
231 tgl@sss.pgh.pa.us 4295 : 116 : wrap = JsonValueListHasMultipleItems(&found);
4296 : : else
4297 : : {
931 amitlan@postgresql.o 4298 [ # # ]:UBC 0 : elog(ERROR, "unrecognized json wrapper %d", (int) wrapper);
4299 : : wrap = false;
4300 : : }
4301 : :
959 amitlan@postgresql.o 4302 [ + + ]:CBC 1832 : if (wrap)
4303 : 252 : return JsonbPGetDatum(JsonbValueToJsonb(wrapItemsInArray(&found)));
4304 : :
4305 : : /* No wrapping means at most one item is expected. */
231 tgl@sss.pgh.pa.us 4306 [ + + ]: 1580 : if (JsonValueListHasMultipleItems(&found))
4307 : : {
959 amitlan@postgresql.o 4308 [ + + ]: 40 : if (error)
4309 : : {
4310 : 32 : *error = true;
4311 : 32 : return (Datum) 0;
4312 : : }
4313 : :
931 4314 [ + + ]: 8 : if (column_name)
4315 [ + - ]: 4 : ereport(ERROR,
4316 : : (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
4317 : : errmsg("JSON path expression for column \"%s\" must return single item when no wrapper is requested",
4318 : : column_name),
4319 : : errhint("Use the WITH WRAPPER clause to wrap SQL/JSON items into an array.")));
4320 : : else
4321 [ + - ]: 4 : ereport(ERROR,
4322 : : (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
4323 : : errmsg("JSON path expression in JSON_QUERY must return single item when no wrapper is requested"),
4324 : : errhint("Use the WITH WRAPPER clause to wrap SQL/JSON items into an array.")));
4325 : : }
4326 : :
231 tgl@sss.pgh.pa.us 4327 [ + + ]: 1540 : if (!JsonValueListIsEmpty(&found))
4328 : 1380 : return JsonbPGetDatum(JsonbValueToJsonb(JsonValueListHead(&found)));
4329 : :
959 amitlan@postgresql.o 4330 : 160 : *empty = true;
4331 : 160 : return PointerGetDatum(NULL);
4332 : : }
4333 : :
4334 : : /*
4335 : : * Executor-callable JSON_VALUE implementation
4336 : : *
4337 : : * Returns NULL instead of throwing errors if 'error' is not NULL, setting
4338 : : * *error to true. *empty is set to true if no match is found.
4339 : : */
4340 : : JsonbValue *
931 4341 : 2778 : JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars,
4342 : : const char *column_name)
4343 : : {
4344 : : JsonbValue *res;
4345 : : JsonValueList found;
4346 : : JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY;
4347 : :
231 tgl@sss.pgh.pa.us 4348 : 2778 : JsonValueListInit(&found);
4349 : :
959 amitlan@postgresql.o 4350 : 2778 : jper = executeJsonPath(jp, vars, GetJsonPathVar, CountJsonPathVars,
4351 : : DatumGetJsonbP(jb),
4352 : : !error, &found, true);
4353 : :
4354 [ + + - + ]: 2770 : Assert(error || !jperIsError(jper));
4355 : :
4356 [ + + + + ]: 2770 : if (error && jperIsError(jper))
4357 : : {
4358 : 32 : *error = true;
4359 : 32 : *empty = false;
4360 : 32 : return NULL;
4361 : : }
4362 : :
231 tgl@sss.pgh.pa.us 4363 : 2738 : *empty = JsonValueListIsEmpty(&found);
4364 : :
959 amitlan@postgresql.o 4365 [ + + ]: 2738 : if (*empty)
4366 : 320 : return NULL;
4367 : :
4368 : : /* JSON_VALUE expects to get only singletons. */
231 tgl@sss.pgh.pa.us 4369 [ + + ]: 2418 : if (JsonValueListHasMultipleItems(&found))
4370 : : {
959 amitlan@postgresql.o 4371 [ + + ]: 12 : if (error)
4372 : : {
4373 : 8 : *error = true;
4374 : 8 : return NULL;
4375 : : }
4376 : :
931 4377 [ - + ]: 4 : if (column_name)
931 amitlan@postgresql.o 4378 [ # # ]:UBC 0 : ereport(ERROR,
4379 : : (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
4380 : : errmsg("JSON path expression for column \"%s\" must return single scalar item",
4381 : : column_name)));
4382 : : else
931 amitlan@postgresql.o 4383 [ + - ]:CBC 4 : ereport(ERROR,
4384 : : (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
4385 : : errmsg("JSON path expression in JSON_VALUE must return single scalar item")));
4386 : : }
4387 : :
231 tgl@sss.pgh.pa.us 4388 : 2406 : res = copyJsonbValue(JsonValueListHead(&found));
959 amitlan@postgresql.o 4389 [ + + - + ]: 2406 : if (res->type == jbvBinary && JsonContainerIsScalar(res->val.binary.data))
959 amitlan@postgresql.o 4390 :UBC 0 : JsonbExtractScalar(res->val.binary.data, res);
4391 : :
4392 : : /* JSON_VALUE expects to get only scalars. */
959 amitlan@postgresql.o 4393 [ + + + + ]:CBC 2406 : if (!IsAJsonbScalar(res))
4394 : : {
4395 [ + + ]: 76 : if (error)
4396 : : {
4397 : 68 : *error = true;
4398 : 68 : return NULL;
4399 : : }
4400 : :
931 4401 [ - + ]: 8 : if (column_name)
931 amitlan@postgresql.o 4402 [ # # ]:UBC 0 : ereport(ERROR,
4403 : : (errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED),
4404 : : errmsg("JSON path expression for column \"%s\" must return single scalar item",
4405 : : column_name)));
4406 : : else
931 amitlan@postgresql.o 4407 [ + - ]:CBC 8 : ereport(ERROR,
4408 : : (errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED),
4409 : : errmsg("JSON path expression in JSON_VALUE must return single scalar item")));
4410 : : }
4411 : :
959 4412 [ + + ]: 2330 : if (res->type == jbvNull)
4413 : 128 : return NULL;
4414 : :
4415 : 2202 : return res;
4416 : : }
4417 : :
4418 : : /************************ JSON_TABLE functions ***************************/
4419 : :
4420 : : /*
4421 : : * Sanity-checks and returns the opaque JsonTableExecContext from the
4422 : : * given executor state struct.
4423 : : */
4424 : : static inline JsonTableExecContext *
945 4425 : 7842 : GetJsonTableExecContext(TableFuncScanState *state, const char *fname)
4426 : : {
4427 : : JsonTableExecContext *result;
4428 : :
4429 [ - + ]: 7842 : if (!IsA(state, TableFuncScanState))
945 amitlan@postgresql.o 4430 [ # # ]:UBC 0 : elog(ERROR, "%s called with invalid TableFuncScanState", fname);
945 amitlan@postgresql.o 4431 :CBC 7842 : result = (JsonTableExecContext *) state->opaque;
4432 [ - + ]: 7842 : if (result->magic != JSON_TABLE_EXEC_CONTEXT_MAGIC)
945 amitlan@postgresql.o 4433 [ # # ]:UBC 0 : elog(ERROR, "%s called with invalid TableFuncScanState", fname);
4434 : :
945 amitlan@postgresql.o 4435 :CBC 7842 : return result;
4436 : : }
4437 : :
4438 : : /*
4439 : : * JsonTableInitOpaque
4440 : : * Fill in TableFuncScanState->opaque for processing JSON_TABLE
4441 : : *
4442 : : * This initializes the PASSING arguments and the JsonTablePlanState for
4443 : : * JsonTablePlan given in TableFunc.
4444 : : */
4445 : : static void
58 peter@eisentraut.org 4446 :GNC 450 : JsonTableInitOpaque(TableFuncScanState *tscanstate, int natts)
4447 : : {
4448 : : JsonTableExecContext *cxt;
4449 : 450 : PlanState *ps = &tscanstate->ss.ps;
945 amitlan@postgresql.o 4450 :CBC 450 : TableFuncScan *tfs = castNode(TableFuncScan, ps->plan);
4451 : 450 : TableFunc *tf = tfs->tablefunc;
4452 : 450 : JsonTablePlan *rootplan = (JsonTablePlan *) tf->plan;
4453 : 450 : JsonExpr *je = castNode(JsonExpr, tf->docexpr);
4454 : 450 : List *args = NIL;
4455 : :
330 michael@paquier.xyz 4456 : 450 : cxt = palloc0_object(JsonTableExecContext);
945 amitlan@postgresql.o 4457 : 450 : cxt->magic = JSON_TABLE_EXEC_CONTEXT_MAGIC;
4458 : :
4459 : : /*
4460 : : * Evaluate JSON_TABLE() PASSING arguments to be passed to the jsonpath
4461 : : * executor via JsonPathVariables.
4462 : : */
58 peter@eisentraut.org 4463 [ + + ]:GNC 450 : if (tscanstate->passingvalexprs)
4464 : : {
4465 : : ListCell *exprlc;
4466 : : ListCell *namelc;
4467 : :
4468 [ - + ]: 104 : Assert(list_length(tscanstate->passingvalexprs) ==
4469 : : list_length(je->passing_names));
4470 [ + - + + : 328 : forboth(exprlc, tscanstate->passingvalexprs,
+ - + + +
+ + - +
+ ]
4471 : : namelc, je->passing_names)
4472 : : {
4473 : 224 : ExprState *exprstate = lfirst_node(ExprState, exprlc);
945 amitlan@postgresql.o 4474 :CBC 224 : String *name = lfirst_node(String, namelc);
330 michael@paquier.xyz 4475 : 224 : JsonPathVariable *var = palloc_object(JsonPathVariable);
4476 : :
945 amitlan@postgresql.o 4477 : 224 : var->name = pstrdup(name->sval);
869 4478 : 224 : var->namelen = strlen(var->name);
58 peter@eisentraut.org 4479 :GNC 224 : var->typid = exprType((Node *) exprstate->expr);
4480 : 224 : var->typmod = exprTypmod((Node *) exprstate->expr);
4481 : :
4482 : : /*
4483 : : * Evaluate the expression and save the value to be returned by
4484 : : * GetJsonPathVar().
4485 : : */
4486 : 224 : var->value = ExecEvalExpr(exprstate, ps->ps_ExprContext,
4487 : : &var->isnull);
4488 : :
945 amitlan@postgresql.o 4489 :CBC 224 : args = lappend(args, var);
4490 : : }
4491 : : }
4492 : :
330 michael@paquier.xyz 4493 : 450 : cxt->colplanstates = palloc_array(JsonTablePlanState *, list_length(tf->colvalexprs));
4494 : :
4495 : : /*
4496 : : * Initialize plan for the root path and, recursively, also any child
4497 : : * plans that compute the NESTED paths.
4498 : : */
941 amitlan@postgresql.o 4499 : 450 : cxt->rootplanstate = JsonTableInitPlan(cxt, rootplan, NULL, args,
4500 : : CurrentMemoryContext);
4501 : :
58 peter@eisentraut.org 4502 :GNC 450 : tscanstate->opaque = cxt;
945 amitlan@postgresql.o 4503 :CBC 450 : }
4504 : :
4505 : : /*
4506 : : * JsonTableDestroyOpaque
4507 : : * Resets state->opaque
4508 : : */
4509 : : static void
4510 : 450 : JsonTableDestroyOpaque(TableFuncScanState *state)
4511 : : {
4512 : : JsonTableExecContext *cxt =
4513 : 450 : GetJsonTableExecContext(state, "JsonTableDestroyOpaque");
4514 : :
4515 : : /* not valid anymore */
4516 : 450 : cxt->magic = 0;
4517 : :
4518 : 450 : state->opaque = NULL;
4519 : 450 : }
4520 : :
4521 : : /*
4522 : : * JsonTableInitPlan
4523 : : * Initialize information for evaluating jsonpath in the given
4524 : : * JsonTablePlan and, recursively, in any child plans
4525 : : */
4526 : : static JsonTablePlanState *
4527 : 956 : JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
4528 : : JsonTablePlanState *parentstate,
4529 : : List *args, MemoryContext mcxt)
4530 : : {
330 michael@paquier.xyz 4531 : 956 : JsonTablePlanState *planstate = palloc0_object(JsonTablePlanState);
4532 : :
945 amitlan@postgresql.o 4533 : 956 : planstate->plan = plan;
941 4534 : 956 : planstate->parent = parentstate;
231 tgl@sss.pgh.pa.us 4535 : 956 : JsonValueListInit(&planstate->found);
4536 : :
945 amitlan@postgresql.o 4537 [ + + ]: 956 : if (IsA(plan, JsonTablePathScan))
4538 : : {
4539 : 832 : JsonTablePathScan *scan = (JsonTablePathScan *) plan;
4540 : : int i;
4541 : :
119 akorotkov@postgresql 4542 :GNC 832 : planstate->outerJoin = scan->outerJoin;
945 amitlan@postgresql.o 4543 :CBC 832 : planstate->path = DatumGetJsonPathP(scan->path->value->constvalue);
4544 : 832 : planstate->args = args;
4545 : 832 : planstate->mcxt = AllocSetContextCreate(mcxt, "JsonTableExecContext",
4546 : : ALLOCSET_DEFAULT_SIZES);
4547 : :
4548 : : /* No row pattern evaluated yet. */
4549 : 832 : planstate->current.value = PointerGetDatum(NULL);
4550 : 832 : planstate->current.isnull = true;
4551 : :
941 4552 [ + + + + ]: 2058 : for (i = scan->colMin; i >= 0 && i <= scan->colMax; i++)
4553 : 1226 : cxt->colplanstates[i] = planstate;
4554 : :
4555 : 832 : planstate->nested = scan->child ?
4556 [ + + ]: 832 : JsonTableInitPlan(cxt, scan->child, planstate, args, mcxt) : NULL;
4557 : : }
4558 [ + - ]: 124 : else if (IsA(plan, JsonTableSiblingJoin))
4559 : : {
4560 : 124 : JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
4561 : :
119 akorotkov@postgresql 4562 :GNC 124 : planstate->cross = join->cross;
4563 : :
941 amitlan@postgresql.o 4564 :CBC 124 : planstate->left = JsonTableInitPlan(cxt, join->lplan, parentstate,
4565 : : args, mcxt);
4566 : 124 : planstate->right = JsonTableInitPlan(cxt, join->rplan, parentstate,
4567 : : args, mcxt);
4568 : : }
4569 : :
945 4570 : 956 : return planstate;
4571 : : }
4572 : :
4573 : : /*
4574 : : * JsonTableSetDocument
4575 : : * Install the input document and evaluate the row pattern
4576 : : */
4577 : : static void
4578 : 446 : JsonTableSetDocument(TableFuncScanState *state, Datum value)
4579 : : {
4580 : : JsonTableExecContext *cxt =
4581 : 446 : GetJsonTableExecContext(state, "JsonTableSetDocument");
4582 : :
4583 : 446 : JsonTableResetRowPattern(cxt->rootplanstate, value);
4584 : 442 : }
4585 : :
4586 : : /*
4587 : : * Evaluate a JsonTablePlan's jsonpath to get a new row pattern from
4588 : : * the given context item
4589 : : */
4590 : : static void
4591 : 1176 : JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
4592 : : {
4593 : 1176 : JsonTablePathScan *scan = castNode(JsonTablePathScan, planstate->plan);
4594 : : MemoryContext oldcxt;
4595 : : JsonPathExecResult res;
4596 : 1176 : Jsonb *js = (Jsonb *) DatumGetJsonbP(item);
4597 : :
4598 : 1176 : JsonValueListClear(&planstate->found);
4599 : :
4600 : 1176 : MemoryContextResetOnly(planstate->mcxt);
4601 : :
4602 : 1176 : oldcxt = MemoryContextSwitchTo(planstate->mcxt);
4603 : :
4604 : 1176 : res = executeJsonPath(planstate->path, planstate->args,
4605 : : GetJsonPathVar, CountJsonPathVars,
4606 : 1176 : js, scan->errorOnError,
4607 : : &planstate->found,
4608 : : true);
4609 : :
4610 : 1172 : MemoryContextSwitchTo(oldcxt);
4611 : :
4612 [ + + ]: 1172 : if (jperIsError(res))
4613 : : {
4614 [ - + ]: 148 : Assert(!scan->errorOnError);
4615 : 148 : JsonValueListClear(&planstate->found);
4616 : : }
4617 : :
119 akorotkov@postgresql 4618 :GNC 1172 : JsonTableRescan(planstate);
945 amitlan@postgresql.o 4619 :CBC 1172 : }
4620 : :
4621 : : /*
4622 : : * Fetch next row from a JsonTablePlan.
4623 : : *
4624 : : * Returns false if the plan has run out of rows, true otherwise.
4625 : : */
4626 : : static bool
4627 : 4656 : JsonTablePlanNextRow(JsonTablePlanState *planstate)
4628 : : {
119 akorotkov@postgresql 4629 [ + + ]:GNC 4656 : if (IsA(planstate->plan, JsonTableSiblingJoin))
4630 : : {
4631 [ + + ]: 1056 : if (planstate->advanceRight)
4632 : : {
4633 : : /* fetch next inner row */
4634 [ + + ]: 440 : if (JsonTablePlanNextRow(planstate->right))
4635 : 264 : return true;
4636 : :
4637 : : /* inner rows are exhausted */
4638 [ + + ]: 176 : if (planstate->cross)
4639 : 72 : planstate->advanceRight = false; /* next outer row */
4640 : : else
4641 : 104 : return false; /* end of scan */
4642 : : }
4643 : :
4644 [ + - ]: 752 : while (!planstate->advanceRight)
4645 : : {
4646 : : /* fetch next outer row */
4647 : 752 : bool more = JsonTablePlanNextRow(planstate->left);
4648 : :
4649 [ + + ]: 752 : if (planstate->cross)
4650 : : {
4651 [ + + ]: 212 : if (!more)
4652 : 76 : return false; /* end of scan */
4653 : :
4654 : 136 : JsonTableRescan(planstate->right);
4655 : :
4656 [ + + ]: 136 : if (!JsonTablePlanNextRow(planstate->right))
4657 : 64 : continue; /* next outer row */
4658 : :
4659 : 72 : planstate->advanceRight = true; /* next inner row */
4660 : : }
4661 [ + + ]: 540 : else if (!more)
4662 : : {
4663 [ + + ]: 168 : if (!JsonTablePlanNextRow(planstate->right))
4664 : 64 : return false; /* end of scan */
4665 : :
4666 : 104 : planstate->advanceRight = true; /* next inner row */
4667 : : }
4668 : :
4669 : 548 : break;
4670 : : }
4671 : : }
4672 : : else
4673 : : {
4674 : : /* reset context item if requested */
4675 [ + + ]: 3600 : if (planstate->reset)
4676 : : {
4677 : 730 : JsonTablePlanState *parent = planstate->parent;
4678 : :
4679 [ + - - + ]: 730 : Assert(parent != NULL && !parent->current.isnull);
4680 : 730 : JsonTableResetRowPattern(planstate, parent->current.value);
4681 : 730 : planstate->reset = false;
4682 : : }
4683 : :
4684 [ + + ]: 3600 : if (planstate->advanceNested)
4685 : : {
4686 : : /* fetch next nested row */
4687 : 998 : planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
4688 [ + + ]: 998 : if (planstate->advanceNested)
4689 : 684 : return true;
4690 : : }
4691 : :
4692 : : for (;;)
4693 : : {
4694 [ + + ]: 2972 : if (!JsonTablePlanScanNextRow(planstate))
4695 : 1180 : return false;
4696 : :
4697 [ + + ]: 1792 : if (planstate->nested == NULL)
4698 : 1282 : break;
4699 : :
4700 : 510 : JsonTableResetNestedPlan(planstate->nested);
4701 : 510 : planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
4702 : :
4703 [ + + + + ]: 510 : if (!planstate->advanceNested && !planstate->outerJoin)
4704 : 56 : continue;
4705 : :
4706 : : /*
4707 : : * We have a row to return: either the nested plan produced one,
4708 : : * or this is an outer join and we emit the parent row with the
4709 : : * nested columns set to NULL.
4710 : : */
4711 : 454 : break;
4712 : : }
4713 : : }
4714 : :
4715 : 2284 : return true;
4716 : : }
4717 : :
4718 : : /*
4719 : : * Advance a JsonTablePlan's path scan to its next row pattern match.
4720 : : *
4721 : : * This only moves this plan's own row pattern iterator forward and makes the
4722 : : * matched item the current row; driving and joining of any nested plan is the
4723 : : * responsibility of JsonTablePlanNextRow(). Returns false when this scan's
4724 : : * row pattern matches are exhausted.
4725 : : */
4726 : : static bool
941 amitlan@postgresql.o 4727 :CBC 2972 : JsonTablePlanScanNextRow(JsonTablePlanState *planstate)
4728 : : {
4729 : : JsonbValue *jbv;
4730 : : MemoryContext oldcxt;
4731 : :
4732 : : /* Fetch new row from the list of found values to set as active. */
231 tgl@sss.pgh.pa.us 4733 : 2972 : jbv = JsonValueListNext(&planstate->iter);
4734 : :
4735 : : /* End of list? */
945 amitlan@postgresql.o 4736 [ + + ]: 2972 : if (jbv == NULL)
4737 : : {
4738 : 1180 : planstate->current.value = PointerGetDatum(NULL);
4739 : 1180 : planstate->current.isnull = true;
4740 : 1180 : return false;
4741 : : }
4742 : :
4743 : : /*
4744 : : * Set current row item for subsequent JsonTableGetValue() calls for
4745 : : * evaluating individual columns.
4746 : : */
4747 : 1792 : oldcxt = MemoryContextSwitchTo(planstate->mcxt);
4748 : 1792 : planstate->current.value = JsonbPGetDatum(JsonbValueToJsonb(jbv));
4749 : 1792 : planstate->current.isnull = false;
4750 : 1792 : MemoryContextSwitchTo(oldcxt);
4751 : :
4752 : : /* Next row! */
4753 : 1792 : planstate->ordinal++;
4754 : :
4755 : : /* There are more rows. */
941 4756 : 1792 : return true;
4757 : : }
4758 : :
4759 : : /*
4760 : : * Re-evaluate the row pattern of a nested plan using the new parent row
4761 : : * pattern.
4762 : : */
4763 : : static void
4764 : 1119 : JsonTableResetNestedPlan(JsonTablePlanState *planstate)
4765 : : {
4766 : : /* This better be a child plan. */
4767 [ - + ]: 1119 : Assert(planstate->parent != NULL);
4768 [ + + ]: 1119 : if (IsA(planstate->plan, JsonTablePathScan))
4769 : : {
4770 : 875 : JsonTablePlanState *parent = planstate->parent;
4771 : :
119 akorotkov@postgresql 4772 :GNC 875 : planstate->reset = true;
4773 : 875 : planstate->advanceNested = false;
4774 : :
4775 [ + + ]: 875 : if (planstate->nested)
4776 : 121 : JsonTableResetNestedPlan(planstate->nested);
4777 : :
4778 : : /*
4779 : : * Reset this plan's transient scan state so that its columns read as
4780 : : * NULL until it is actually advanced. Re-evaluating the path against
4781 : : * the new parent row is deferred (see the reset flag) until the plan
4782 : : * is advanced by JsonTablePlanNextRow(), so that the path is not
4783 : : * evaluated more than once per parent row.
4784 : : */
4785 [ + + ]: 875 : if (!parent->current.isnull)
113 4786 : 754 : JsonTableRescan(planstate);
4787 : : }
941 amitlan@postgresql.o 4788 [ + - ]:CBC 244 : else if (IsA(planstate->plan, JsonTableSiblingJoin))
4789 : : {
4790 : 244 : JsonTableResetNestedPlan(planstate->left);
4791 : 244 : JsonTableResetNestedPlan(planstate->right);
119 akorotkov@postgresql 4792 :GNC 244 : planstate->advanceRight = false;
4793 : : }
941 amitlan@postgresql.o 4794 :CBC 1119 : }
4795 : :
4796 : : /*
4797 : : * JsonTableFetchRow
4798 : : * Prepare the next "current" row for upcoming GetValue calls.
4799 : : *
4800 : : * Returns false if no more rows can be returned.
4801 : : */
4802 : : static bool
945 4803 : 1652 : JsonTableFetchRow(TableFuncScanState *state)
4804 : : {
4805 : : JsonTableExecContext *cxt =
4806 : 1652 : GetJsonTableExecContext(state, "JsonTableFetchRow");
4807 : :
4808 : 1652 : return JsonTablePlanNextRow(cxt->rootplanstate);
4809 : : }
4810 : :
4811 : : /*
4812 : : * JsonTableGetValue
4813 : : * Return the value for column number 'colnum' for the current row.
4814 : : *
4815 : : * This leaks memory, so be sure to reset often the context in which it's
4816 : : * called.
4817 : : */
4818 : : static Datum
4819 : 5294 : JsonTableGetValue(TableFuncScanState *state, int colnum,
4820 : : Oid typid, int32 typmod, bool *isnull)
4821 : : {
4822 : : JsonTableExecContext *cxt =
4823 : 5294 : GetJsonTableExecContext(state, "JsonTableGetValue");
4824 : 5294 : ExprContext *econtext = state->ss.ps.ps_ExprContext;
4825 : 5294 : ExprState *estate = list_nth(state->colvalexprs, colnum);
941 4826 : 5294 : JsonTablePlanState *planstate = cxt->colplanstates[colnum];
945 4827 : 5294 : JsonTablePlanRowSource *current = &planstate->current;
4828 : : Datum result;
4829 : :
4830 : : /* Row pattern value is NULL */
4831 [ + + ]: 5294 : if (current->isnull)
4832 : : {
4833 : 972 : result = (Datum) 0;
4834 : 972 : *isnull = true;
4835 : : }
4836 : : /* Evaluate JsonExpr. */
4837 [ + + ]: 4322 : else if (estate)
4838 : : {
4839 : 3562 : Datum saved_caseValue = econtext->caseValue_datum;
4840 : 3562 : bool saved_caseIsNull = econtext->caseValue_isNull;
4841 : :
4842 : : /* Pass the row pattern value via CaseTestExpr. */
4843 : 3562 : econtext->caseValue_datum = current->value;
4844 : 3562 : econtext->caseValue_isNull = false;
4845 : :
4846 : 3562 : result = ExecEvalExpr(estate, econtext, isnull);
4847 : :
4848 : 3486 : econtext->caseValue_datum = saved_caseValue;
4849 : 3486 : econtext->caseValue_isNull = saved_caseIsNull;
4850 : : }
4851 : : /* ORDINAL column */
4852 : : else
4853 : : {
4854 : 760 : result = Int32GetDatum(planstate->ordinal);
4855 : 760 : *isnull = false;
4856 : : }
4857 : :
4858 : 5218 : return result;
4859 : : }
4860 : :
4861 : : /* Recursively reset planstate and its child nodes */
4862 : : static void
119 akorotkov@postgresql 4863 :GNC 2826 : JsonTableRescan(JsonTablePlanState *planstate)
4864 : : {
4865 [ + + ]: 2826 : if (IsA(planstate->plan, JsonTablePathScan))
4866 : : {
4867 : : /* Reset plan iterator to the beginning of the item list */
4868 : 2702 : JsonValueListInitIterator(&planstate->found, &planstate->iter);
4869 : 2702 : planstate->current.value = PointerGetDatum(NULL);
4870 : 2702 : planstate->current.isnull = true;
4871 : 2702 : planstate->ordinal = 0;
4872 : :
4873 [ + + ]: 2702 : if (planstate->nested)
4874 : 516 : JsonTableRescan(planstate->nested);
4875 : : }
4876 [ + - ]: 124 : else if (IsA(planstate->plan, JsonTableSiblingJoin))
4877 : : {
4878 : 124 : JsonTableRescan(planstate->left);
4879 : 124 : JsonTableRescan(planstate->right);
4880 : 124 : planstate->advanceRight = false;
4881 : : }
4882 : 2826 : }
|