postgresql/src/backend/utils/adt/jsonpath_exec.c

4293 lines
112 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 avoid 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-2024, 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 "executor/execExpr.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/miscnodes.h"
#include "nodes/nodeFuncs.h"
#include "regex/regex.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/float.h"
#include "utils/formatting.h"
#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
/*
* Represents "base object" and it's "id" for .keyvalue() evaluation.
*/
typedef struct JsonBaseObjectInfo
{
JsonbContainer *jbc;
int id;
} JsonBaseObjectInfo;
/* Callbacks for executeJsonPath() */
typedef JsonbValue *(*JsonPathGetVarCallback) (void *vars, char *varName, int varNameLen,
JsonbValue *baseObject, int *baseObjectId);
typedef int (*JsonPathCountVarsCallback) (void *vars);
/*
* Context of jsonpath execution.
*/
typedef struct JsonPathExecContext
{
void *vars; /* variables to substitute into jsonpath */
JsonPathGetVarCallback getVar; /* callback to extract a given variable
* from 'vars' */
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 */
bool useTz;
} 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;
/* Structures for JSON_TABLE execution */
/*
* Struct holding the result of jsonpath evaluation, to be used as source row
* for JsonTableGetValue() which in turn computes the values of individual
* JSON_TABLE columns.
*/
typedef struct JsonTablePlanRowSource
{
Datum value;
bool isnull;
} JsonTablePlanRowSource;
/*
* State of evaluation of row pattern derived by applying jsonpath given in
* a JsonTablePlan to an input document given in the parent TableFunc.
*/
typedef struct JsonTablePlanState
{
/* Original plan */
JsonTablePlan *plan;
/* The following fields are only valid for JsonTablePathScan plans */
/* jsonpath to evaluate against the input doc to get the row pattern */
JsonPath *path;
/*
* Memory context to use when evaluating the row pattern from the jsonpath
*/
MemoryContext mcxt;
/* PASSING arguments passed to jsonpath executor */
List *args;
/* List and iterator of jsonpath result values */
JsonValueList found;
JsonValueListIterator iter;
/* Currently selected row for JsonTableGetValue() to use */
JsonTablePlanRowSource current;
/* Counter for ORDINAL columns */
int ordinal;
} JsonTablePlanState;
/* Random number to identify JsonTableExecContext for sanity checking */
#define JSON_TABLE_EXEC_CONTEXT_MAGIC 418352867
typedef struct JsonTableExecContext
{
int magic;
/* State of the plan providing a row evaluated from "root" jsonpath */
JsonTablePlanState *rootplanstate;
} JsonTableExecContext;
/* strict/lax flags is decomposed into four [un]wrap/error flags */
#define jspStrictAbsenceOfErrors(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, void *vars,
JsonPathGetVarCallback getVar,
JsonPathCountVarsCallback countVars,
Jsonb *json, bool throwErrors,
JsonValueList *result, bool useTz);
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 executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
JsonbValue *jb, 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 JsonbValue *GetJsonPathVar(void *cxt, char *varName, int varNameLen,
JsonbValue *baseObject, int *baseObjectId);
static int CountJsonPathVars(void *cxt);
static void JsonItemFromDatum(Datum val, Oid typid, int32 typmod,
JsonbValue *res);
static void JsonbValueInitNumericDatum(JsonbValue *jbv, Datum num);
static void getJsonPathVariable(JsonPathExecContext *cxt,
JsonPathItem *variable, JsonbValue *value);
static int countVariablesFromJsonb(void *varsJsonb);
static JsonbValue *getJsonPathVariableFromJsonb(void *varsJsonb, char *varName,
int varNameLen,
JsonbValue *baseObject,
int *baseObjectId);
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,
bool useTz);
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 JsonValueListClear(JsonValueList *jvl);
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);
static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
bool useTz, bool *cast_error);
static void checkTimezoneIsUsedForCast(bool useTz, const char *type1,
const char *type2);
static void JsonTableInitOpaque(TableFuncScanState *state, int natts);
static JsonTablePlanState *JsonTableInitPlan(JsonTableExecContext *cxt,
JsonTablePlan *plan,
List *args,
MemoryContext mcxt);
static void JsonTableSetDocument(TableFuncScanState *state, Datum value);
static void JsonTableResetRowPattern(JsonTablePlanState *plan, Datum item);
static bool JsonTableFetchRow(TableFuncScanState *state);
static Datum JsonTableGetValue(TableFuncScanState *state, int colnum,
Oid typid, int32 typmod, bool *isnull);
static void JsonTableDestroyOpaque(TableFuncScanState *state);
static bool JsonTablePlanNextRow(JsonTablePlanState *planstate);
const TableFuncRoutine JsonbTableRoutine =
{
.InitOpaque = JsonTableInitOpaque,
.SetDocument = JsonTableSetDocument,
.SetNamespace = NULL,
.SetRowFilter = NULL,
.SetColumnFilter = NULL,
.FetchRow = JsonTableFetchRow,
.GetValue = JsonTableGetValue,
.DestroyOpaque = JsonTableDestroyOpaque
};
/****************** 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 few 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.
*/
static Datum
jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
{
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, getJsonPathVariableFromJsonb,
countVariablesFromJsonb,
jb, !silent, NULL, tz);
PG_FREE_IF_COPY(jb, 0);
PG_FREE_IF_COPY(jp, 1);
if (jperIsError(res))
PG_RETURN_NULL();
PG_RETURN_BOOL(res == jperOk);
}
Datum
jsonb_path_exists(PG_FUNCTION_ARGS)
{
return jsonb_path_exists_internal(fcinfo, false);
}
Datum
jsonb_path_exists_tz(PG_FUNCTION_ARGS)
{
return jsonb_path_exists_internal(fcinfo, true);
}
/*
* 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_internal(fcinfo, false);
}
/*
* jsonb_path_match
* Returns jsonpath predicate result item for the specified jsonb value.
* See jsonb_path_exists() comment for details regarding error handling.
*/
static Datum
jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
{
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, getJsonPathVariableFromJsonb,
countVariablesFromJsonb,
jb, !silent, &found, tz);
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_SQL_JSON_ITEM_REQUIRED),
errmsg("single boolean result is expected")));
PG_RETURN_NULL();
}
Datum
jsonb_path_match(PG_FUNCTION_ARGS)
{
return jsonb_path_match_internal(fcinfo, false);
}
Datum
jsonb_path_match_tz(PG_FUNCTION_ARGS)
{
return jsonb_path_match_internal(fcinfo, true);
}
/*
* 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_internal(fcinfo, false);
}
/*
* jsonb_path_query
* Executes jsonpath for given jsonb document and returns result as
* rowset.
*/
static Datum
jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
{
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, getJsonPathVariableFromJsonb,
countVariablesFromJsonb,
jb, !silent, &found, tz);
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)));
}
Datum
jsonb_path_query(PG_FUNCTION_ARGS)
{
return jsonb_path_query_internal(fcinfo, false);
}
Datum
jsonb_path_query_tz(PG_FUNCTION_ARGS)
{
return jsonb_path_query_internal(fcinfo, true);
}
/*
* jsonb_path_query_array
* Executes jsonpath for given jsonb document and returns result as
* jsonb array.
*/
static Datum
jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
{
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, getJsonPathVariableFromJsonb,
countVariablesFromJsonb,
jb, !silent, &found, tz);
PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
}
Datum
jsonb_path_query_array(PG_FUNCTION_ARGS)
{
return jsonb_path_query_array_internal(fcinfo, false);
}
Datum
jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
{
return jsonb_path_query_array_internal(fcinfo, true);
}
/*
* jsonb_path_query_first
* Executes jsonpath for given jsonb document and returns first result
* item. If there are no items, NULL returned.
*/
static Datum
jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
{
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, getJsonPathVariableFromJsonb,
countVariablesFromJsonb,
jb, !silent, &found, tz);
if (JsonValueListLength(&found) >= 1)
PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
else
PG_RETURN_NULL();
}
Datum
jsonb_path_query_first(PG_FUNCTION_ARGS)
{
return jsonb_path_query_first_internal(fcinfo, false);
}
Datum
jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
{
return jsonb_path_query_first_internal(fcinfo, true);
}
/********************Execute functions for JsonPath**************************/
/*
* Interface to jsonpath executor
*
* 'path' - jsonpath to be executed
* 'vars' - variables to be substituted to jsonpath
* 'getVar' - callback used by getJsonPathVariable() to extract variables from
* 'vars'
* 'countVars' - callback to count the number of jsonpath variables in 'vars'
* '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, void *vars, JsonPathGetVarCallback getVar,
JsonPathCountVarsCallback countVars,
Jsonb *json, bool throwErrors, JsonValueList *result,
bool useTz)
{
JsonPathExecContext cxt;
JsonPathExecResult res;
JsonPathItem jsp;
JsonbValue jbv;
jspInit(&jsp, path);
if (!JsonbExtractScalar(&json->root, &jbv))
JsonbInitBinary(&jbv, json);
cxt.vars = vars;
cxt.getVar = getVar;
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;
/* 1 + number of base objects in vars */
cxt.lastGeneratedObjectId = 1 + countVars(vars);
cxt.innermostArraySize = -1;
cxt.throwErrors = throwErrors;
cxt.useTz = useTz;
if (jspStrictAbsenceOfErrors(&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)
{
case jpiNull:
case jpiBool:
case jpiNumeric:
case jpiString:
case jpiVariable:
{
JsonbValue vbuf;
JsonbValue *v;
bool hasNext = jspGetNext(jsp, &elem);
if (!hasNext && !found && jsp->type != jpiVariable)
{
/*
* Skip evaluation, but not for variables. We must
* trigger an error for the missing variable.
*/
res = jperOk;
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;
/* 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 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 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_SQL_JSON_ARRAY_NOT_FOUND),
errmsg("jsonpath wildcard array accessor can only be applied to an array"))));
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_SQL_JSON_OBJECT_NOT_FOUND),
errmsg("jsonpath wildcard member accessor can only be applied to an object"))));
}
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_SQL_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_SQL_JSON_ARRAY_NOT_FOUND),
errmsg("jsonpath array accessor can only be applied to an array"))));
}
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 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_SQL_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_SQL_JSON_MEMBER_NOT_FOUND),
errmsg("jsonpath member accessor can only be applied to an object"))));
}
break;
case jpiCurrent:
res = executeNextItem(cxt, jsp, NULL, cxt->current,
found, true);
break;
case jpiRoot:
jb = cxt->root;
baseObject = setBaseObject(cxt, jb, 0);
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
cxt->baseObject = baseObject;
break;
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 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_SQL_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 = int64_to_numeric(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)));
double val;
ErrorSaveContext escontext = {T_ErrorSaveContext};
val = float8in_internal(tmp,
NULL,
"double precision",
tmp,
(Node *) &escontext);
if (escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type double precision",
tmp, jspOperationName(jsp->type)))));
if (isinf(val) || isnan(val))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
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);
ErrorSaveContext escontext = {T_ErrorSaveContext};
val = float8in_internal(tmp,
NULL,
"double precision",
tmp,
(Node *) &escontext);
if (escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type double precision",
tmp, jspOperationName(jsp->type)))));
if (isinf(val) || isnan(val))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
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_SQL_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 jpiDatetime:
case jpiDate:
case jpiTime:
case jpiTimeTz:
case jpiTimestamp:
case jpiTimestampTz:
if (unwrap && JsonbType(jb) == jbvArray)
return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
return executeDateTimeMethod(cxt, jsp, jb, found);
case jpiKeyValue:
if (unwrap && JsonbType(jb) == jbvArray)
return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
return executeKeyValueMethod(cxt, jsp, jb, found);
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 = int64_to_numeric(last);
res = executeNextItem(cxt, jsp, &elem,
lastjbv, found, hasNext);
}
break;
case jpiBigint:
{
JsonbValue jbv;
Datum datum;
if (unwrap && JsonbType(jb) == jbvArray)
return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
false);
if (jb->type == jbvNumeric)
{
bool have_error;
int64 val;
val = numeric_int8_opt_error(jb->val.numeric, &have_error);
if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type bigint",
DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric))),
jspOperationName(jsp->type)))));
datum = Int64GetDatum(val);
res = jperOk;
}
else if (jb->type == jbvString)
{
/* cast string as bigint */
char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
ErrorSaveContext escontext = {T_ErrorSaveContext};
bool noerr;
noerr = DirectInputFunctionCallSafe(int8in, tmp,
InvalidOid, -1,
(Node *) &escontext,
&datum);
if (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type bigint",
tmp, jspOperationName(jsp->type)))));
res = jperOk;
}
if (res == jperNotFound)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("jsonpath item method .%s() can only be applied to a string or numeric value",
jspOperationName(jsp->type)))));
jb = &jbv;
jb->type = jbvNumeric;
jb->val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
datum));
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
}
break;
case jpiBoolean:
{
JsonbValue jbv;
bool bval;
if (unwrap && JsonbType(jb) == jbvArray)
return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
false);
if (jb->type == jbvBool)
{
bval = jb->val.boolean;
res = jperOk;
}
else if (jb->type == jbvNumeric)
{
int ival;
Datum datum;
bool noerr;
char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric)));
ErrorSaveContext escontext = {T_ErrorSaveContext};
noerr = DirectInputFunctionCallSafe(int4in, tmp,
InvalidOid, -1,
(Node *) &escontext,
&datum);
if (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type boolean",
tmp, jspOperationName(jsp->type)))));
ival = DatumGetInt32(datum);
if (ival == 0)
bval = false;
else
bval = true;
res = jperOk;
}
else if (jb->type == jbvString)
{
/* cast string as boolean */
char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
if (!parse_bool(tmp, &bval))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type boolean",
tmp, jspOperationName(jsp->type)))));
res = jperOk;
}
if (res == jperNotFound)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("jsonpath item method .%s() can only be applied to a bool, string, or numeric value",
jspOperationName(jsp->type)))));
jb = &jbv;
jb->type = jbvBool;
jb->val.boolean = bval;
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
}
break;
case jpiDecimal:
case jpiNumber:
{
JsonbValue jbv;
Numeric num;
char *numstr = NULL;
if (unwrap && JsonbType(jb) == jbvArray)
return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
false);
if (jb->type == jbvNumeric)
{
num = jb->val.numeric;
if (numeric_is_nan(num) || numeric_is_inf(num))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
jspOperationName(jsp->type)))));
if (jsp->type == jpiDecimal)
numstr = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num)));
res = jperOk;
}
else if (jb->type == jbvString)
{
/* cast string as number */
Datum datum;
bool noerr;
ErrorSaveContext escontext = {T_ErrorSaveContext};
numstr = pnstrdup(jb->val.string.val, jb->val.string.len);
noerr = DirectInputFunctionCallSafe(numeric_in, numstr,
InvalidOid, -1,
(Node *) &escontext,
&datum);
if (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type numeric",
numstr, jspOperationName(jsp->type)))));
num = DatumGetNumeric(datum);
if (numeric_is_nan(num) || numeric_is_inf(num))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
jspOperationName(jsp->type)))));
res = jperOk;
}
if (res == jperNotFound)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("jsonpath item method .%s() can only be applied to a string or numeric value",
jspOperationName(jsp->type)))));
/*
* If we have arguments, then they must be the precision and
* optional scale used in .decimal(). Convert them to the
* typmod equivalent and then truncate the numeric value per
* this typmod details.
*/
if (jsp->type == jpiDecimal && jsp->content.args.left)
{
Datum numdatum;
Datum dtypmod;
int32 precision;
int32 scale = 0;
bool have_error;
bool noerr;
ArrayType *arrtypmod;
Datum datums[2];
char pstr[12]; /* sign, 10 digits and '\0' */
char sstr[12]; /* sign, 10 digits and '\0' */
ErrorSaveContext escontext = {T_ErrorSaveContext};
jspGetLeftArg(jsp, &elem);
if (elem.type != jpiNumeric)
elog(ERROR, "invalid jsonpath item type for .decimal() precision");
precision = numeric_int4_opt_error(jspGetNumeric(&elem),
&have_error);
if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("precision of jsonpath item method .%s() is out of range for type integer",
jspOperationName(jsp->type)))));
if (jsp->content.args.right)
{
jspGetRightArg(jsp, &elem);
if (elem.type != jpiNumeric)
elog(ERROR, "invalid jsonpath item type for .decimal() scale");
scale = numeric_int4_opt_error(jspGetNumeric(&elem),
&have_error);
if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("scale of jsonpath item method .%s() is out of range for type integer",
jspOperationName(jsp->type)))));
}
/*
* numerictypmodin() takes the precision and scale in the
* form of CString arrays.
*/
pg_ltoa(precision, pstr);
datums[0] = CStringGetDatum(pstr);
pg_ltoa(scale, sstr);
datums[1] = CStringGetDatum(sstr);
arrtypmod = construct_array_builtin(datums, 2, CSTRINGOID);
dtypmod = DirectFunctionCall1(numerictypmodin,
PointerGetDatum(arrtypmod));
/* Convert numstr to Numeric with typmod */
Assert(numstr != NULL);
noerr = DirectInputFunctionCallSafe(numeric_in, numstr,
InvalidOid, dtypmod,
(Node *) &escontext,
&numdatum);
if (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type numeric",
numstr, jspOperationName(jsp->type)))));
num = DatumGetNumeric(numdatum);
pfree(arrtypmod);
}
jb = &jbv;
jb->type = jbvNumeric;
jb->val.numeric = num;
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
}
break;
case jpiInteger:
{
JsonbValue jbv;
Datum datum;
if (unwrap && JsonbType(jb) == jbvArray)
return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
false);
if (jb->type == jbvNumeric)
{
bool have_error;
int32 val;
val = numeric_int4_opt_error(jb->val.numeric, &have_error);
if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type integer",
DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric))),
jspOperationName(jsp->type)))));
datum = Int32GetDatum(val);
res = jperOk;
}
else if (jb->type == jbvString)
{
/* cast string as integer */
char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
ErrorSaveContext escontext = {T_ErrorSaveContext};
bool noerr;
noerr = DirectInputFunctionCallSafe(int4in, tmp,
InvalidOid, -1,
(Node *) &escontext,
&datum);
if (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type integer",
tmp, jspOperationName(jsp->type)))));
res = jperOk;
}
if (res == jperNotFound)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("jsonpath item method .%s() can only be applied to a string or numeric value",
jspOperationName(jsp->type)))));
jb = &jbv;
jb->type = jbvNumeric;
jb->val.numeric = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
datum));
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
}
break;
case jpiStringFunc:
{
JsonbValue jbv;
char *tmp = NULL;
switch (JsonbType(jb))
{
case jbvString:
/*
* Value is not necessarily null-terminated, so we do
* pnstrdup() here.
*/
tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
break;
case jbvNumeric:
tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric)));
break;
case jbvBool:
tmp = (jb->val.boolean) ? "true" : "false";
break;
case jbvDatetime:
{
switch (jb->val.datetime.typid)
{
case DATEOID:
tmp = DatumGetCString(DirectFunctionCall1(date_out,
jb->val.datetime.value));
break;
case TIMEOID:
tmp = DatumGetCString(DirectFunctionCall1(time_out,
jb->val.datetime.value));
break;
case TIMETZOID:
tmp = DatumGetCString(DirectFunctionCall1(timetz_out,
jb->val.datetime.value));
break;
case TIMESTAMPOID:
tmp = DatumGetCString(DirectFunctionCall1(timestamp_out,
jb->val.datetime.value));
break;
case TIMESTAMPTZOID:
tmp = DatumGetCString(DirectFunctionCall1(timestamptz_out,
jb->val.datetime.value));
break;
default:
elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
jb->val.datetime.typid);
}
}
break;
case jbvNull:
case jbvArray:
case jbvObject:
case jbvBinary:
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("jsonpath item method .%s() can only be applied to a bool, string, numeric, or datetime value",
jspOperationName(jsp->type)))));
break;
}
jb = &jbv;
Assert(tmp != NULL); /* We must have set tmp above */
jb->val.string.val = tmp;
jb->val.string.len = strlen(jb->val.string.val);
jb->type = jbvString;
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
}
break;
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;
/* since this function recurses, it could be driven to stack overflow */
check_stack_depth();
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, cxt);
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 (jspStrictAbsenceOfErrors(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 (jspStrictAbsenceOfErrors(cxt))
return jpbUnknown;
error = true;
}
else if (res == jpbTrue)
{
if (!jspStrictAbsenceOfErrors(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_SQL_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_SQL_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_SQL_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)
{
cxt->regex =
cstring_to_text_with_len(jsp->content.like_regex.pattern,
jsp->content.like_regex.patternlen);
(void) jspConvertRegexFlags(jsp->content.like_regex.flags,
&(cxt->cflags), NULL);
}
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_SQL_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 the .datetime() and related methods.
*
* Converts a string into a date/time value. The actual type is determined at
* run time.
* If an argument is provided, this argument is used as a template string.
* Otherwise, the first fitting ISO format is selected.
*
* .date(), .time(), .time_tz(), .timestamp(), .timestamp_tz() methods don't
* have a format, so ISO format is used. However, except for .date(), they all
* take an optional time precision.
*/
static JsonPathExecResult
executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
JsonbValue *jb, JsonValueList *found)
{
JsonbValue jbvbuf;
Datum value;
text *datetime;
Oid collid;
Oid typid;
int32 typmod = -1;
int tz = 0;
bool hasNext;
JsonPathExecResult res = jperNotFound;
JsonPathItem elem;
int32 time_precision = -1;
if (!(jb = getScalar(jb, jbvString)))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("jsonpath item method .%s() can only be applied to a string",
jspOperationName(jsp->type)))));
datetime = cstring_to_text_with_len(jb->val.string.val,
jb->val.string.len);
/*
* At some point we might wish to have callers supply the collation to
* use, but right now it's unclear that they'd be able to do better than
* DEFAULT_COLLATION_OID anyway.
*/
collid = DEFAULT_COLLATION_OID;
/*
* .datetime(template) has an argument, the rest of the methods don't have
* an argument. So we handle that separately.
*/
if (jsp->type == jpiDatetime && jsp->content.arg)
{
text *template;
char *template_str;
int template_len;
ErrorSaveContext escontext = {T_ErrorSaveContext};
jspGetArg(jsp, &elem);
if (elem.type != jpiString)
elog(ERROR, "invalid jsonpath item type for .datetime() argument");
template_str = jspGetString(&elem, &template_len);
template = cstring_to_text_with_len(template_str,
template_len);
value = parse_datetime(datetime, template, collid, true,
&typid, &typmod, &tz,
jspThrowErrors(cxt) ? NULL : (Node *) &escontext);
if (escontext.error_occurred)
res = jperError;
else
res = jperOk;
}
else
{
/*
* According to SQL/JSON standard enumerate ISO formats for: date,
* timetz, time, timestamptz, timestamp.
*
* We also support ISO 8601 format (with "T") for timestamps, because
* to_json[b]() functions use this format.
*/
static const char *fmt_str[] =
{
"yyyy-mm-dd", /* date */
"HH24:MI:SS.USTZ", /* timetz */
"HH24:MI:SSTZ",
"HH24:MI:SS.US", /* time without tz */
"HH24:MI:SS",
"yyyy-mm-dd HH24:MI:SS.USTZ", /* timestamptz */
"yyyy-mm-dd HH24:MI:SSTZ",
"yyyy-mm-dd\"T\"HH24:MI:SS.USTZ",
"yyyy-mm-dd\"T\"HH24:MI:SSTZ",
"yyyy-mm-dd HH24:MI:SS.US", /* timestamp without tz */
"yyyy-mm-dd HH24:MI:SS",
"yyyy-mm-dd\"T\"HH24:MI:SS.US",
"yyyy-mm-dd\"T\"HH24:MI:SS"
};
/* cache for format texts */
static text *fmt_txt[lengthof(fmt_str)] = {0};
int i;
/*
* Check for optional precision for methods other than .datetime() and
* .date()
*/
if (jsp->type != jpiDatetime && jsp->type != jpiDate &&
jsp->content.arg)
{
bool have_error;
jspGetArg(jsp, &elem);
if (elem.type != jpiNumeric)
elog(ERROR, "invalid jsonpath item type for %s argument",
jspOperationName(jsp->type));
time_precision = numeric_int4_opt_error(jspGetNumeric(&elem),
&have_error);
if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("time precision of jsonpath item method .%s() is out of range for type integer",
jspOperationName(jsp->type)))));
}
/* loop until datetime format fits */
for (i = 0; i < lengthof(fmt_str); i++)
{
ErrorSaveContext escontext = {T_ErrorSaveContext};
if (!fmt_txt[i])
{
MemoryContext oldcxt =
MemoryContextSwitchTo(TopMemoryContext);
fmt_txt[i] = cstring_to_text(fmt_str[i]);
MemoryContextSwitchTo(oldcxt);
}
value = parse_datetime(datetime, fmt_txt[i], collid, true,
&typid, &typmod, &tz,
(Node *) &escontext);
if (!escontext.error_occurred)
{
res = jperOk;
break;
}
}
if (res == jperNotFound)
{
if (jsp->type == jpiDatetime)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("%s format is not recognized: \"%s\"",
"datetime", text_to_cstring(datetime)),
errhint("Use a datetime template argument to specify the input data format."))));
else
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("%s format is not recognized: \"%s\"",
jspOperationName(jsp->type), text_to_cstring(datetime)))));
}
}
/*
* parse_datetime() processes the entire input string per the template or
* ISO format and returns the Datum in best fitted datetime type. So, if
* this call is for a specific datatype, then we do the conversion here.
* Throw an error for incompatible types.
*/
switch (jsp->type)
{
case jpiDatetime: /* Nothing to do for DATETIME */
break;
case jpiDate:
{
/* Convert result type to date */
switch (typid)
{
case DATEOID: /* Nothing to do for DATE */
break;
case TIMEOID:
case TIMETZOID:
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("%s format is not recognized: \"%s\"",
"date", text_to_cstring(datetime)))));
break;
case TIMESTAMPOID:
value = DirectFunctionCall1(timestamp_date,
value);
break;
case TIMESTAMPTZOID:
checkTimezoneIsUsedForCast(cxt->useTz,
"timestamptz", "date");
value = DirectFunctionCall1(timestamptz_date,
value);
break;
default:
elog(ERROR, "type with oid %u not supported", typid);
}
typid = DATEOID;
}
break;
case jpiTime:
{
/* Convert result type to time without time zone */
switch (typid)
{
case DATEOID:
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("%s format is not recognized: \"%s\"",
"time", text_to_cstring(datetime)))));
break;
case TIMEOID: /* Nothing to do for TIME */
break;
case TIMETZOID:
checkTimezoneIsUsedForCast(cxt->useTz,
"timetz", "time");
value = DirectFunctionCall1(timetz_time,
value);
break;
case TIMESTAMPOID:
value = DirectFunctionCall1(timestamp_time,
value);
break;
case TIMESTAMPTZOID:
checkTimezoneIsUsedForCast(cxt->useTz,
"timestamptz", "time");
value = DirectFunctionCall1(timestamptz_time,
value);
break;
default:
elog(ERROR, "type with oid %u not supported", typid);
}
/* Force the user-given time precision, if any */
if (time_precision != -1)
{
TimeADT result;
/* Get a warning when precision is reduced */
time_precision = anytime_typmod_check(false,
time_precision);
result = DatumGetTimeADT(value);
AdjustTimeForTypmod(&result, time_precision);
value = TimeADTGetDatum(result);
/* Update the typmod value with the user-given precision */
typmod = time_precision;
}
typid = TIMEOID;
}
break;
case jpiTimeTz:
{
/* Convert result type to time with time zone */
switch (typid)
{
case DATEOID:
case TIMESTAMPOID:
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("%s format is not recognized: \"%s\"",
"time_tz", text_to_cstring(datetime)))));
break;
case TIMEOID:
checkTimezoneIsUsedForCast(cxt->useTz,
"time", "timetz");
value = DirectFunctionCall1(time_timetz,
value);
break;
case TIMETZOID: /* Nothing to do for TIMETZ */
break;
case TIMESTAMPTZOID:
value = DirectFunctionCall1(timestamptz_timetz,
value);
break;
default:
elog(ERROR, "type with oid %u not supported", typid);
}
/* Force the user-given time precision, if any */
if (time_precision != -1)
{
TimeTzADT *result;
/* Get a warning when precision is reduced */
time_precision = anytime_typmod_check(true,
time_precision);
result = DatumGetTimeTzADTP(value);
AdjustTimeForTypmod(&result->time, time_precision);
value = TimeTzADTPGetDatum(result);
/* Update the typmod value with the user-given precision */
typmod = time_precision;
}
typid = TIMETZOID;
}
break;
case jpiTimestamp:
{
/* Convert result type to timestamp without time zone */
switch (typid)
{
case DATEOID:
value = DirectFunctionCall1(date_timestamp,
value);
break;
case TIMEOID:
case TIMETZOID:
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("%s format is not recognized: \"%s\"",
"timestamp", text_to_cstring(datetime)))));
break;
case TIMESTAMPOID: /* Nothing to do for TIMESTAMP */
break;
case TIMESTAMPTZOID:
checkTimezoneIsUsedForCast(cxt->useTz,
"timestamptz", "timestamp");
value = DirectFunctionCall1(timestamptz_timestamp,
value);
break;
default:
elog(ERROR, "type with oid %u not supported", typid);
}
/* Force the user-given time precision, if any */
if (time_precision != -1)
{
Timestamp result;
ErrorSaveContext escontext = {T_ErrorSaveContext};
/* Get a warning when precision is reduced */
time_precision = anytimestamp_typmod_check(false,
time_precision);
result = DatumGetTimestamp(value);
AdjustTimestampForTypmod(&result, time_precision,
(Node *) &escontext);
if (escontext.error_occurred) /* should not happen */
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("time precision of jsonpath item method .%s() is invalid",
jspOperationName(jsp->type)))));
value = TimestampGetDatum(result);
/* Update the typmod value with the user-given precision */
typmod = time_precision;
}
typid = TIMESTAMPOID;
}
break;
case jpiTimestampTz:
{
/* Convert result type to timestamp with time zone */
switch (typid)
{
case DATEOID:
checkTimezoneIsUsedForCast(cxt->useTz,
"date", "timestamptz");
value = DirectFunctionCall1(date_timestamptz,
value);
break;
case TIMEOID:
case TIMETZOID:
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("%s format is not recognized: \"%s\"",
"timestamp_tz", text_to_cstring(datetime)))));
break;
case TIMESTAMPOID:
checkTimezoneIsUsedForCast(cxt->useTz,
"timestamp", "timestamptz");
value = DirectFunctionCall1(timestamp_timestamptz,
value);
break;
case TIMESTAMPTZOID: /* Nothing to do for TIMESTAMPTZ */
break;
default:
elog(ERROR, "type with oid %u not supported", typid);
}
/* Force the user-given time precision, if any */
if (time_precision != -1)
{
Timestamp result;
ErrorSaveContext escontext = {T_ErrorSaveContext};
/* Get a warning when precision is reduced */
time_precision = anytimestamp_typmod_check(true,
time_precision);
result = DatumGetTimestampTz(value);
AdjustTimestampForTypmod(&result, time_precision,
(Node *) &escontext);
if (escontext.error_occurred) /* should not happen */
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("time precision of jsonpath item method .%s() is invalid",
jspOperationName(jsp->type)))));
value = TimestampTzGetDatum(result);
/* Update the typmod value with the user-given precision */
typmod = time_precision;
}
typid = TIMESTAMPTZOID;
}
break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
}
pfree(datetime);
if (jperIsError(res))
return res;
hasNext = jspGetNext(jsp, &elem);
if (!hasNext && !found)
return res;
jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
jb->type = jbvDatetime;
jb->val.datetime.value = value;
jb->val.datetime.typid = typid;
jb->val.datetime.typmod = typmod;
jb->val.datetime.tz = tz;
return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
}
/*
* 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_SQL_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 = int64_to_numeric(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, value);
return;
default:
elog(ERROR, "unexpected jsonpath item type");
}
}
/*
* Returns the computed value of a JSON path variable with given name.
*/
static JsonbValue *
GetJsonPathVar(void *cxt, char *varName, int varNameLen,
JsonbValue *baseObject, int *baseObjectId)
{
JsonPathVariable *var = NULL;
List *vars = cxt;
ListCell *lc;
JsonbValue *result;
int id = 1;
foreach(lc, vars)
{
JsonPathVariable *curvar = lfirst(lc);
if (!strncmp(curvar->name, varName, varNameLen))
{
var = curvar;
break;
}
id++;
}
if (var == NULL)
{
*baseObjectId = -1;
return NULL;
}
result = palloc(sizeof(JsonbValue));
if (var->isnull)
{
*baseObjectId = 0;
result->type = jbvNull;
}
else
JsonItemFromDatum(var->value, var->typid, var->typmod, result);
*baseObject = *result;
*baseObjectId = id;
return result;
}
static int
CountJsonPathVars(void *cxt)
{
List *vars = (List *) cxt;
return list_length(vars);
}
/*
* Initialize JsonbValue to pass to jsonpath executor from given
* datum value of the specified type.
*/
static void
JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonbValue *res)
{
switch (typid)
{
case BOOLOID:
res->type = jbvBool;
res->val.boolean = DatumGetBool(val);
break;
case NUMERICOID:
JsonbValueInitNumericDatum(res, val);
break;
case INT2OID:
JsonbValueInitNumericDatum(res, DirectFunctionCall1(int2_numeric, val));
break;
case INT4OID:
JsonbValueInitNumericDatum(res, DirectFunctionCall1(int4_numeric, val));
break;
case INT8OID:
JsonbValueInitNumericDatum(res, DirectFunctionCall1(int8_numeric, val));
break;
case FLOAT4OID:
JsonbValueInitNumericDatum(res, DirectFunctionCall1(float4_numeric, val));
break;
case FLOAT8OID:
JsonbValueInitNumericDatum(res, DirectFunctionCall1(float8_numeric, val));
break;
case TEXTOID:
case VARCHAROID:
res->type = jbvString;
res->val.string.val = VARDATA_ANY(val);
res->val.string.len = VARSIZE_ANY_EXHDR(val);
break;
case DATEOID:
case TIMEOID:
case TIMETZOID:
case TIMESTAMPOID:
case TIMESTAMPTZOID:
res->type = jbvDatetime;
res->val.datetime.value = val;
res->val.datetime.typid = typid;
res->val.datetime.typmod = typmod;
res->val.datetime.tz = 0;
break;
case JSONBOID:
{
JsonbValue *jbv = res;
Jsonb *jb = DatumGetJsonbP(val);
if (JsonContainerIsScalar(&jb->root))
{
bool result PG_USED_FOR_ASSERTS_ONLY;
result = JsonbExtractScalar(&jb->root, jbv);
Assert(result);
}
else
JsonbInitBinary(jbv, jb);
break;
}
case JSONOID:
{
text *txt = DatumGetTextP(val);
char *str = text_to_cstring(txt);
Jsonb *jb;
jb = DatumGetJsonbP(DirectFunctionCall1(jsonb_in,
CStringGetDatum(str)));
pfree(str);
JsonItemFromDatum(JsonbPGetDatum(jb), JSONBOID, -1, res);
break;
}
default:
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not convert value of type %s to jsonpath",
format_type_be(typid)));
}
}
/* Initialize numeric value from the given datum */
static void
JsonbValueInitNumericDatum(JsonbValue *jbv, Datum num)
{
jbv->type = jbvNumeric;
jbv->val.numeric = DatumGetNumeric(num);
}
/*
* Get the value of variable passed to jsonpath executor
*/
static void
getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable,
JsonbValue *value)
{
char *varName;
int varNameLength;
JsonbValue baseObject;
int baseObjectId;
JsonbValue *v;
Assert(variable->type == jpiVariable);
varName = jspGetString(variable, &varNameLength);
if (cxt->vars == NULL ||
(v = cxt->getVar(cxt->vars, varName, varNameLength,
&baseObject, &baseObjectId)) == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("could not find jsonpath variable \"%s\"",
pnstrdup(varName, varNameLength))));
if (baseObjectId > 0)
{
*value = *v;
setBaseObject(cxt, &baseObject, baseObjectId);
}
}
/*
* Definition of JsonPathGetVarCallback for when JsonPathExecContext.vars
* is specified as a jsonb value.
*/
static JsonbValue *
getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength,
JsonbValue *baseObject, int *baseObjectId)
{
Jsonb *vars = varsJsonb;
JsonbValue tmp;
JsonbValue *result;
tmp.type = jbvString;
tmp.val.string.val = varName;
tmp.val.string.len = varNameLength;
result = findJsonbValueFromContainer(&vars->root, JB_FOBJECT, &tmp);
if (result == NULL)
{
*baseObjectId = -1;
return NULL;
}
*baseObjectId = 1;
JsonbInitBinary(baseObject, vars);
return result;
}
/*
* Definition of JsonPathCountVarsCallback for when JsonPathExecContext.vars
* is specified as a jsonb value.
*/
static int
countVariablesFromJsonb(void *varsJsonb)
{
Jsonb *vars = varsJsonb;
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."));
}
/* count of base objects */
return vars != NULL ? 1 : 0;
}
/**************** 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)
{
JsonPathExecContext *cxt = (JsonPathExecContext *) p;
return compareItems(cmp->type, lv, rv, cxt->useTz);
}
/*
* Perform per-byte comparison of two strings.
*/
static int
binaryCompareStrings(const char *s1, int len1,
const char *s2, int len2)
{
int cmp;
cmp = memcmp(s1, s2, Min(len1, len2));
if (cmp != 0)
return cmp;
if (len1 == len2)
return 0;
return len1 < len2 ? -1 : 1;
}
/*
* Compare two strings in the current server encoding using Unicode codepoint
* collation.
*/
static int
compareStrings(const char *mbstr1, int mblen1,
const char *mbstr2, int mblen2)
{
if (GetDatabaseEncoding() == PG_SQL_ASCII ||
GetDatabaseEncoding() == PG_UTF8)
{
/*
* It's known property of UTF-8 strings that their per-byte comparison
* result matches codepoints comparison result. ASCII can be
* considered as special case of UTF-8.
*/
return binaryCompareStrings(mbstr1, mblen1, mbstr2, mblen2);
}
else
{
char *utf8str1,
*utf8str2;
int cmp,
utf8len1,
utf8len2;
/*
* We have to convert other encodings to UTF-8 first, then compare.
* Input strings may be not null-terminated and pg_server_to_any() may
* return them "as is". So, use strlen() only if there is real
* conversion.
*/
utf8str1 = pg_server_to_any(mbstr1, mblen1, PG_UTF8);
utf8str2 = pg_server_to_any(mbstr2, mblen2, PG_UTF8);
utf8len1 = (mbstr1 == utf8str1) ? mblen1 : strlen(utf8str1);
utf8len2 = (mbstr2 == utf8str2) ? mblen2 : strlen(utf8str2);
cmp = binaryCompareStrings(utf8str1, utf8len1, utf8str2, utf8len2);
/*
* If pg_server_to_any() did no real conversion, then we actually
* compared original strings. So, we already done.
*/
if (mbstr1 == utf8str1 && mbstr2 == utf8str2)
return cmp;
/* Free memory if needed */
if (mbstr1 != utf8str1)
pfree(utf8str1);
if (mbstr2 != utf8str2)
pfree(utf8str2);
/*
* When all Unicode codepoints are equal, return result of binary
* comparison. In some edge cases, same characters may have different
* representations in encoding. Then our behavior could diverge from
* standard. However, that allow us to do simple binary comparison
* for "==" operator, which is performance critical in typical cases.
* In future to implement strict standard conformance, we can do
* normalization of input JSON strings.
*/
if (cmp == 0)
return binaryCompareStrings(mbstr1, mblen1, mbstr2, mblen2);
else
return cmp;
}
}
/*
* Compare two SQL/JSON items using comparison operation 'op'.
*/
static JsonPathBool
compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
{
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 = compareStrings(jb1->val.string.val, jb1->val.string.len,
jb2->val.string.val, jb2->val.string.len);
break;
case jbvDatetime:
{
bool cast_error;
cmp = compareDatetime(jb1->val.datetime.value,
jb1->val.datetime.typid,
jb2->val.datetime.value,
jb2->val.datetime.typid,
useTz,
&cast_error);
if (cast_error)
return jpbUnknown;
}
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_SQL_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_SQL_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
JsonValueListClear(JsonValueList *jvl)
{
jvl->singleton = NULL;
jvl->list = NIL;
}
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 && (jvl->list == NIL);
}
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 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);
}
/* Check if the timezone required for casting from type1 to type2 is used */
static void
checkTimezoneIsUsedForCast(bool useTz, const char *type1, const char *type2)
{
if (!useTz)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert value from %s to %s without time zone usage",
type1, type2),
errhint("Use *_tz() function for time zone support.")));
}
/* Convert time datum to timetz datum */
static Datum
castTimeToTimeTz(Datum time, bool useTz)
{
checkTimezoneIsUsedForCast(useTz, "time", "timetz");
return DirectFunctionCall1(time_timetz, time);
}
/*
* Compare date to timestamp.
* Note that this doesn't involve any timezone considerations.
*/
static int
cmpDateToTimestamp(DateADT date1, Timestamp ts2, bool useTz)
{
return date_cmp_timestamp_internal(date1, ts2);
}
/*
* Compare date to timestamptz.
*/
static int
cmpDateToTimestampTz(DateADT date1, TimestampTz tstz2, bool useTz)
{
checkTimezoneIsUsedForCast(useTz, "date", "timestamptz");
return date_cmp_timestamptz_internal(date1, tstz2);
}
/*
* Compare timestamp to timestamptz.
*/
static int
cmpTimestampToTimestampTz(Timestamp ts1, TimestampTz tstz2, bool useTz)
{
checkTimezoneIsUsedForCast(useTz, "timestamp", "timestamptz");
return timestamp_cmp_timestamptz_internal(ts1, tstz2);
}
/*
* Cross-type comparison of two datetime SQL/JSON items. If items are
* uncomparable *cast_error flag is set, otherwise *cast_error is unset.
* If the cast requires timezone and it is not used, then explicit error is thrown.
*/
static int
compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
bool useTz, bool *cast_error)
{
PGFunction cmpfunc;
*cast_error = false;
switch (typid1)
{
case DATEOID:
switch (typid2)
{
case DATEOID:
cmpfunc = date_cmp;
break;
case TIMESTAMPOID:
return cmpDateToTimestamp(DatumGetDateADT(val1),
DatumGetTimestamp(val2),
useTz);
case TIMESTAMPTZOID:
return cmpDateToTimestampTz(DatumGetDateADT(val1),
DatumGetTimestampTz(val2),
useTz);
case TIMEOID:
case TIMETZOID:
*cast_error = true; /* uncomparable types */
return 0;
default:
elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
typid2);
}
break;
case TIMEOID:
switch (typid2)
{
case TIMEOID:
cmpfunc = time_cmp;
break;
case TIMETZOID:
val1 = castTimeToTimeTz(val1, useTz);
cmpfunc = timetz_cmp;
break;
case DATEOID:
case TIMESTAMPOID:
case TIMESTAMPTZOID:
*cast_error = true; /* uncomparable types */
return 0;
default:
elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
typid2);
}
break;
case TIMETZOID:
switch (typid2)
{
case TIMEOID:
val2 = castTimeToTimeTz(val2, useTz);
cmpfunc = timetz_cmp;
break;
case TIMETZOID:
cmpfunc = timetz_cmp;
break;
case DATEOID:
case TIMESTAMPOID:
case TIMESTAMPTZOID:
*cast_error = true; /* uncomparable types */
return 0;
default:
elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
typid2);
}
break;
case TIMESTAMPOID:
switch (typid2)
{
case DATEOID:
return -cmpDateToTimestamp(DatumGetDateADT(val2),
DatumGetTimestamp(val1),
useTz);
case TIMESTAMPOID:
cmpfunc = timestamp_cmp;
break;
case TIMESTAMPTZOID:
return cmpTimestampToTimestampTz(DatumGetTimestamp(val1),
DatumGetTimestampTz(val2),
useTz);
case TIMEOID:
case TIMETZOID:
*cast_error = true; /* uncomparable types */
return 0;
default:
elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
typid2);
}
break;
case TIMESTAMPTZOID:
switch (typid2)
{
case DATEOID:
return -cmpDateToTimestampTz(DatumGetDateADT(val2),
DatumGetTimestampTz(val1),
useTz);
case TIMESTAMPOID:
return -cmpTimestampToTimestampTz(DatumGetTimestamp(val2),
DatumGetTimestampTz(val1),
useTz);
case TIMESTAMPTZOID:
cmpfunc = timestamp_cmp;
break;
case TIMEOID:
case TIMETZOID:
*cast_error = true; /* uncomparable types */
return 0;
default:
elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
typid2);
}
break;
default:
elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", typid1);
}
if (*cast_error)
return 0; /* cast error */
return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
}
/*
* Executor-callable JSON_EXISTS implementation
*
* Returns NULL instead of throwing errors if 'error' is not NULL, setting
* *error to true.
*/
bool
JsonPathExists(Datum jb, JsonPath *jp, bool *error, List *vars)
{
JsonPathExecResult res;
res = executeJsonPath(jp, vars,
GetJsonPathVar, CountJsonPathVars,
DatumGetJsonbP(jb), !error, NULL, true);
Assert(error || !jperIsError(res));
if (error && jperIsError(res))
*error = true;
return res == jperOk;
}
/*
* Executor-callable JSON_QUERY implementation
*
* Returns NULL instead of throwing errors if 'error' is not NULL, setting
* *error to true. *empty is set to true if no match is found.
*/
Datum
JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty,
bool *error, List *vars)
{
JsonbValue *singleton;
bool wrap;
JsonValueList found = {0};
JsonPathExecResult res;
int count;
res = executeJsonPath(jp, vars,
GetJsonPathVar, CountJsonPathVars,
DatumGetJsonbP(jb), !error, &found, true);
Assert(error || !jperIsError(res));
if (error && jperIsError(res))
{
*error = true;
*empty = false;
return (Datum) 0;
}
/* WRAP or not? */
count = JsonValueListLength(&found);
singleton = count > 0 ? JsonValueListHead(&found) : NULL;
if (singleton == NULL)
wrap = false;
else if (wrapper == JSW_NONE || wrapper == JSW_UNSPEC)
wrap = false;
else if (wrapper == JSW_UNCONDITIONAL)
wrap = true;
else if (wrapper == JSW_CONDITIONAL)
wrap = count > 1 ||
IsAJsonbScalar(singleton) ||
(singleton->type == jbvBinary &&
JsonContainerIsScalar(singleton->val.binary.data));
else
{
elog(ERROR, "unrecognized json wrapper %d", wrapper);
wrap = false;
}
if (wrap)
return JsonbPGetDatum(JsonbValueToJsonb(wrapItemsInArray(&found)));
/* No wrapping means only one item is expected. */
if (count > 1)
{
if (error)
{
*error = true;
return (Datum) 0;
}
ereport(ERROR,
(errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
errmsg("JSON path expression in JSON_QUERY should return singleton item without wrapper"),
errhint("Use WITH WRAPPER clause to wrap SQL/JSON item sequence into array.")));
}
if (singleton)
return JsonbPGetDatum(JsonbValueToJsonb(singleton));
*empty = true;
return PointerGetDatum(NULL);
}
/*
* Executor-callable JSON_VALUE implementation
*
* Returns NULL instead of throwing errors if 'error' is not NULL, setting
* *error to true. *empty is set to true if no match is found.
*/
JsonbValue *
JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars)
{
JsonbValue *res;
JsonValueList found = {0};
JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY;
int count;
jper = executeJsonPath(jp, vars, GetJsonPathVar, CountJsonPathVars,
DatumGetJsonbP(jb),
!error, &found, true);
Assert(error || !jperIsError(jper));
if (error && jperIsError(jper))
{
*error = true;
*empty = false;
return NULL;
}
count = JsonValueListLength(&found);
*empty = (count == 0);
if (*empty)
return NULL;
/* JSON_VALUE expects to get only singletons. */
if (count > 1)
{
if (error)
{
*error = true;
return NULL;
}
ereport(ERROR,
(errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
errmsg("JSON path expression in JSON_VALUE should return singleton scalar item")));
}
res = JsonValueListHead(&found);
if (res->type == jbvBinary && JsonContainerIsScalar(res->val.binary.data))
JsonbExtractScalar(res->val.binary.data, res);
/* JSON_VALUE expects to get only scalars. */
if (!IsAJsonbScalar(res))
{
if (error)
{
*error = true;
return NULL;
}
ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED),
errmsg("JSON path expression in JSON_VALUE should return singleton scalar item")));
}
if (res->type == jbvNull)
return NULL;
return res;
}
/************************ JSON_TABLE functions ***************************/
/*
* Sanity-checks and returns the opaque JsonTableExecContext from the
* given executor state struct.
*/
static inline JsonTableExecContext *
GetJsonTableExecContext(TableFuncScanState *state, const char *fname)
{
JsonTableExecContext *result;
if (!IsA(state, TableFuncScanState))
elog(ERROR, "%s called with invalid TableFuncScanState", fname);
result = (JsonTableExecContext *) state->opaque;
if (result->magic != JSON_TABLE_EXEC_CONTEXT_MAGIC)
elog(ERROR, "%s called with invalid TableFuncScanState", fname);
return result;
}
/*
* JsonTableInitOpaque
* Fill in TableFuncScanState->opaque for processing JSON_TABLE
*
* This initializes the PASSING arguments and the JsonTablePlanState for
* JsonTablePlan given in TableFunc.
*/
static void
JsonTableInitOpaque(TableFuncScanState *state, int natts)
{
JsonTableExecContext *cxt;
PlanState *ps = &state->ss.ps;
TableFuncScan *tfs = castNode(TableFuncScan, ps->plan);
TableFunc *tf = tfs->tablefunc;
JsonTablePlan *rootplan = (JsonTablePlan *) tf->plan;
JsonExpr *je = castNode(JsonExpr, tf->docexpr);
List *args = NIL;
cxt = palloc0(sizeof(JsonTableExecContext));
cxt->magic = JSON_TABLE_EXEC_CONTEXT_MAGIC;
/*
* Evaluate JSON_TABLE() PASSING arguments to be passed to the jsonpath
* executor via JsonPathVariables.
*/
if (state->passingvalexprs)
{
ListCell *exprlc;
ListCell *namelc;
Assert(list_length(state->passingvalexprs) ==
list_length(je->passing_names));
forboth(exprlc, state->passingvalexprs,
namelc, je->passing_names)
{
ExprState *state = lfirst_node(ExprState, exprlc);
String *name = lfirst_node(String, namelc);
JsonPathVariable *var = palloc(sizeof(*var));
var->name = pstrdup(name->sval);
var->typid = exprType((Node *) state->expr);
var->typmod = exprTypmod((Node *) state->expr);
/*
* Evaluate the expression and save the value to be returned by
* GetJsonPathVar().
*/
var->value = ExecEvalExpr(state, ps->ps_ExprContext,
&var->isnull);
args = lappend(args, var);
}
}
/* Initialize plan */
cxt->rootplanstate = JsonTableInitPlan(cxt, rootplan, args,
CurrentMemoryContext);
state->opaque = cxt;
}
/*
* JsonTableDestroyOpaque
* Resets state->opaque
*/
static void
JsonTableDestroyOpaque(TableFuncScanState *state)
{
JsonTableExecContext *cxt =
GetJsonTableExecContext(state, "JsonTableDestroyOpaque");
/* not valid anymore */
cxt->magic = 0;
state->opaque = NULL;
}
/*
* JsonTableInitPlan
* Initialize information for evaluating jsonpath in the given
* JsonTablePlan
*/
static JsonTablePlanState *
JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
List *args, MemoryContext mcxt)
{
JsonTablePlanState *planstate = palloc0(sizeof(*planstate));
planstate->plan = plan;
if (IsA(plan, JsonTablePathScan))
{
JsonTablePathScan *scan = (JsonTablePathScan *) plan;
planstate->path = DatumGetJsonPathP(scan->path->value->constvalue);
planstate->args = args;
planstate->mcxt = AllocSetContextCreate(mcxt, "JsonTableExecContext",
ALLOCSET_DEFAULT_SIZES);
/* No row pattern evaluated yet. */
planstate->current.value = PointerGetDatum(NULL);
planstate->current.isnull = true;
}
return planstate;
}
/*
* JsonTableSetDocument
* Install the input document and evaluate the row pattern
*/
static void
JsonTableSetDocument(TableFuncScanState *state, Datum value)
{
JsonTableExecContext *cxt =
GetJsonTableExecContext(state, "JsonTableSetDocument");
JsonTableResetRowPattern(cxt->rootplanstate, value);
}
/*
* Evaluate a JsonTablePlan's jsonpath to get a new row pattren from
* the given context item
*/
static void
JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
{
JsonTablePathScan *scan = castNode(JsonTablePathScan, planstate->plan);
MemoryContext oldcxt;
JsonPathExecResult res;
Jsonb *js = (Jsonb *) DatumGetJsonbP(item);
JsonValueListClear(&planstate->found);
MemoryContextResetOnly(planstate->mcxt);
oldcxt = MemoryContextSwitchTo(planstate->mcxt);
res = executeJsonPath(planstate->path, planstate->args,
GetJsonPathVar, CountJsonPathVars,
js, scan->errorOnError,
&planstate->found,
true);
MemoryContextSwitchTo(oldcxt);
if (jperIsError(res))
{
Assert(!scan->errorOnError);
JsonValueListClear(&planstate->found);
}
/* Reset plan iterator to the beginning of the item list */
JsonValueListInitIterator(&planstate->found, &planstate->iter);
planstate->current.value = PointerGetDatum(NULL);
planstate->current.isnull = true;
planstate->ordinal = 0;
}
/*
* Fetch next row from a JsonTablePlan's path evaluation result.
*
* Returns false if the plan has run out of rows, true otherwise.
*/
static bool
JsonTablePlanNextRow(JsonTablePlanState *planstate)
{
JsonbValue *jbv = JsonValueListNext(&planstate->found, &planstate->iter);
MemoryContext oldcxt;
/* End of list? */
if (jbv == NULL)
{
planstate->current.value = PointerGetDatum(NULL);
planstate->current.isnull = true;
return false;
}
/*
* Set current row item for subsequent JsonTableGetValue() calls for
* evaluating individual columns.
*/
oldcxt = MemoryContextSwitchTo(planstate->mcxt);
planstate->current.value = JsonbPGetDatum(JsonbValueToJsonb(jbv));
planstate->current.isnull = false;
MemoryContextSwitchTo(oldcxt);
/* Next row! */
planstate->ordinal++;
return true;
}
/*
* JsonTableFetchRow
* Prepare the next "current" row for upcoming GetValue calls.
*
* Returns false if no more rows can be returned.
*/
static bool
JsonTableFetchRow(TableFuncScanState *state)
{
JsonTableExecContext *cxt =
GetJsonTableExecContext(state, "JsonTableFetchRow");
return JsonTablePlanNextRow(cxt->rootplanstate);
}
/*
* JsonTableGetValue
* Return the value for column number 'colnum' for the current row.
*
* This leaks memory, so be sure to reset often the context in which it's
* called.
*/
static Datum
JsonTableGetValue(TableFuncScanState *state, int colnum,
Oid typid, int32 typmod, bool *isnull)
{
JsonTableExecContext *cxt =
GetJsonTableExecContext(state, "JsonTableGetValue");
ExprContext *econtext = state->ss.ps.ps_ExprContext;
ExprState *estate = list_nth(state->colvalexprs, colnum);
JsonTablePlanState *planstate = cxt->rootplanstate;
JsonTablePlanRowSource *current = &planstate->current;
Datum result;
/* Row pattern value is NULL */
if (current->isnull)
{
result = (Datum) 0;
*isnull = true;
}
/* Evaluate JsonExpr. */
else if (estate)
{
Datum saved_caseValue = econtext->caseValue_datum;
bool saved_caseIsNull = econtext->caseValue_isNull;
/* Pass the row pattern value via CaseTestExpr. */
econtext->caseValue_datum = current->value;
econtext->caseValue_isNull = false;
result = ExecEvalExpr(estate, econtext, isnull);
econtext->caseValue_datum = saved_caseValue;
econtext->caseValue_isNull = saved_caseIsNull;
}
/* ORDINAL column */
else
{
result = Int32GetDatum(planstate->ordinal);
*isnull = false;
}
return result;
}