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