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