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