diff options
Diffstat (limited to 'src/backend/executor/execExprInterp.c')
-rw-r--r-- | src/backend/executor/execExprInterp.c | 338 |
1 files changed, 337 insertions, 1 deletions
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index a25ab7570fe..24a3990a30a 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -72,8 +72,8 @@ #include "utils/datum.h" #include "utils/expandedrecord.h" #include "utils/json.h" -#include "utils/jsonb.h" #include "utils/jsonfuncs.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/timestamp.h" @@ -180,6 +180,7 @@ static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate AggStatePerGroup pergroup, ExprContext *aggcontext, int setno); +static char *ExecGetJsonValueItemString(JsonbValue *item, bool *resnull); /* * ScalarArrayOpExprHashEntry @@ -481,6 +482,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_XMLEXPR, &&CASE_EEOP_JSON_CONSTRUCTOR, &&CASE_EEOP_IS_JSON, + &&CASE_EEOP_JSONEXPR_PATH, + &&CASE_EEOP_JSONEXPR_COERCION, + &&CASE_EEOP_JSONEXPR_COERCION_FINISH, &&CASE_EEOP_AGGREF, &&CASE_EEOP_GROUPING_FUNC, &&CASE_EEOP_WINDOW_FUNC, @@ -1554,6 +1558,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_JSONEXPR_PATH) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalJsonExprPath(state, op, econtext)); + } + + EEO_CASE(EEOP_JSONEXPR_COERCION) + { + /* too complex for an inline implementation */ + ExecEvalJsonCoercion(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_JSONEXPR_COERCION_FINISH) + { + /* too complex for an inline implementation */ + ExecEvalJsonCoercionFinish(state, op); + + EEO_NEXT(); + } + EEO_CASE(EEOP_AGGREF) { /* @@ -4222,6 +4248,316 @@ ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op) *op->resvalue = BoolGetDatum(res); } +/* + * Evaluate a jsonpath against a document, both of which must have been + * evaluated and their values saved in op->d.jsonexpr.jsestate. + * + * If an error occurs during JsonPath* evaluation or when coercing its result + * to the RETURNING type, JsonExprState.error is set to true, provided the + * ON ERROR behavior is not ERROR. Similarly, if JsonPath{Query|Value}() found + * no matching items, JsonExprState.empty is set to true, provided the ON EMPTY + * behavior is not ERROR. That is to signal to the subsequent steps that check + * those flags to return the ON ERROR / ON EMPTY expression. + * + * Return value is the step address to be performed next. It will be one of + * jump_error, jump_empty, jump_eval_coercion, or jump_end, all given in + * op->d.jsonexpr.jsestate. + */ +int +ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + JsonExprState *jsestate = op->d.jsonexpr.jsestate; + JsonExpr *jsexpr = jsestate->jsexpr; + Datum item; + JsonPath *path; + bool throw_error = jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR; + bool error = false, + empty = false; + int jump_eval_coercion = jsestate->jump_eval_coercion; + char *val_string = NULL; + + item = jsestate->formatted_expr.value; + path = DatumGetJsonPathP(jsestate->pathspec.value); + + /* Set error/empty to false. */ + memset(&jsestate->error, 0, sizeof(NullableDatum)); + memset(&jsestate->empty, 0, sizeof(NullableDatum)); + + /* + * Also reset ErrorSaveContext contents for the next row. Since we don't + * set details_wanted, we don't need to also reset error_data, which would + * be NULL anyway. + */ + Assert(!jsestate->escontext.details_wanted && + jsestate->escontext.error_data == NULL); + jsestate->escontext.error_occurred = false; + + switch (jsexpr->op) + { + case JSON_EXISTS_OP: + { + bool exists = JsonPathExists(item, path, + !throw_error ? &error : NULL, + jsestate->args); + + if (!error) + { + *op->resvalue = BoolGetDatum(exists); + *op->resnull = false; + } + } + break; + + case JSON_QUERY_OP: + *op->resvalue = JsonPathQuery(item, path, jsexpr->wrapper, &empty, + !throw_error ? &error : NULL, + jsestate->args); + + *op->resnull = (DatumGetPointer(*op->resvalue) == NULL); + + /* Handle OMIT QUOTES. */ + if (!*op->resnull && jsexpr->omit_quotes) + { + val_string = JsonbUnquote(DatumGetJsonbP(*op->resvalue)); + + /* + * Pass the string as a text value to the cast expression if + * one present. If not, use the input function call below to + * do the coercion. + */ + if (jump_eval_coercion >= 0) + *op->resvalue = + DirectFunctionCall1(textin, + PointerGetDatum(val_string)); + } + break; + + case JSON_VALUE_OP: + { + JsonbValue *jbv = JsonPathValue(item, path, &empty, + !throw_error ? &error : NULL, + jsestate->args); + + if (jbv == NULL) + { + /* Will be coerced with coercion_expr, if any. */ + *op->resvalue = (Datum) 0; + *op->resnull = true; + } + else if (!error && !empty) + { + if (jsexpr->returning->typid == JSONOID || + jsexpr->returning->typid == JSONBOID) + { + val_string = DatumGetCString(DirectFunctionCall1(jsonb_out, + JsonbPGetDatum(JsonbValueToJsonb(jbv)))); + } + else + { + val_string = ExecGetJsonValueItemString(jbv, op->resnull); + + /* + * Pass the string as a text value to the cast + * expression if one present. If not, use the input + * function call below to do the coercion. + */ + *op->resvalue = PointerGetDatum(val_string); + if (jump_eval_coercion >= 0) + *op->resvalue = DirectFunctionCall1(textin, *op->resvalue); + } + } + break; + } + + default: + elog(ERROR, "unrecognized SQL/JSON expression op %d", + (int) jsexpr->op); + return false; + } + + /* + * Coerce the result value to the RETURNING type by calling its input + * function. + */ + if (!*op->resnull && jsexpr->use_io_coercion) + { + FunctionCallInfo fcinfo; + + Assert(jump_eval_coercion == -1); + fcinfo = jsestate->input_fcinfo; + Assert(fcinfo != NULL); + Assert(val_string != NULL); + fcinfo->args[0].value = PointerGetDatum(val_string); + fcinfo->args[0].isnull = *op->resnull; + + /* + * Second and third arguments are already set up in + * ExecInitJsonExpr(). + */ + + fcinfo->isnull = false; + *op->resvalue = FunctionCallInvoke(fcinfo); + if (SOFT_ERROR_OCCURRED(&jsestate->escontext)) + error = true; + } + + /* Handle ON EMPTY. */ + if (empty) + { + if (jsexpr->on_empty) + { + if (jsexpr->on_empty->btype == JSON_BEHAVIOR_ERROR) + ereport(ERROR, + errcode(ERRCODE_NO_SQL_JSON_ITEM), + errmsg("no SQL/JSON item")); + else + jsestate->empty.value = BoolGetDatum(true); + + Assert(jsestate->jump_empty >= 0); + return jsestate->jump_empty; + } + else if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR) + ereport(ERROR, + errcode(ERRCODE_NO_SQL_JSON_ITEM), + errmsg("no SQL/JSON item")); + else + jsestate->error.value = BoolGetDatum(true); + + *op->resvalue = (Datum) 0; + *op->resnull = true; + + Assert(!throw_error && jsestate->jump_error >= 0); + return jsestate->jump_error; + } + + /* + * ON ERROR. Wouldn't get here if the behavior is ERROR, because they + * would have already been thrown. + */ + if (error) + { + Assert(!throw_error && jsestate->jump_error >= 0); + *op->resvalue = (Datum) 0; + *op->resnull = true; + jsestate->error.value = BoolGetDatum(true); + return jsestate->jump_error; + } + + return jump_eval_coercion >= 0 ? jump_eval_coercion : jsestate->jump_end; +} + +/* + * Convert the given JsonbValue to its C string representation + * + * *resnull is set if the JsonbValue is a jbvNull. + */ +static char * +ExecGetJsonValueItemString(JsonbValue *item, bool *resnull) +{ + *resnull = false; + + /* get coercion state reference and datum of the corresponding SQL type */ + switch (item->type) + { + case jbvNull: + *resnull = true; + return NULL; + + case jbvString: + { + char *str = palloc(item->val.string.len + 1); + + memcpy(str, item->val.string.val, item->val.string.len); + str[item->val.string.len] = '\0'; + return str; + } + + case jbvNumeric: + return DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(item->val.numeric))); + + case jbvBool: + return DatumGetCString(DirectFunctionCall1(boolout, + BoolGetDatum(item->val.boolean))); + + case jbvDatetime: + switch (item->val.datetime.typid) + { + case DATEOID: + return DatumGetCString(DirectFunctionCall1(date_out, + item->val.datetime.value)); + case TIMEOID: + return DatumGetCString(DirectFunctionCall1(time_out, + item->val.datetime.value)); + case TIMETZOID: + return DatumGetCString(DirectFunctionCall1(timetz_out, + item->val.datetime.value)); + case TIMESTAMPOID: + return DatumGetCString(DirectFunctionCall1(timestamp_out, + item->val.datetime.value)); + case TIMESTAMPTZOID: + return DatumGetCString(DirectFunctionCall1(timestamptz_out, + item->val.datetime.value)); + default: + elog(ERROR, "unexpected jsonb datetime type oid %u", + item->val.datetime.typid); + } + break; + + case jbvArray: + case jbvObject: + case jbvBinary: + return DatumGetCString(DirectFunctionCall1(jsonb_out, + JsonbPGetDatum(JsonbValueToJsonb(item)))); + + default: + elog(ERROR, "unexpected jsonb value type %d", item->type); + } + + Assert(false); + *resnull = true; + return NULL; +} + +/* + * Coerce a jsonb value produced by ExecEvalJsonExprPath() or an ON ERROR / + * ON EMPTY behavior expression to the target type. + * + * Any soft errors that occur here will be checked by + * EEOP_JSONEXPR_COERCION_FINISH that will run after this. + */ +void +ExecEvalJsonCoercion(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + ErrorSaveContext *escontext = op->d.jsonexpr_coercion.escontext; + + *op->resvalue = json_populate_type(*op->resvalue, JSONBOID, + op->d.jsonexpr_coercion.targettype, + op->d.jsonexpr_coercion.targettypmod, + &op->d.jsonexpr_coercion.json_populate_type_cache, + econtext->ecxt_per_query_memory, + op->resnull, (Node *) escontext); +} + +/* + * Checks if an error occurred either when evaluating JsonExpr.coercion_expr or + * in ExecEvalJsonCoercion(). If so, this sets JsonExprState.error to trigger + * the ON ERROR handling steps. + */ +void +ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr.jsestate; + + if (SOFT_ERROR_OCCURRED(&jsestate->escontext)) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + jsestate->error.value = BoolGetDatum(true); + } +} /* * ExecEvalGroupingFunc |