
These were introduced by pgindent due to fixe to broken indentation (c.f. 8255c7a5eeba8). Previously the mis-indentation of function prototypes was creatively used to reduce indentation in a few places. As that formatting only exists in master and REL_12_STABLE, it seems better to fix it in both, rather than having some odd indentation in v12 that somebody might copy for future patches or such. Author: Andres Freund Discussion: https://postgr.es/m/20190728013754.jwcbe5nfyt3533vx@alap3.anarazel.de Backpatch: 12-
2288 lines
59 KiB
C
2288 lines
59 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* jsonpath_exec.c
|
|
* Routines for SQL/JSON path execution.
|
|
*
|
|
* Jsonpath is executed in the global context stored in JsonPathExecContext,
|
|
* which is passed to almost every function involved into execution. Entry
|
|
* point for jsonpath execution is executeJsonPath() function, which
|
|
* initializes execution context including initial JsonPathItem and JsonbValue,
|
|
* flags, stack for calculation of @ in filters.
|
|
*
|
|
* The result of jsonpath query execution is enum JsonPathExecResult and
|
|
* if succeeded sequence of JsonbValue, written to JsonValueList *found, which
|
|
* is passed through the jsonpath items. When found == NULL, we're inside
|
|
* exists-query and we're interested only in whether result is empty. In this
|
|
* case execution is stopped once first result item is found, and the only
|
|
* execution result is JsonPathExecResult. The values of JsonPathExecResult
|
|
* are following:
|
|
* - jperOk -- result sequence is not empty
|
|
* - jperNotFound -- result sequence is empty
|
|
* - jperError -- error occurred during execution
|
|
*
|
|
* Jsonpath is executed recursively (see executeItem()) starting form the
|
|
* first path item (which in turn might be, for instance, an arithmetic
|
|
* expression evaluated separately). On each step single JsonbValue obtained
|
|
* from previous path item is processed. The result of processing is a
|
|
* sequence of JsonbValue (probably empty), which is passed to the next path
|
|
* item one by one. When there is no next path item, then JsonbValue is added
|
|
* to the 'found' list. When found == NULL, then execution functions just
|
|
* return jperOk (see executeNextItem()).
|
|
*
|
|
* Many of jsonpath operations require automatic unwrapping of arrays in lax
|
|
* mode. So, if input value is array, then corresponding operation is
|
|
* processed not on array itself, but on all of its members one by one.
|
|
* executeItemOptUnwrapTarget() function have 'unwrap' argument, which indicates
|
|
* whether unwrapping of array is needed. When unwrap == true, each of array
|
|
* members is passed to executeItemOptUnwrapTarget() again but with unwrap == false
|
|
* in order to evade subsequent array unwrapping.
|
|
*
|
|
* All boolean expressions (predicates) are evaluated by executeBoolItem()
|
|
* function, which returns tri-state JsonPathBool. When error is occurred
|
|
* during predicate execution, it returns jpbUnknown. According to standard
|
|
* predicates can be only inside filters. But we support their usage as
|
|
* jsonpath expression. This helps us to implement @@ operator. In this case
|
|
* resulting JsonPathBool is transformed into jsonb bool or null.
|
|
*
|
|
* Arithmetic and boolean expression are evaluated recursively from expression
|
|
* tree top down to the leaves. Therefore, for binary arithmetic expressions
|
|
* we calculate operands first. Then we check that results are numeric
|
|
* singleton lists, calculate the result and pass it to the next path item.
|
|
*
|
|
* Copyright (c) 2019, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/jsonpath_exec.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "catalog/pg_collation.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "funcapi.h"
|
|
#include "lib/stringinfo.h"
|
|
#include "miscadmin.h"
|
|
#include "regex/regex.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/datum.h"
|
|
#include "utils/formatting.h"
|
|
#include "utils/float.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/json.h"
|
|
#include "utils/jsonpath.h"
|
|
#include "utils/date.h"
|
|
#include "utils/timestamp.h"
|
|
#include "utils/varlena.h"
|
|
|
|
|
|
/*
|
|
* Represents "base object" and it's "id" for .keyvalue() evaluation.
|
|
*/
|
|
typedef struct JsonBaseObjectInfo
|
|
{
|
|
JsonbContainer *jbc;
|
|
int id;
|
|
} JsonBaseObjectInfo;
|
|
|
|
/*
|
|
* Context of jsonpath execution.
|
|
*/
|
|
typedef struct JsonPathExecContext
|
|
{
|
|
Jsonb *vars; /* variables to substitute into jsonpath */
|
|
JsonbValue *root; /* for $ evaluation */
|
|
JsonbValue *current; /* for @ evaluation */
|
|
JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue()
|
|
* evaluation */
|
|
int lastGeneratedObjectId; /* "id" counter for .keyvalue()
|
|
* evaluation */
|
|
int innermostArraySize; /* for LAST array index evaluation */
|
|
bool laxMode; /* true for "lax" mode, false for "strict"
|
|
* mode */
|
|
bool ignoreStructuralErrors; /* with "true" structural errors such
|
|
* as absence of required json item or
|
|
* unexpected json item type are
|
|
* ignored */
|
|
bool throwErrors; /* with "false" all suppressible errors are
|
|
* suppressed */
|
|
} JsonPathExecContext;
|
|
|
|
/* Context for LIKE_REGEX execution. */
|
|
typedef struct JsonLikeRegexContext
|
|
{
|
|
text *regex;
|
|
int cflags;
|
|
} JsonLikeRegexContext;
|
|
|
|
/* Result of jsonpath predicate evaluation */
|
|
typedef enum JsonPathBool
|
|
{
|
|
jpbFalse = 0,
|
|
jpbTrue = 1,
|
|
jpbUnknown = 2
|
|
} JsonPathBool;
|
|
|
|
/* Result of jsonpath expression evaluation */
|
|
typedef enum JsonPathExecResult
|
|
{
|
|
jperOk = 0,
|
|
jperNotFound = 1,
|
|
jperError = 2
|
|
} JsonPathExecResult;
|
|
|
|
#define jperIsError(jper) ((jper) == jperError)
|
|
|
|
/*
|
|
* List of jsonb values with shortcut for single-value list.
|
|
*/
|
|
typedef struct JsonValueList
|
|
{
|
|
JsonbValue *singleton;
|
|
List *list;
|
|
} JsonValueList;
|
|
|
|
typedef struct JsonValueListIterator
|
|
{
|
|
JsonbValue *value;
|
|
List *list;
|
|
ListCell *next;
|
|
} JsonValueListIterator;
|
|
|
|
/* strict/lax flags is decomposed into four [un]wrap/error flags */
|
|
#define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode)
|
|
#define jspAutoUnwrap(cxt) ((cxt)->laxMode)
|
|
#define jspAutoWrap(cxt) ((cxt)->laxMode)
|
|
#define jspIgnoreStructuralErrors(cxt) ((cxt)->ignoreStructuralErrors)
|
|
#define jspThrowErrors(cxt) ((cxt)->throwErrors)
|
|
|
|
/* Convenience macro: return or throw error depending on context */
|
|
#define RETURN_ERROR(throw_error) \
|
|
do { \
|
|
if (jspThrowErrors(cxt)) \
|
|
throw_error; \
|
|
else \
|
|
return jperError; \
|
|
} while (0)
|
|
|
|
typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
|
|
JsonbValue *larg,
|
|
JsonbValue *rarg,
|
|
void *param);
|
|
typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
|
|
|
|
static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
|
|
Jsonb *json, bool throwErrors, JsonValueList *result);
|
|
static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
|
|
static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb,
|
|
JsonValueList *found, bool unwrap);
|
|
static JsonPathExecResult executeItemUnwrapTargetArray(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb,
|
|
JsonValueList *found, bool unwrapElements);
|
|
static JsonPathExecResult executeNextItem(JsonPathExecContext *cxt,
|
|
JsonPathItem *cur, JsonPathItem *next,
|
|
JsonbValue *v, JsonValueList *found, bool copy);
|
|
static JsonPathExecResult executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
|
|
bool unwrap, JsonValueList *found);
|
|
static JsonPathExecResult executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, bool unwrap, JsonValueList *found);
|
|
static JsonPathBool executeBoolItem(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb, bool canHaveNext);
|
|
static JsonPathBool executeNestedBoolItem(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb);
|
|
static JsonPathExecResult executeAnyItem(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbContainer *jbc, JsonValueList *found,
|
|
uint32 level, uint32 first, uint32 last,
|
|
bool ignoreStructuralErrors, bool unwrapNext);
|
|
static JsonPathBool executePredicate(JsonPathExecContext *cxt,
|
|
JsonPathItem *pred, JsonPathItem *larg, JsonPathItem *rarg,
|
|
JsonbValue *jb, bool unwrapRightArg,
|
|
JsonPathPredicateCallback exec, void *param);
|
|
static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb,
|
|
BinaryArithmFunc func, JsonValueList *found);
|
|
static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
|
|
JsonValueList *found);
|
|
static JsonPathBool executeStartsWith(JsonPathItem *jsp,
|
|
JsonbValue *whole, JsonbValue *initial, void *param);
|
|
static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
|
|
JsonbValue *rarg, void *param);
|
|
static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
|
|
JsonValueList *found);
|
|
static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
|
|
static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonValueList *found, JsonPathBool res);
|
|
static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
|
|
JsonbValue *value);
|
|
static void getJsonPathVariable(JsonPathExecContext *cxt,
|
|
JsonPathItem *variable, Jsonb *vars, JsonbValue *value);
|
|
static int JsonbArraySize(JsonbValue *jb);
|
|
static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
|
|
JsonbValue *rv, void *p);
|
|
static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
|
|
static int compareNumeric(Numeric a, Numeric b);
|
|
static JsonbValue *copyJsonbValue(JsonbValue *src);
|
|
static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp, JsonbValue *jb, int32 *index);
|
|
static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt,
|
|
JsonbValue *jbv, int32 id);
|
|
static void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv);
|
|
static int JsonValueListLength(const JsonValueList *jvl);
|
|
static bool JsonValueListIsEmpty(JsonValueList *jvl);
|
|
static JsonbValue *JsonValueListHead(JsonValueList *jvl);
|
|
static List *JsonValueListGetList(JsonValueList *jvl);
|
|
static void JsonValueListInitIterator(const JsonValueList *jvl,
|
|
JsonValueListIterator *it);
|
|
static JsonbValue *JsonValueListNext(const JsonValueList *jvl,
|
|
JsonValueListIterator *it);
|
|
static int JsonbType(JsonbValue *jb);
|
|
static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
|
|
static int JsonbType(JsonbValue *jb);
|
|
static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
|
|
static JsonbValue *wrapItemsInArray(const JsonValueList *items);
|
|
|
|
/****************** User interface to JsonPath executor ********************/
|
|
|
|
/*
|
|
* jsonb_path_exists
|
|
* Returns true if jsonpath returns at least one item for the specified
|
|
* jsonb value. This function and jsonb_path_match() are used to
|
|
* implement @? and @@ operators, which in turn are intended to have an
|
|
* index support. Thus, it's desirable to make it easier to achieve
|
|
* consistency between index scan results and sequential scan results.
|
|
* So, we throw as less errors as possible. Regarding this function,
|
|
* such behavior also matches behavior of JSON_EXISTS() clause of
|
|
* SQL/JSON. Regarding jsonb_path_match(), this function doesn't have
|
|
* an analogy in SQL/JSON, so we define its behavior on our own.
|
|
*/
|
|
Datum
|
|
jsonb_path_exists(PG_FUNCTION_ARGS)
|
|
{
|
|
Jsonb *jb = PG_GETARG_JSONB_P(0);
|
|
JsonPath *jp = PG_GETARG_JSONPATH_P(1);
|
|
JsonPathExecResult res;
|
|
Jsonb *vars = NULL;
|
|
bool silent = true;
|
|
|
|
if (PG_NARGS() == 4)
|
|
{
|
|
vars = PG_GETARG_JSONB_P(2);
|
|
silent = PG_GETARG_BOOL(3);
|
|
}
|
|
|
|
res = executeJsonPath(jp, vars, jb, !silent, NULL);
|
|
|
|
PG_FREE_IF_COPY(jb, 0);
|
|
PG_FREE_IF_COPY(jp, 1);
|
|
|
|
if (jperIsError(res))
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_BOOL(res == jperOk);
|
|
}
|
|
|
|
/*
|
|
* jsonb_path_exists_opr
|
|
* Implementation of operator "jsonb @? jsonpath" (2-argument version of
|
|
* jsonb_path_exists()).
|
|
*/
|
|
Datum
|
|
jsonb_path_exists_opr(PG_FUNCTION_ARGS)
|
|
{
|
|
/* just call the other one -- it can handle both cases */
|
|
return jsonb_path_exists(fcinfo);
|
|
}
|
|
|
|
/*
|
|
* jsonb_path_match
|
|
* Returns jsonpath predicate result item for the specified jsonb value.
|
|
* See jsonb_path_exists() comment for details regarding error handling.
|
|
*/
|
|
Datum
|
|
jsonb_path_match(PG_FUNCTION_ARGS)
|
|
{
|
|
Jsonb *jb = PG_GETARG_JSONB_P(0);
|
|
JsonPath *jp = PG_GETARG_JSONPATH_P(1);
|
|
JsonValueList found = {0};
|
|
Jsonb *vars = NULL;
|
|
bool silent = true;
|
|
|
|
if (PG_NARGS() == 4)
|
|
{
|
|
vars = PG_GETARG_JSONB_P(2);
|
|
silent = PG_GETARG_BOOL(3);
|
|
}
|
|
|
|
(void) executeJsonPath(jp, vars, jb, !silent, &found);
|
|
|
|
PG_FREE_IF_COPY(jb, 0);
|
|
PG_FREE_IF_COPY(jp, 1);
|
|
|
|
if (JsonValueListLength(&found) == 1)
|
|
{
|
|
JsonbValue *jbv = JsonValueListHead(&found);
|
|
|
|
if (jbv->type == jbvBool)
|
|
PG_RETURN_BOOL(jbv->val.boolean);
|
|
|
|
if (jbv->type == jbvNull)
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
if (!silent)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
|
|
errmsg("single boolean result is expected")));
|
|
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
/*
|
|
* jsonb_path_match_opr
|
|
* Implementation of operator "jsonb @@ jsonpath" (2-argument version of
|
|
* jsonb_path_match()).
|
|
*/
|
|
Datum
|
|
jsonb_path_match_opr(PG_FUNCTION_ARGS)
|
|
{
|
|
/* just call the other one -- it can handle both cases */
|
|
return jsonb_path_match(fcinfo);
|
|
}
|
|
|
|
/*
|
|
* jsonb_path_query
|
|
* Executes jsonpath for given jsonb document and returns result as
|
|
* rowset.
|
|
*/
|
|
Datum
|
|
jsonb_path_query(PG_FUNCTION_ARGS)
|
|
{
|
|
FuncCallContext *funcctx;
|
|
List *found;
|
|
JsonbValue *v;
|
|
ListCell *c;
|
|
|
|
if (SRF_IS_FIRSTCALL())
|
|
{
|
|
JsonPath *jp;
|
|
Jsonb *jb;
|
|
MemoryContext oldcontext;
|
|
Jsonb *vars;
|
|
bool silent;
|
|
JsonValueList found = {0};
|
|
|
|
funcctx = SRF_FIRSTCALL_INIT();
|
|
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
|
|
|
jb = PG_GETARG_JSONB_P_COPY(0);
|
|
jp = PG_GETARG_JSONPATH_P_COPY(1);
|
|
vars = PG_GETARG_JSONB_P_COPY(2);
|
|
silent = PG_GETARG_BOOL(3);
|
|
|
|
(void) executeJsonPath(jp, vars, jb, !silent, &found);
|
|
|
|
funcctx->user_fctx = JsonValueListGetList(&found);
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
}
|
|
|
|
funcctx = SRF_PERCALL_SETUP();
|
|
found = funcctx->user_fctx;
|
|
|
|
c = list_head(found);
|
|
|
|
if (c == NULL)
|
|
SRF_RETURN_DONE(funcctx);
|
|
|
|
v = lfirst(c);
|
|
funcctx->user_fctx = list_delete_first(found);
|
|
|
|
SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
|
|
}
|
|
|
|
/*
|
|
* jsonb_path_query_array
|
|
* Executes jsonpath for given jsonb document and returns result as
|
|
* jsonb array.
|
|
*/
|
|
Datum
|
|
jsonb_path_query_array(PG_FUNCTION_ARGS)
|
|
{
|
|
Jsonb *jb = PG_GETARG_JSONB_P(0);
|
|
JsonPath *jp = PG_GETARG_JSONPATH_P(1);
|
|
JsonValueList found = {0};
|
|
Jsonb *vars = PG_GETARG_JSONB_P(2);
|
|
bool silent = PG_GETARG_BOOL(3);
|
|
|
|
(void) executeJsonPath(jp, vars, jb, !silent, &found);
|
|
|
|
PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
|
|
}
|
|
|
|
/*
|
|
* jsonb_path_query_first
|
|
* Executes jsonpath for given jsonb document and returns first result
|
|
* item. If there are no items, NULL returned.
|
|
*/
|
|
Datum
|
|
jsonb_path_query_first(PG_FUNCTION_ARGS)
|
|
{
|
|
Jsonb *jb = PG_GETARG_JSONB_P(0);
|
|
JsonPath *jp = PG_GETARG_JSONPATH_P(1);
|
|
JsonValueList found = {0};
|
|
Jsonb *vars = PG_GETARG_JSONB_P(2);
|
|
bool silent = PG_GETARG_BOOL(3);
|
|
|
|
(void) executeJsonPath(jp, vars, jb, !silent, &found);
|
|
|
|
if (JsonValueListLength(&found) >= 1)
|
|
PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
|
|
else
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
/********************Execute functions for JsonPath**************************/
|
|
|
|
/*
|
|
* Interface to jsonpath executor
|
|
*
|
|
* 'path' - jsonpath to be executed
|
|
* 'vars' - variables to be substituted to jsonpath
|
|
* 'json' - target document for jsonpath evaluation
|
|
* 'throwErrors' - whether we should throw suppressible errors
|
|
* 'result' - list to store result items into
|
|
*
|
|
* Returns an error if a recoverable error happens during processing, or NULL
|
|
* on no error.
|
|
*
|
|
* Note, jsonb and jsonpath values should be available and untoasted during
|
|
* work because JsonPathItem, JsonbValue and result item could have pointers
|
|
* into input values. If caller needs to just check if document matches
|
|
* jsonpath, then it doesn't provide a result arg. In this case executor
|
|
* works till first positive result and does not check the rest if possible.
|
|
* In other case it tries to find all the satisfied result items.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
|
|
JsonValueList *result)
|
|
{
|
|
JsonPathExecContext cxt;
|
|
JsonPathExecResult res;
|
|
JsonPathItem jsp;
|
|
JsonbValue jbv;
|
|
|
|
jspInit(&jsp, path);
|
|
|
|
if (!JsonbExtractScalar(&json->root, &jbv))
|
|
JsonbInitBinary(&jbv, json);
|
|
|
|
if (vars && !JsonContainerIsObject(&vars->root))
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("\"vars\" argument is not an object"),
|
|
errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object.")));
|
|
}
|
|
|
|
cxt.vars = vars;
|
|
cxt.laxMode = (path->header & JSONPATH_LAX) != 0;
|
|
cxt.ignoreStructuralErrors = cxt.laxMode;
|
|
cxt.root = &jbv;
|
|
cxt.current = &jbv;
|
|
cxt.baseObject.jbc = NULL;
|
|
cxt.baseObject.id = 0;
|
|
cxt.lastGeneratedObjectId = vars ? 2 : 1;
|
|
cxt.innermostArraySize = -1;
|
|
cxt.throwErrors = throwErrors;
|
|
|
|
if (jspStrictAbsenseOfErrors(&cxt) && !result)
|
|
{
|
|
/*
|
|
* In strict mode we must get a complete list of values to check that
|
|
* there are no errors at all.
|
|
*/
|
|
JsonValueList vals = {0};
|
|
|
|
res = executeItem(&cxt, &jsp, &jbv, &vals);
|
|
|
|
if (jperIsError(res))
|
|
return res;
|
|
|
|
return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
|
|
}
|
|
|
|
res = executeItem(&cxt, &jsp, &jbv, result);
|
|
|
|
Assert(!throwErrors || !jperIsError(res));
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Execute jsonpath with automatic unwrapping of current item in lax mode.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, JsonValueList *found)
|
|
{
|
|
return executeItemOptUnwrapTarget(cxt, jsp, jb, found, jspAutoUnwrap(cxt));
|
|
}
|
|
|
|
/*
|
|
* Main jsonpath executor function: walks on jsonpath structure, finds
|
|
* relevant parts of jsonb and evaluates expressions over them.
|
|
* When 'unwrap' is true current SQL/JSON item is unwrapped if it is an array.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, JsonValueList *found, bool unwrap)
|
|
{
|
|
JsonPathItem elem;
|
|
JsonPathExecResult res = jperNotFound;
|
|
JsonBaseObjectInfo baseObject;
|
|
|
|
check_stack_depth();
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
switch (jsp->type)
|
|
{
|
|
/* all boolean item types: */
|
|
case jpiAnd:
|
|
case jpiOr:
|
|
case jpiNot:
|
|
case jpiIsUnknown:
|
|
case jpiEqual:
|
|
case jpiNotEqual:
|
|
case jpiLess:
|
|
case jpiGreater:
|
|
case jpiLessOrEqual:
|
|
case jpiGreaterOrEqual:
|
|
case jpiExists:
|
|
case jpiStartsWith:
|
|
case jpiLikeRegex:
|
|
{
|
|
JsonPathBool st = executeBoolItem(cxt, jsp, jb, true);
|
|
|
|
res = appendBoolResult(cxt, jsp, found, st);
|
|
break;
|
|
}
|
|
|
|
case jpiKey:
|
|
if (JsonbType(jb) == jbvObject)
|
|
{
|
|
JsonbValue *v;
|
|
JsonbValue key;
|
|
|
|
key.type = jbvString;
|
|
key.val.string.val = jspGetString(jsp, &key.val.string.len);
|
|
|
|
v = findJsonbValueFromContainer(jb->val.binary.data,
|
|
JB_FOBJECT, &key);
|
|
|
|
if (v != NULL)
|
|
{
|
|
res = executeNextItem(cxt, jsp, NULL,
|
|
v, found, false);
|
|
|
|
/* free value if it was not added to found list */
|
|
if (jspHasNext(jsp) || !found)
|
|
pfree(v);
|
|
}
|
|
else if (!jspIgnoreStructuralErrors(cxt))
|
|
{
|
|
Assert(found);
|
|
|
|
if (!jspThrowErrors(cxt))
|
|
return jperError;
|
|
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_JSON_MEMBER_NOT_FOUND), \
|
|
errmsg("JSON object does not contain key \"%s\"",
|
|
pnstrdup(key.val.string.val,
|
|
key.val.string.len))));
|
|
}
|
|
}
|
|
else if (unwrap && JsonbType(jb) == jbvArray)
|
|
return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
|
|
else if (!jspIgnoreStructuralErrors(cxt))
|
|
{
|
|
Assert(found);
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_JSON_MEMBER_NOT_FOUND),
|
|
errmsg("jsonpath member accessor can only be applied to an object"))));
|
|
}
|
|
break;
|
|
|
|
case jpiRoot:
|
|
jb = cxt->root;
|
|
baseObject = setBaseObject(cxt, jb, 0);
|
|
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
|
|
cxt->baseObject = baseObject;
|
|
break;
|
|
|
|
case jpiCurrent:
|
|
res = executeNextItem(cxt, jsp, NULL, cxt->current,
|
|
found, true);
|
|
break;
|
|
|
|
case jpiAnyArray:
|
|
if (JsonbType(jb) == jbvArray)
|
|
{
|
|
bool hasNext = jspGetNext(jsp, &elem);
|
|
|
|
res = executeItemUnwrapTargetArray(cxt, hasNext ? &elem : NULL,
|
|
jb, found, jspAutoUnwrap(cxt));
|
|
}
|
|
else if (jspAutoWrap(cxt))
|
|
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
|
|
else if (!jspIgnoreStructuralErrors(cxt))
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
|
|
errmsg("jsonpath wildcard array accessor can only be applied to an array"))));
|
|
break;
|
|
|
|
case jpiIndexArray:
|
|
if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
|
|
{
|
|
int innermostArraySize = cxt->innermostArraySize;
|
|
int i;
|
|
int size = JsonbArraySize(jb);
|
|
bool singleton = size < 0;
|
|
bool hasNext = jspGetNext(jsp, &elem);
|
|
|
|
if (singleton)
|
|
size = 1;
|
|
|
|
cxt->innermostArraySize = size; /* for LAST evaluation */
|
|
|
|
for (i = 0; i < jsp->content.array.nelems; i++)
|
|
{
|
|
JsonPathItem from;
|
|
JsonPathItem to;
|
|
int32 index;
|
|
int32 index_from;
|
|
int32 index_to;
|
|
bool range = jspGetArraySubscript(jsp, &from,
|
|
&to, i);
|
|
|
|
res = getArrayIndex(cxt, &from, jb, &index_from);
|
|
|
|
if (jperIsError(res))
|
|
break;
|
|
|
|
if (range)
|
|
{
|
|
res = getArrayIndex(cxt, &to, jb, &index_to);
|
|
|
|
if (jperIsError(res))
|
|
break;
|
|
}
|
|
else
|
|
index_to = index_from;
|
|
|
|
if (!jspIgnoreStructuralErrors(cxt) &&
|
|
(index_from < 0 ||
|
|
index_from > index_to ||
|
|
index_to >= size))
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
|
|
errmsg("jsonpath array subscript is out of bounds"))));
|
|
|
|
if (index_from < 0)
|
|
index_from = 0;
|
|
|
|
if (index_to >= size)
|
|
index_to = size - 1;
|
|
|
|
res = jperNotFound;
|
|
|
|
for (index = index_from; index <= index_to; index++)
|
|
{
|
|
JsonbValue *v;
|
|
bool copy;
|
|
|
|
if (singleton)
|
|
{
|
|
v = jb;
|
|
copy = true;
|
|
}
|
|
else
|
|
{
|
|
v = getIthJsonbValueFromContainer(jb->val.binary.data,
|
|
(uint32) index);
|
|
|
|
if (v == NULL)
|
|
continue;
|
|
|
|
copy = false;
|
|
}
|
|
|
|
if (!hasNext && !found)
|
|
return jperOk;
|
|
|
|
res = executeNextItem(cxt, jsp, &elem, v, found,
|
|
copy);
|
|
|
|
if (jperIsError(res))
|
|
break;
|
|
|
|
if (res == jperOk && !found)
|
|
break;
|
|
}
|
|
|
|
if (jperIsError(res))
|
|
break;
|
|
|
|
if (res == jperOk && !found)
|
|
break;
|
|
}
|
|
|
|
cxt->innermostArraySize = innermostArraySize;
|
|
}
|
|
else if (!jspIgnoreStructuralErrors(cxt))
|
|
{
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
|
|
errmsg("jsonpath array accessor can only be applied to an array"))));
|
|
}
|
|
break;
|
|
|
|
case jpiLast:
|
|
{
|
|
JsonbValue tmpjbv;
|
|
JsonbValue *lastjbv;
|
|
int last;
|
|
bool hasNext = jspGetNext(jsp, &elem);
|
|
|
|
if (cxt->innermostArraySize < 0)
|
|
elog(ERROR, "evaluating jsonpath LAST outside of array subscript");
|
|
|
|
if (!hasNext && !found)
|
|
{
|
|
res = jperOk;
|
|
break;
|
|
}
|
|
|
|
last = cxt->innermostArraySize - 1;
|
|
|
|
lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv));
|
|
|
|
lastjbv->type = jbvNumeric;
|
|
lastjbv->val.numeric =
|
|
DatumGetNumeric(DirectFunctionCall1(int4_numeric,
|
|
Int32GetDatum(last)));
|
|
|
|
res = executeNextItem(cxt, jsp, &elem,
|
|
lastjbv, found, hasNext);
|
|
}
|
|
break;
|
|
|
|
case jpiAnyKey:
|
|
if (JsonbType(jb) == jbvObject)
|
|
{
|
|
bool hasNext = jspGetNext(jsp, &elem);
|
|
|
|
if (jb->type != jbvBinary)
|
|
elog(ERROR, "invalid jsonb object type: %d", jb->type);
|
|
|
|
return executeAnyItem
|
|
(cxt, hasNext ? &elem : NULL,
|
|
jb->val.binary.data, found, 1, 1, 1,
|
|
false, jspAutoUnwrap(cxt));
|
|
}
|
|
else if (unwrap && JsonbType(jb) == jbvArray)
|
|
return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
|
|
else if (!jspIgnoreStructuralErrors(cxt))
|
|
{
|
|
Assert(found);
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_JSON_OBJECT_NOT_FOUND),
|
|
errmsg("jsonpath wildcard member accessor can only be applied to an object"))));
|
|
}
|
|
break;
|
|
|
|
case jpiAdd:
|
|
return executeBinaryArithmExpr(cxt, jsp, jb,
|
|
numeric_add_opt_error, found);
|
|
|
|
case jpiSub:
|
|
return executeBinaryArithmExpr(cxt, jsp, jb,
|
|
numeric_sub_opt_error, found);
|
|
|
|
case jpiMul:
|
|
return executeBinaryArithmExpr(cxt, jsp, jb,
|
|
numeric_mul_opt_error, found);
|
|
|
|
case jpiDiv:
|
|
return executeBinaryArithmExpr(cxt, jsp, jb,
|
|
numeric_div_opt_error, found);
|
|
|
|
case jpiMod:
|
|
return executeBinaryArithmExpr(cxt, jsp, jb,
|
|
numeric_mod_opt_error, found);
|
|
|
|
case jpiPlus:
|
|
return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
|
|
|
|
case jpiMinus:
|
|
return executeUnaryArithmExpr(cxt, jsp, jb, numeric_uminus,
|
|
found);
|
|
|
|
case jpiFilter:
|
|
{
|
|
JsonPathBool st;
|
|
|
|
if (unwrap && JsonbType(jb) == jbvArray)
|
|
return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
|
|
false);
|
|
|
|
jspGetArg(jsp, &elem);
|
|
st = executeNestedBoolItem(cxt, &elem, jb);
|
|
if (st != jpbTrue)
|
|
res = jperNotFound;
|
|
else
|
|
res = executeNextItem(cxt, jsp, NULL,
|
|
jb, found, true);
|
|
break;
|
|
}
|
|
|
|
case jpiAny:
|
|
{
|
|
bool hasNext = jspGetNext(jsp, &elem);
|
|
|
|
/* first try without any intermediate steps */
|
|
if (jsp->content.anybounds.first == 0)
|
|
{
|
|
bool savedIgnoreStructuralErrors;
|
|
|
|
savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors;
|
|
cxt->ignoreStructuralErrors = true;
|
|
res = executeNextItem(cxt, jsp, &elem,
|
|
jb, found, true);
|
|
cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors;
|
|
|
|
if (res == jperOk && !found)
|
|
break;
|
|
}
|
|
|
|
if (jb->type == jbvBinary)
|
|
res = executeAnyItem
|
|
(cxt, hasNext ? &elem : NULL,
|
|
jb->val.binary.data, found,
|
|
1,
|
|
jsp->content.anybounds.first,
|
|
jsp->content.anybounds.last,
|
|
true, jspAutoUnwrap(cxt));
|
|
break;
|
|
}
|
|
|
|
case jpiNull:
|
|
case jpiBool:
|
|
case jpiNumeric:
|
|
case jpiString:
|
|
case jpiVariable:
|
|
{
|
|
JsonbValue vbuf;
|
|
JsonbValue *v;
|
|
bool hasNext = jspGetNext(jsp, &elem);
|
|
|
|
if (!hasNext && !found)
|
|
{
|
|
res = jperOk; /* skip evaluation */
|
|
break;
|
|
}
|
|
|
|
v = hasNext ? &vbuf : palloc(sizeof(*v));
|
|
|
|
baseObject = cxt->baseObject;
|
|
getJsonPathItem(cxt, jsp, v);
|
|
|
|
res = executeNextItem(cxt, jsp, &elem,
|
|
v, found, hasNext);
|
|
cxt->baseObject = baseObject;
|
|
}
|
|
break;
|
|
|
|
case jpiType:
|
|
{
|
|
JsonbValue *jbv = palloc(sizeof(*jbv));
|
|
|
|
jbv->type = jbvString;
|
|
jbv->val.string.val = pstrdup(JsonbTypeName(jb));
|
|
jbv->val.string.len = strlen(jbv->val.string.val);
|
|
|
|
res = executeNextItem(cxt, jsp, NULL, jbv,
|
|
found, false);
|
|
}
|
|
break;
|
|
|
|
case jpiSize:
|
|
{
|
|
int size = JsonbArraySize(jb);
|
|
|
|
if (size < 0)
|
|
{
|
|
if (!jspAutoWrap(cxt))
|
|
{
|
|
if (!jspIgnoreStructuralErrors(cxt))
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
|
|
errmsg("jsonpath item method .%s() can only be applied to an array",
|
|
jspOperationName(jsp->type)))));
|
|
break;
|
|
}
|
|
|
|
size = 1;
|
|
}
|
|
|
|
jb = palloc(sizeof(*jb));
|
|
|
|
jb->type = jbvNumeric;
|
|
jb->val.numeric =
|
|
DatumGetNumeric(DirectFunctionCall1(int4_numeric,
|
|
Int32GetDatum(size)));
|
|
|
|
res = executeNextItem(cxt, jsp, NULL, jb, found, false);
|
|
}
|
|
break;
|
|
|
|
case jpiAbs:
|
|
return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_abs,
|
|
found);
|
|
|
|
case jpiFloor:
|
|
return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_floor,
|
|
found);
|
|
|
|
case jpiCeiling:
|
|
return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_ceil,
|
|
found);
|
|
|
|
case jpiDouble:
|
|
{
|
|
JsonbValue jbv;
|
|
|
|
if (unwrap && JsonbType(jb) == jbvArray)
|
|
return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
|
|
false);
|
|
|
|
if (jb->type == jbvNumeric)
|
|
{
|
|
char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
|
|
NumericGetDatum(jb->val.numeric)));
|
|
bool have_error = false;
|
|
|
|
(void) float8in_internal_opt_error(tmp,
|
|
NULL,
|
|
"double precision",
|
|
tmp,
|
|
&have_error);
|
|
|
|
if (have_error)
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
|
|
errmsg("jsonpath item method .%s() can only be applied to a numeric value",
|
|
jspOperationName(jsp->type)))));
|
|
res = jperOk;
|
|
}
|
|
else if (jb->type == jbvString)
|
|
{
|
|
/* cast string as double */
|
|
double val;
|
|
char *tmp = pnstrdup(jb->val.string.val,
|
|
jb->val.string.len);
|
|
bool have_error = false;
|
|
|
|
val = float8in_internal_opt_error(tmp,
|
|
NULL,
|
|
"double precision",
|
|
tmp,
|
|
&have_error);
|
|
|
|
if (have_error || isinf(val))
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
|
|
errmsg("jsonpath item method .%s() can only be applied to a numeric value",
|
|
jspOperationName(jsp->type)))));
|
|
|
|
jb = &jbv;
|
|
jb->type = jbvNumeric;
|
|
jb->val.numeric = DatumGetNumeric(DirectFunctionCall1(float8_numeric,
|
|
Float8GetDatum(val)));
|
|
res = jperOk;
|
|
}
|
|
|
|
if (res == jperNotFound)
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
|
|
errmsg("jsonpath item method .%s() can only be applied to a string or numeric value",
|
|
jspOperationName(jsp->type)))));
|
|
|
|
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
|
|
}
|
|
break;
|
|
|
|
case jpiKeyValue:
|
|
if (unwrap && JsonbType(jb) == jbvArray)
|
|
return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
|
|
|
|
return executeKeyValueMethod(cxt, jsp, jb, found);
|
|
|
|
default:
|
|
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Unwrap current array item and execute jsonpath for each of its elements.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, JsonValueList *found,
|
|
bool unwrapElements)
|
|
{
|
|
if (jb->type != jbvBinary)
|
|
{
|
|
Assert(jb->type != jbvArray);
|
|
elog(ERROR, "invalid jsonb array value type: %d", jb->type);
|
|
}
|
|
|
|
return executeAnyItem
|
|
(cxt, jsp, jb->val.binary.data, found, 1, 1, 1,
|
|
false, unwrapElements);
|
|
}
|
|
|
|
/*
|
|
* Execute next jsonpath item if exists. Otherwise put "v" to the "found"
|
|
* list if provided.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeNextItem(JsonPathExecContext *cxt,
|
|
JsonPathItem *cur, JsonPathItem *next,
|
|
JsonbValue *v, JsonValueList *found, bool copy)
|
|
{
|
|
JsonPathItem elem;
|
|
bool hasNext;
|
|
|
|
if (!cur)
|
|
hasNext = next != NULL;
|
|
else if (next)
|
|
hasNext = jspHasNext(cur);
|
|
else
|
|
{
|
|
next = &elem;
|
|
hasNext = jspGetNext(cur, next);
|
|
}
|
|
|
|
if (hasNext)
|
|
return executeItem(cxt, next, v, found);
|
|
|
|
if (found)
|
|
JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
|
|
|
|
return jperOk;
|
|
}
|
|
|
|
/*
|
|
* Same as executeItem(), but when "unwrap == true" automatically unwraps
|
|
* each array item from the resulting sequence in lax mode.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, bool unwrap,
|
|
JsonValueList *found)
|
|
{
|
|
if (unwrap && jspAutoUnwrap(cxt))
|
|
{
|
|
JsonValueList seq = {0};
|
|
JsonValueListIterator it;
|
|
JsonPathExecResult res = executeItem(cxt, jsp, jb, &seq);
|
|
JsonbValue *item;
|
|
|
|
if (jperIsError(res))
|
|
return res;
|
|
|
|
JsonValueListInitIterator(&seq, &it);
|
|
while ((item = JsonValueListNext(&seq, &it)))
|
|
{
|
|
Assert(item->type != jbvArray);
|
|
|
|
if (JsonbType(item) == jbvArray)
|
|
executeItemUnwrapTargetArray(cxt, NULL, item, found, false);
|
|
else
|
|
JsonValueListAppend(found, item);
|
|
}
|
|
|
|
return jperOk;
|
|
}
|
|
|
|
return executeItem(cxt, jsp, jb, found);
|
|
}
|
|
|
|
/*
|
|
* Same as executeItemOptUnwrapResult(), but with error suppression.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt,
|
|
JsonPathItem *jsp,
|
|
JsonbValue *jb, bool unwrap,
|
|
JsonValueList *found)
|
|
{
|
|
JsonPathExecResult res;
|
|
bool throwErrors = cxt->throwErrors;
|
|
|
|
cxt->throwErrors = false;
|
|
res = executeItemOptUnwrapResult(cxt, jsp, jb, unwrap, found);
|
|
cxt->throwErrors = throwErrors;
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Execute boolean-valued jsonpath expression. */
|
|
static JsonPathBool
|
|
executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, bool canHaveNext)
|
|
{
|
|
JsonPathItem larg;
|
|
JsonPathItem rarg;
|
|
JsonPathBool res;
|
|
JsonPathBool res2;
|
|
|
|
if (!canHaveNext && jspHasNext(jsp))
|
|
elog(ERROR, "boolean jsonpath item cannot have next item");
|
|
|
|
switch (jsp->type)
|
|
{
|
|
case jpiAnd:
|
|
jspGetLeftArg(jsp, &larg);
|
|
res = executeBoolItem(cxt, &larg, jb, false);
|
|
|
|
if (res == jpbFalse)
|
|
return jpbFalse;
|
|
|
|
/*
|
|
* SQL/JSON says that we should check second arg in case of
|
|
* jperError
|
|
*/
|
|
|
|
jspGetRightArg(jsp, &rarg);
|
|
res2 = executeBoolItem(cxt, &rarg, jb, false);
|
|
|
|
return res2 == jpbTrue ? res : res2;
|
|
|
|
case jpiOr:
|
|
jspGetLeftArg(jsp, &larg);
|
|
res = executeBoolItem(cxt, &larg, jb, false);
|
|
|
|
if (res == jpbTrue)
|
|
return jpbTrue;
|
|
|
|
jspGetRightArg(jsp, &rarg);
|
|
res2 = executeBoolItem(cxt, &rarg, jb, false);
|
|
|
|
return res2 == jpbFalse ? res : res2;
|
|
|
|
case jpiNot:
|
|
jspGetArg(jsp, &larg);
|
|
|
|
res = executeBoolItem(cxt, &larg, jb, false);
|
|
|
|
if (res == jpbUnknown)
|
|
return jpbUnknown;
|
|
|
|
return res == jpbTrue ? jpbFalse : jpbTrue;
|
|
|
|
case jpiIsUnknown:
|
|
jspGetArg(jsp, &larg);
|
|
res = executeBoolItem(cxt, &larg, jb, false);
|
|
return res == jpbUnknown ? jpbTrue : jpbFalse;
|
|
|
|
case jpiEqual:
|
|
case jpiNotEqual:
|
|
case jpiLess:
|
|
case jpiGreater:
|
|
case jpiLessOrEqual:
|
|
case jpiGreaterOrEqual:
|
|
jspGetLeftArg(jsp, &larg);
|
|
jspGetRightArg(jsp, &rarg);
|
|
return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
|
|
executeComparison, NULL);
|
|
|
|
case jpiStartsWith: /* 'whole STARTS WITH initial' */
|
|
jspGetLeftArg(jsp, &larg); /* 'whole' */
|
|
jspGetRightArg(jsp, &rarg); /* 'initial' */
|
|
return executePredicate(cxt, jsp, &larg, &rarg, jb, false,
|
|
executeStartsWith, NULL);
|
|
|
|
case jpiLikeRegex: /* 'expr LIKE_REGEX pattern FLAGS flags' */
|
|
{
|
|
/*
|
|
* 'expr' is a sequence-returning expression. 'pattern' is a
|
|
* regex string literal. SQL/JSON standard requires XQuery
|
|
* regexes, but we use Postgres regexes here. 'flags' is a
|
|
* string literal converted to integer flags at compile-time.
|
|
*/
|
|
JsonLikeRegexContext lrcxt = {0};
|
|
|
|
jspInitByBuffer(&larg, jsp->base,
|
|
jsp->content.like_regex.expr);
|
|
|
|
return executePredicate(cxt, jsp, &larg, NULL, jb, false,
|
|
executeLikeRegex, &lrcxt);
|
|
}
|
|
|
|
case jpiExists:
|
|
jspGetArg(jsp, &larg);
|
|
|
|
if (jspStrictAbsenseOfErrors(cxt))
|
|
{
|
|
/*
|
|
* In strict mode we must get a complete list of values to
|
|
* check that there are no errors at all.
|
|
*/
|
|
JsonValueList vals = {0};
|
|
JsonPathExecResult res =
|
|
executeItemOptUnwrapResultNoThrow(cxt, &larg, jb,
|
|
false, &vals);
|
|
|
|
if (jperIsError(res))
|
|
return jpbUnknown;
|
|
|
|
return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
|
|
}
|
|
else
|
|
{
|
|
JsonPathExecResult res =
|
|
executeItemOptUnwrapResultNoThrow(cxt, &larg, jb,
|
|
false, NULL);
|
|
|
|
if (jperIsError(res))
|
|
return jpbUnknown;
|
|
|
|
return res == jperOk ? jpbTrue : jpbFalse;
|
|
}
|
|
|
|
default:
|
|
elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
|
|
return jpbUnknown;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Execute nested (filters etc.) boolean expression pushing current SQL/JSON
|
|
* item onto the stack.
|
|
*/
|
|
static JsonPathBool
|
|
executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb)
|
|
{
|
|
JsonbValue *prev;
|
|
JsonPathBool res;
|
|
|
|
prev = cxt->current;
|
|
cxt->current = jb;
|
|
res = executeBoolItem(cxt, jsp, jb, false);
|
|
cxt->current = prev;
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Implementation of several jsonpath nodes:
|
|
* - jpiAny (.** accessor),
|
|
* - jpiAnyKey (.* accessor),
|
|
* - jpiAnyArray ([*] accessor)
|
|
*/
|
|
static JsonPathExecResult
|
|
executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc,
|
|
JsonValueList *found, uint32 level, uint32 first, uint32 last,
|
|
bool ignoreStructuralErrors, bool unwrapNext)
|
|
{
|
|
JsonPathExecResult res = jperNotFound;
|
|
JsonbIterator *it;
|
|
int32 r;
|
|
JsonbValue v;
|
|
|
|
check_stack_depth();
|
|
|
|
if (level > last)
|
|
return res;
|
|
|
|
it = JsonbIteratorInit(jbc);
|
|
|
|
/*
|
|
* Recursively iterate over jsonb objects/arrays
|
|
*/
|
|
while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
|
|
{
|
|
if (r == WJB_KEY)
|
|
{
|
|
r = JsonbIteratorNext(&it, &v, true);
|
|
Assert(r == WJB_VALUE);
|
|
}
|
|
|
|
if (r == WJB_VALUE || r == WJB_ELEM)
|
|
{
|
|
|
|
if (level >= first ||
|
|
(first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
|
|
v.type != jbvBinary)) /* leaves only requested */
|
|
{
|
|
/* check expression */
|
|
if (jsp)
|
|
{
|
|
if (ignoreStructuralErrors)
|
|
{
|
|
bool savedIgnoreStructuralErrors;
|
|
|
|
savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors;
|
|
cxt->ignoreStructuralErrors = true;
|
|
res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext);
|
|
cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors;
|
|
}
|
|
else
|
|
res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext);
|
|
|
|
if (jperIsError(res))
|
|
break;
|
|
|
|
if (res == jperOk && !found)
|
|
break;
|
|
}
|
|
else if (found)
|
|
JsonValueListAppend(found, copyJsonbValue(&v));
|
|
else
|
|
return jperOk;
|
|
}
|
|
|
|
if (level < last && v.type == jbvBinary)
|
|
{
|
|
res = executeAnyItem
|
|
(cxt, jsp, v.val.binary.data, found,
|
|
level + 1, first, last,
|
|
ignoreStructuralErrors, unwrapNext);
|
|
|
|
if (jperIsError(res))
|
|
break;
|
|
|
|
if (res == jperOk && found == NULL)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Execute unary or binary predicate.
|
|
*
|
|
* Predicates have existence semantics, because their operands are item
|
|
* sequences. Pairs of items from the left and right operand's sequences are
|
|
* checked. TRUE returned only if any pair satisfying the condition is found.
|
|
* In strict mode, even if the desired pair has already been found, all pairs
|
|
* still need to be examined to check the absence of errors. If any error
|
|
* occurs, UNKNOWN (analogous to SQL NULL) is returned.
|
|
*/
|
|
static JsonPathBool
|
|
executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
|
|
JsonPathItem *larg, JsonPathItem *rarg, JsonbValue *jb,
|
|
bool unwrapRightArg, JsonPathPredicateCallback exec,
|
|
void *param)
|
|
{
|
|
JsonPathExecResult res;
|
|
JsonValueListIterator lseqit;
|
|
JsonValueList lseq = {0};
|
|
JsonValueList rseq = {0};
|
|
JsonbValue *lval;
|
|
bool error = false;
|
|
bool found = false;
|
|
|
|
/* Left argument is always auto-unwrapped. */
|
|
res = executeItemOptUnwrapResultNoThrow(cxt, larg, jb, true, &lseq);
|
|
if (jperIsError(res))
|
|
return jpbUnknown;
|
|
|
|
if (rarg)
|
|
{
|
|
/* Right argument is conditionally auto-unwrapped. */
|
|
res = executeItemOptUnwrapResultNoThrow(cxt, rarg, jb,
|
|
unwrapRightArg, &rseq);
|
|
if (jperIsError(res))
|
|
return jpbUnknown;
|
|
}
|
|
|
|
JsonValueListInitIterator(&lseq, &lseqit);
|
|
while ((lval = JsonValueListNext(&lseq, &lseqit)))
|
|
{
|
|
JsonValueListIterator rseqit;
|
|
JsonbValue *rval;
|
|
bool first = true;
|
|
|
|
JsonValueListInitIterator(&rseq, &rseqit);
|
|
if (rarg)
|
|
rval = JsonValueListNext(&rseq, &rseqit);
|
|
else
|
|
rval = NULL;
|
|
|
|
/* Loop over right arg sequence or do single pass otherwise */
|
|
while (rarg ? (rval != NULL) : first)
|
|
{
|
|
JsonPathBool res = exec(pred, lval, rval, param);
|
|
|
|
if (res == jpbUnknown)
|
|
{
|
|
if (jspStrictAbsenseOfErrors(cxt))
|
|
return jpbUnknown;
|
|
|
|
error = true;
|
|
}
|
|
else if (res == jpbTrue)
|
|
{
|
|
if (!jspStrictAbsenseOfErrors(cxt))
|
|
return jpbTrue;
|
|
|
|
found = true;
|
|
}
|
|
|
|
first = false;
|
|
if (rarg)
|
|
rval = JsonValueListNext(&rseq, &rseqit);
|
|
}
|
|
}
|
|
|
|
if (found) /* possible only in strict mode */
|
|
return jpbTrue;
|
|
|
|
if (error) /* possible only in lax mode */
|
|
return jpbUnknown;
|
|
|
|
return jpbFalse;
|
|
}
|
|
|
|
/*
|
|
* Execute binary arithmetic expression on singleton numeric operands.
|
|
* Array operands are automatically unwrapped in lax mode.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, BinaryArithmFunc func,
|
|
JsonValueList *found)
|
|
{
|
|
JsonPathExecResult jper;
|
|
JsonPathItem elem;
|
|
JsonValueList lseq = {0};
|
|
JsonValueList rseq = {0};
|
|
JsonbValue *lval;
|
|
JsonbValue *rval;
|
|
Numeric res;
|
|
|
|
jspGetLeftArg(jsp, &elem);
|
|
|
|
/*
|
|
* XXX: By standard only operands of multiplicative expressions are
|
|
* unwrapped. We extend it to other binary arithmetic expressions too.
|
|
*/
|
|
jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &lseq);
|
|
if (jperIsError(jper))
|
|
return jper;
|
|
|
|
jspGetRightArg(jsp, &elem);
|
|
|
|
jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &rseq);
|
|
if (jperIsError(jper))
|
|
return jper;
|
|
|
|
if (JsonValueListLength(&lseq) != 1 ||
|
|
!(lval = getScalar(JsonValueListHead(&lseq), jbvNumeric)))
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
|
|
errmsg("left operand of jsonpath operator %s is not a single numeric value",
|
|
jspOperationName(jsp->type)))));
|
|
|
|
if (JsonValueListLength(&rseq) != 1 ||
|
|
!(rval = getScalar(JsonValueListHead(&rseq), jbvNumeric)))
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
|
|
errmsg("right operand of jsonpath operator %s is not a single numeric value",
|
|
jspOperationName(jsp->type)))));
|
|
|
|
if (jspThrowErrors(cxt))
|
|
{
|
|
res = func(lval->val.numeric, rval->val.numeric, NULL);
|
|
}
|
|
else
|
|
{
|
|
bool error = false;
|
|
|
|
res = func(lval->val.numeric, rval->val.numeric, &error);
|
|
|
|
if (error)
|
|
return jperError;
|
|
}
|
|
|
|
if (!jspGetNext(jsp, &elem) && !found)
|
|
return jperOk;
|
|
|
|
lval = palloc(sizeof(*lval));
|
|
lval->type = jbvNumeric;
|
|
lval->val.numeric = res;
|
|
|
|
return executeNextItem(cxt, jsp, &elem, lval, found, false);
|
|
}
|
|
|
|
/*
|
|
* Execute unary arithmetic expression for each numeric item in its operand's
|
|
* sequence. Array operand is automatically unwrapped in lax mode.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, PGFunction func, JsonValueList *found)
|
|
{
|
|
JsonPathExecResult jper;
|
|
JsonPathExecResult jper2;
|
|
JsonPathItem elem;
|
|
JsonValueList seq = {0};
|
|
JsonValueListIterator it;
|
|
JsonbValue *val;
|
|
bool hasNext;
|
|
|
|
jspGetArg(jsp, &elem);
|
|
jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &seq);
|
|
|
|
if (jperIsError(jper))
|
|
return jper;
|
|
|
|
jper = jperNotFound;
|
|
|
|
hasNext = jspGetNext(jsp, &elem);
|
|
|
|
JsonValueListInitIterator(&seq, &it);
|
|
while ((val = JsonValueListNext(&seq, &it)))
|
|
{
|
|
if ((val = getScalar(val, jbvNumeric)))
|
|
{
|
|
if (!found && !hasNext)
|
|
return jperOk;
|
|
}
|
|
else
|
|
{
|
|
if (!found && !hasNext)
|
|
continue; /* skip non-numerics processing */
|
|
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_JSON_NUMBER_NOT_FOUND),
|
|
errmsg("operand of unary jsonpath operator %s is not a numeric value",
|
|
jspOperationName(jsp->type)))));
|
|
}
|
|
|
|
if (func)
|
|
val->val.numeric =
|
|
DatumGetNumeric(DirectFunctionCall1(func,
|
|
NumericGetDatum(val->val.numeric)));
|
|
|
|
jper2 = executeNextItem(cxt, jsp, &elem, val, found, false);
|
|
|
|
if (jperIsError(jper2))
|
|
return jper2;
|
|
|
|
if (jper2 == jperOk)
|
|
{
|
|
if (!found)
|
|
return jperOk;
|
|
jper = jperOk;
|
|
}
|
|
}
|
|
|
|
return jper;
|
|
}
|
|
|
|
/*
|
|
* STARTS_WITH predicate callback.
|
|
*
|
|
* Check if the 'whole' string starts from 'initial' string.
|
|
*/
|
|
static JsonPathBool
|
|
executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial,
|
|
void *param)
|
|
{
|
|
if (!(whole = getScalar(whole, jbvString)))
|
|
return jpbUnknown; /* error */
|
|
|
|
if (!(initial = getScalar(initial, jbvString)))
|
|
return jpbUnknown; /* error */
|
|
|
|
if (whole->val.string.len >= initial->val.string.len &&
|
|
!memcmp(whole->val.string.val,
|
|
initial->val.string.val,
|
|
initial->val.string.len))
|
|
return jpbTrue;
|
|
|
|
return jpbFalse;
|
|
}
|
|
|
|
/*
|
|
* LIKE_REGEX predicate callback.
|
|
*
|
|
* Check if the string matches regex pattern.
|
|
*/
|
|
static JsonPathBool
|
|
executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg,
|
|
void *param)
|
|
{
|
|
JsonLikeRegexContext *cxt = param;
|
|
|
|
if (!(str = getScalar(str, jbvString)))
|
|
return jpbUnknown;
|
|
|
|
/* Cache regex text and converted flags. */
|
|
if (!cxt->regex)
|
|
{
|
|
uint32 flags = jsp->content.like_regex.flags;
|
|
|
|
cxt->regex =
|
|
cstring_to_text_with_len(jsp->content.like_regex.pattern,
|
|
jsp->content.like_regex.patternlen);
|
|
|
|
/* Convert regex flags. */
|
|
cxt->cflags = REG_ADVANCED;
|
|
|
|
if (flags & JSP_REGEX_ICASE)
|
|
cxt->cflags |= REG_ICASE;
|
|
if (flags & JSP_REGEX_MLINE)
|
|
cxt->cflags |= REG_NEWLINE;
|
|
if (flags & JSP_REGEX_SLINE)
|
|
cxt->cflags &= ~REG_NEWLINE;
|
|
if (flags & JSP_REGEX_WSPACE)
|
|
cxt->cflags |= REG_EXPANDED;
|
|
|
|
/*
|
|
* 'q' flag can work together only with 'i'. When other is specified,
|
|
* then 'q' has no effect.
|
|
*/
|
|
if ((flags & JSP_REGEX_QUOTE) &&
|
|
!(flags & (JSP_REGEX_MLINE | JSP_REGEX_SLINE | JSP_REGEX_WSPACE)))
|
|
{
|
|
cxt->cflags &= ~REG_ADVANCED;
|
|
cxt->cflags |= REG_QUOTE;
|
|
}
|
|
}
|
|
|
|
if (RE_compile_and_execute(cxt->regex, str->val.string.val,
|
|
str->val.string.len,
|
|
cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL))
|
|
return jpbTrue;
|
|
|
|
return jpbFalse;
|
|
}
|
|
|
|
/*
|
|
* Execute numeric item methods (.abs(), .floor(), .ceil()) using the specified
|
|
* user function 'func'.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, bool unwrap, PGFunction func,
|
|
JsonValueList *found)
|
|
{
|
|
JsonPathItem next;
|
|
Datum datum;
|
|
|
|
if (unwrap && JsonbType(jb) == jbvArray)
|
|
return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
|
|
|
|
if (!(jb = getScalar(jb, jbvNumeric)))
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
|
|
errmsg("jsonpath item method .%s() can only be applied to a numeric value",
|
|
jspOperationName(jsp->type)))));
|
|
|
|
datum = DirectFunctionCall1(func, NumericGetDatum(jb->val.numeric));
|
|
|
|
if (!jspGetNext(jsp, &next) && !found)
|
|
return jperOk;
|
|
|
|
jb = palloc(sizeof(*jb));
|
|
jb->type = jbvNumeric;
|
|
jb->val.numeric = DatumGetNumeric(datum);
|
|
|
|
return executeNextItem(cxt, jsp, &next, jb, found, false);
|
|
}
|
|
|
|
/*
|
|
* Implementation of .keyvalue() method.
|
|
*
|
|
* .keyvalue() method returns a sequence of object's key-value pairs in the
|
|
* following format: '{ "key": key, "value": value, "id": id }'.
|
|
*
|
|
* "id" field is an object identifier which is constructed from the two parts:
|
|
* base object id and its binary offset in base object's jsonb:
|
|
* id = 10000000000 * base_object_id + obj_offset_in_base_object
|
|
*
|
|
* 10000000000 (10^10) -- is a first round decimal number greater than 2^32
|
|
* (maximal offset in jsonb). Decimal multiplier is used here to improve the
|
|
* readability of identifiers.
|
|
*
|
|
* Base object is usually a root object of the path: context item '$' or path
|
|
* variable '$var', literals can't produce objects for now. But if the path
|
|
* contains generated objects (.keyvalue() itself, for example), then they
|
|
* become base object for the subsequent .keyvalue().
|
|
*
|
|
* Id of '$' is 0. Id of '$var' is its ordinal (positive) number in the list
|
|
* of variables (see getJsonPathVariable()). Ids for generated objects
|
|
* are assigned using global counter JsonPathExecContext.lastGeneratedObjectId.
|
|
*/
|
|
static JsonPathExecResult
|
|
executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonbValue *jb, JsonValueList *found)
|
|
{
|
|
JsonPathExecResult res = jperNotFound;
|
|
JsonPathItem next;
|
|
JsonbContainer *jbc;
|
|
JsonbValue key;
|
|
JsonbValue val;
|
|
JsonbValue idval;
|
|
JsonbValue keystr;
|
|
JsonbValue valstr;
|
|
JsonbValue idstr;
|
|
JsonbIterator *it;
|
|
JsonbIteratorToken tok;
|
|
int64 id;
|
|
bool hasNext;
|
|
|
|
if (JsonbType(jb) != jbvObject || jb->type != jbvBinary)
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_JSON_OBJECT_NOT_FOUND),
|
|
errmsg("jsonpath item method .%s() can only be applied to an object",
|
|
jspOperationName(jsp->type)))));
|
|
|
|
jbc = jb->val.binary.data;
|
|
|
|
if (!JsonContainerSize(jbc))
|
|
return jperNotFound; /* no key-value pairs */
|
|
|
|
hasNext = jspGetNext(jsp, &next);
|
|
|
|
keystr.type = jbvString;
|
|
keystr.val.string.val = "key";
|
|
keystr.val.string.len = 3;
|
|
|
|
valstr.type = jbvString;
|
|
valstr.val.string.val = "value";
|
|
valstr.val.string.len = 5;
|
|
|
|
idstr.type = jbvString;
|
|
idstr.val.string.val = "id";
|
|
idstr.val.string.len = 2;
|
|
|
|
/* construct object id from its base object and offset inside that */
|
|
id = jb->type != jbvBinary ? 0 :
|
|
(int64) ((char *) jbc - (char *) cxt->baseObject.jbc);
|
|
id += (int64) cxt->baseObject.id * INT64CONST(10000000000);
|
|
|
|
idval.type = jbvNumeric;
|
|
idval.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
|
|
Int64GetDatum(id)));
|
|
|
|
it = JsonbIteratorInit(jbc);
|
|
|
|
while ((tok = JsonbIteratorNext(&it, &key, true)) != WJB_DONE)
|
|
{
|
|
JsonBaseObjectInfo baseObject;
|
|
JsonbValue obj;
|
|
JsonbParseState *ps;
|
|
JsonbValue *keyval;
|
|
Jsonb *jsonb;
|
|
|
|
if (tok != WJB_KEY)
|
|
continue;
|
|
|
|
res = jperOk;
|
|
|
|
if (!hasNext && !found)
|
|
break;
|
|
|
|
tok = JsonbIteratorNext(&it, &val, true);
|
|
Assert(tok == WJB_VALUE);
|
|
|
|
ps = NULL;
|
|
pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
|
|
|
|
pushJsonbValue(&ps, WJB_KEY, &keystr);
|
|
pushJsonbValue(&ps, WJB_VALUE, &key);
|
|
|
|
pushJsonbValue(&ps, WJB_KEY, &valstr);
|
|
pushJsonbValue(&ps, WJB_VALUE, &val);
|
|
|
|
pushJsonbValue(&ps, WJB_KEY, &idstr);
|
|
pushJsonbValue(&ps, WJB_VALUE, &idval);
|
|
|
|
keyval = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
|
|
|
|
jsonb = JsonbValueToJsonb(keyval);
|
|
|
|
JsonbInitBinary(&obj, jsonb);
|
|
|
|
baseObject = setBaseObject(cxt, &obj, cxt->lastGeneratedObjectId++);
|
|
|
|
res = executeNextItem(cxt, jsp, &next, &obj, found, true);
|
|
|
|
cxt->baseObject = baseObject;
|
|
|
|
if (jperIsError(res))
|
|
return res;
|
|
|
|
if (res == jperOk && !found)
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Convert boolean execution status 'res' to a boolean JSON item and execute
|
|
* next jsonpath.
|
|
*/
|
|
static JsonPathExecResult
|
|
appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
|
|
JsonValueList *found, JsonPathBool res)
|
|
{
|
|
JsonPathItem next;
|
|
JsonbValue jbv;
|
|
|
|
if (!jspGetNext(jsp, &next) && !found)
|
|
return jperOk; /* found singleton boolean value */
|
|
|
|
if (res == jpbUnknown)
|
|
{
|
|
jbv.type = jbvNull;
|
|
}
|
|
else
|
|
{
|
|
jbv.type = jbvBool;
|
|
jbv.val.boolean = res == jpbTrue;
|
|
}
|
|
|
|
return executeNextItem(cxt, jsp, &next, &jbv, found, true);
|
|
}
|
|
|
|
/*
|
|
* Convert jsonpath's scalar or variable node to actual jsonb value.
|
|
*
|
|
* If node is a variable then its id returned, otherwise 0 returned.
|
|
*/
|
|
static void
|
|
getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
|
|
JsonbValue *value)
|
|
{
|
|
switch (item->type)
|
|
{
|
|
case jpiNull:
|
|
value->type = jbvNull;
|
|
break;
|
|
case jpiBool:
|
|
value->type = jbvBool;
|
|
value->val.boolean = jspGetBool(item);
|
|
break;
|
|
case jpiNumeric:
|
|
value->type = jbvNumeric;
|
|
value->val.numeric = jspGetNumeric(item);
|
|
break;
|
|
case jpiString:
|
|
value->type = jbvString;
|
|
value->val.string.val = jspGetString(item,
|
|
&value->val.string.len);
|
|
break;
|
|
case jpiVariable:
|
|
getJsonPathVariable(cxt, item, cxt->vars, value);
|
|
return;
|
|
default:
|
|
elog(ERROR, "unexpected jsonpath item type");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the value of variable passed to jsonpath executor
|
|
*/
|
|
static void
|
|
getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable,
|
|
Jsonb *vars, JsonbValue *value)
|
|
{
|
|
char *varName;
|
|
int varNameLength;
|
|
JsonbValue tmp;
|
|
JsonbValue *v;
|
|
|
|
if (!vars)
|
|
{
|
|
value->type = jbvNull;
|
|
return;
|
|
}
|
|
|
|
Assert(variable->type == jpiVariable);
|
|
varName = jspGetString(variable, &varNameLength);
|
|
tmp.type = jbvString;
|
|
tmp.val.string.val = varName;
|
|
tmp.val.string.len = varNameLength;
|
|
|
|
v = findJsonbValueFromContainer(&vars->root, JB_FOBJECT, &tmp);
|
|
|
|
if (v)
|
|
{
|
|
*value = *v;
|
|
pfree(v);
|
|
}
|
|
else
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("could not find jsonpath variable \"%s\"",
|
|
pnstrdup(varName, varNameLength))));
|
|
}
|
|
|
|
JsonbInitBinary(&tmp, vars);
|
|
setBaseObject(cxt, &tmp, 1);
|
|
}
|
|
|
|
/**************** Support functions for JsonPath execution *****************/
|
|
|
|
/*
|
|
* Returns the size of an array item, or -1 if item is not an array.
|
|
*/
|
|
static int
|
|
JsonbArraySize(JsonbValue *jb)
|
|
{
|
|
Assert(jb->type != jbvArray);
|
|
|
|
if (jb->type == jbvBinary)
|
|
{
|
|
JsonbContainer *jbc = jb->val.binary.data;
|
|
|
|
if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
|
|
return JsonContainerSize(jbc);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Comparison predicate callback. */
|
|
static JsonPathBool
|
|
executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
|
|
{
|
|
return compareItems(cmp->type, lv, rv);
|
|
}
|
|
|
|
/*
|
|
* Compare two SQL/JSON items using comparison operation 'op'.
|
|
*/
|
|
static JsonPathBool
|
|
compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
|
|
{
|
|
int cmp;
|
|
bool res;
|
|
|
|
if (jb1->type != jb2->type)
|
|
{
|
|
if (jb1->type == jbvNull || jb2->type == jbvNull)
|
|
|
|
/*
|
|
* Equality and order comparison of nulls to non-nulls returns
|
|
* always false, but inequality comparison returns true.
|
|
*/
|
|
return op == jpiNotEqual ? jpbTrue : jpbFalse;
|
|
|
|
/* Non-null items of different types are not comparable. */
|
|
return jpbUnknown;
|
|
}
|
|
|
|
switch (jb1->type)
|
|
{
|
|
case jbvNull:
|
|
cmp = 0;
|
|
break;
|
|
case jbvBool:
|
|
cmp = jb1->val.boolean == jb2->val.boolean ? 0 :
|
|
jb1->val.boolean ? 1 : -1;
|
|
break;
|
|
case jbvNumeric:
|
|
cmp = compareNumeric(jb1->val.numeric, jb2->val.numeric);
|
|
break;
|
|
case jbvString:
|
|
if (op == jpiEqual)
|
|
return jb1->val.string.len != jb2->val.string.len ||
|
|
memcmp(jb1->val.string.val,
|
|
jb2->val.string.val,
|
|
jb1->val.string.len) ? jpbFalse : jpbTrue;
|
|
|
|
cmp = varstr_cmp(jb1->val.string.val, jb1->val.string.len,
|
|
jb2->val.string.val, jb2->val.string.len,
|
|
DEFAULT_COLLATION_OID);
|
|
break;
|
|
|
|
case jbvBinary:
|
|
case jbvArray:
|
|
case jbvObject:
|
|
return jpbUnknown; /* non-scalars are not comparable */
|
|
|
|
default:
|
|
elog(ERROR, "invalid jsonb value type %d", jb1->type);
|
|
}
|
|
|
|
switch (op)
|
|
{
|
|
case jpiEqual:
|
|
res = (cmp == 0);
|
|
break;
|
|
case jpiNotEqual:
|
|
res = (cmp != 0);
|
|
break;
|
|
case jpiLess:
|
|
res = (cmp < 0);
|
|
break;
|
|
case jpiGreater:
|
|
res = (cmp > 0);
|
|
break;
|
|
case jpiLessOrEqual:
|
|
res = (cmp <= 0);
|
|
break;
|
|
case jpiGreaterOrEqual:
|
|
res = (cmp >= 0);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized jsonpath operation: %d", op);
|
|
return jpbUnknown;
|
|
}
|
|
|
|
return res ? jpbTrue : jpbFalse;
|
|
}
|
|
|
|
/* Compare two numerics */
|
|
static int
|
|
compareNumeric(Numeric a, Numeric b)
|
|
{
|
|
return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
|
|
NumericGetDatum(a),
|
|
NumericGetDatum(b)));
|
|
}
|
|
|
|
static JsonbValue *
|
|
copyJsonbValue(JsonbValue *src)
|
|
{
|
|
JsonbValue *dst = palloc(sizeof(*dst));
|
|
|
|
*dst = *src;
|
|
|
|
return dst;
|
|
}
|
|
|
|
/*
|
|
* Execute array subscript expression and convert resulting numeric item to
|
|
* the integer type with truncation.
|
|
*/
|
|
static JsonPathExecResult
|
|
getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
|
|
int32 *index)
|
|
{
|
|
JsonbValue *jbv;
|
|
JsonValueList found = {0};
|
|
JsonPathExecResult res = executeItem(cxt, jsp, jb, &found);
|
|
Datum numeric_index;
|
|
bool have_error = false;
|
|
|
|
if (jperIsError(res))
|
|
return res;
|
|
|
|
if (JsonValueListLength(&found) != 1 ||
|
|
!(jbv = getScalar(JsonValueListHead(&found), jbvNumeric)))
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
|
|
errmsg("jsonpath array subscript is not a single numeric value"))));
|
|
|
|
numeric_index = DirectFunctionCall2(numeric_trunc,
|
|
NumericGetDatum(jbv->val.numeric),
|
|
Int32GetDatum(0));
|
|
|
|
*index = numeric_int4_opt_error(DatumGetNumeric(numeric_index),
|
|
&have_error);
|
|
|
|
if (have_error)
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
|
|
errmsg("jsonpath array subscript is out of integer range"))));
|
|
|
|
return jperOk;
|
|
}
|
|
|
|
/* Save base object and its id needed for the execution of .keyvalue(). */
|
|
static JsonBaseObjectInfo
|
|
setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id)
|
|
{
|
|
JsonBaseObjectInfo baseObject = cxt->baseObject;
|
|
|
|
cxt->baseObject.jbc = jbv->type != jbvBinary ? NULL :
|
|
(JsonbContainer *) jbv->val.binary.data;
|
|
cxt->baseObject.id = id;
|
|
|
|
return baseObject;
|
|
}
|
|
|
|
static void
|
|
JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
|
|
{
|
|
if (jvl->singleton)
|
|
{
|
|
jvl->list = list_make2(jvl->singleton, jbv);
|
|
jvl->singleton = NULL;
|
|
}
|
|
else if (!jvl->list)
|
|
jvl->singleton = jbv;
|
|
else
|
|
jvl->list = lappend(jvl->list, jbv);
|
|
}
|
|
|
|
static int
|
|
JsonValueListLength(const JsonValueList *jvl)
|
|
{
|
|
return jvl->singleton ? 1 : list_length(jvl->list);
|
|
}
|
|
|
|
static bool
|
|
JsonValueListIsEmpty(JsonValueList *jvl)
|
|
{
|
|
return !jvl->singleton && list_length(jvl->list) <= 0;
|
|
}
|
|
|
|
static JsonbValue *
|
|
JsonValueListHead(JsonValueList *jvl)
|
|
{
|
|
return jvl->singleton ? jvl->singleton : linitial(jvl->list);
|
|
}
|
|
|
|
static List *
|
|
JsonValueListGetList(JsonValueList *jvl)
|
|
{
|
|
if (jvl->singleton)
|
|
return list_make1(jvl->singleton);
|
|
|
|
return jvl->list;
|
|
}
|
|
|
|
static void
|
|
JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it)
|
|
{
|
|
if (jvl->singleton)
|
|
{
|
|
it->value = jvl->singleton;
|
|
it->list = NIL;
|
|
it->next = NULL;
|
|
}
|
|
else if (jvl->list != NIL)
|
|
{
|
|
it->value = (JsonbValue *) linitial(jvl->list);
|
|
it->list = jvl->list;
|
|
it->next = list_second_cell(jvl->list);
|
|
}
|
|
else
|
|
{
|
|
it->value = NULL;
|
|
it->list = NIL;
|
|
it->next = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the next item from the sequence advancing iterator.
|
|
*/
|
|
static JsonbValue *
|
|
JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it)
|
|
{
|
|
JsonbValue *result = it->value;
|
|
|
|
if (it->next)
|
|
{
|
|
it->value = lfirst(it->next);
|
|
it->next = lnext(it->list, it->next);
|
|
}
|
|
else
|
|
{
|
|
it->value = NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Initialize a binary JsonbValue with the given jsonb container.
|
|
*/
|
|
static JsonbValue *
|
|
JsonbInitBinary(JsonbValue *jbv, Jsonb *jb)
|
|
{
|
|
jbv->type = jbvBinary;
|
|
jbv->val.binary.data = &jb->root;
|
|
jbv->val.binary.len = VARSIZE_ANY_EXHDR(jb);
|
|
|
|
return jbv;
|
|
}
|
|
|
|
/*
|
|
* Returns jbv* type of of JsonbValue. Note, it never returns jbvBinary as is.
|
|
*/
|
|
static int
|
|
JsonbType(JsonbValue *jb)
|
|
{
|
|
int type = jb->type;
|
|
|
|
if (jb->type == jbvBinary)
|
|
{
|
|
JsonbContainer *jbc = (void *) jb->val.binary.data;
|
|
|
|
/* Scalars should be always extracted during jsonpath execution. */
|
|
Assert(!JsonContainerIsScalar(jbc));
|
|
|
|
if (JsonContainerIsObject(jbc))
|
|
type = jbvObject;
|
|
else if (JsonContainerIsArray(jbc))
|
|
type = jbvArray;
|
|
else
|
|
elog(ERROR, "invalid jsonb container type: 0x%08x", jbc->header);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
/* Get scalar of given type or NULL on type mismatch */
|
|
static JsonbValue *
|
|
getScalar(JsonbValue *scalar, enum jbvType type)
|
|
{
|
|
/* Scalars should be always extracted during jsonpath execution. */
|
|
Assert(scalar->type != jbvBinary ||
|
|
!JsonContainerIsScalar(scalar->val.binary.data));
|
|
|
|
return scalar->type == type ? scalar : NULL;
|
|
}
|
|
|
|
/* Construct a JSON array from the item list */
|
|
static JsonbValue *
|
|
wrapItemsInArray(const JsonValueList *items)
|
|
{
|
|
JsonbParseState *ps = NULL;
|
|
JsonValueListIterator it;
|
|
JsonbValue *jbv;
|
|
|
|
pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
|
|
|
|
JsonValueListInitIterator(items, &it);
|
|
while ((jbv = JsonValueListNext(items, &it)))
|
|
pushJsonbValue(&ps, WJB_ELEM, jbv);
|
|
|
|
return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
|
|
}
|