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