From 87956c96df4902eaacbaee4a810bc0199135d76f Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Wed, 22 Jun 2022 12:00:47 -0400 Subject: [PATCH v6] Some improvements to JsonExpr evaluation * Store a pointer to the returning type input function FmgrInfo, not the struct itself, in JsonExprState * Evaluate various JsonExpr sub-expressions using parent ExprState These include PASSING args, ON ERROR, ON EMPTY default expressions. PASSING args are simple, because they all must be evaluated before the JSON path evaluation can occur anyway, so pushing those expressions to be uncondionally evaluated before the step to evaluate JsonExpr proper suffices. Evaluation of the ON ERROR and the ON EMPTY expressions is conditional on what JSON item pops out of the path evaluation (ExecEvalJson()), so needs more logic to gate the steps for their expressions that are pushed into the parent ExprState, rather than be imlemented using separate ExprStates. To that end, this moves the ON ERROR, ON EMPTY behavior decision logic out of ExecEvalJson() and ExecEvalJsonExpr() into a new function ExecEvalJsonExprBehavior(). * Final result coercion is now also computed as a separate step that gets pushed into the parent ExprState. Though given that any coercion evaluation errors must be handled the same way as the errors of JSON item evaluation, that is, to be handled according the ON ERROR behavior specification, a sub-transaction must be used around coercion evaluation, so the ExprState to use for the same must have to be a different one from the JsonExpr's. However, instead of using one ExprState for each JsonCoercion member of JsonItemCoercions, only one is used for the whole JsonItemCoercions. The individual JsonCoercion members (or their Exprs) are handled by steps pushed into that ExprState, of which only the one that applies to a given JSON item is chosen at the runtime and others "jumped" over. * Move the logic of deciding whether to use a sub-transaction for JSON path evaluation and subsequent coercion to ExecInitExprRec() instead of recomputing the same thing on every evaluation. --- src/backend/executor/execExpr.c | 447 +++++++++++++++++++----- src/backend/executor/execExprInterp.c | 474 +++++++++++++++++--------- src/backend/jit/llvm/llvmjit_expr.c | 220 +++++++++++- src/backend/jit/llvm/llvmjit_types.c | 4 + src/backend/optimizer/util/clauses.c | 6 +- src/backend/utils/adt/jsonpath_exec.c | 68 ++++ src/include/executor/execExpr.h | 193 ++++++++--- src/include/utils/jsonb.h | 2 + src/include/utils/jsonpath.h | 3 - 9 files changed, 1121 insertions(+), 296 deletions(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index d0a57c7aae..37d2acbc8b 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -86,6 +86,10 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, FunctionCallInfo fcinfo, AggStatePerTrans pertrans, int transno, int setno, int setoff, bool ishash, bool nullcheck); +static void ExecInitJsonExpr(ExprEvalStep *scratch, JsonExpr *jexpr, ExprState *state, + Datum *resv, bool *resnull); +static void ExecInitJsonItemCoercions(ExprEvalStep *scratch, JsonItemCoercions *coercions, + ExprState *state, Datum *resv, bool *resnull); static ExprState * @@ -178,7 +182,7 @@ ExecInitExprWithParams(Expr *node, ParamListInfo ext_params) * ExecInitExprWithCaseValue: prepare an expression tree for execution * * This is the same as ExecInitExpr, except that a pointer to the value for - * CasTestExpr is passed here. + * CaseTestExpr is passed here. */ ExprState * ExecInitExprWithCaseValue(Expr *node, PlanState *parent, @@ -2561,110 +2565,54 @@ ExecInitExprRec(Expr *node, ExprState *state, case T_JsonExpr: { JsonExpr *jexpr = castNode(JsonExpr, node); - JsonExprState *jsestate = palloc0(sizeof(JsonExprState)); - ListCell *argexprlc; - ListCell *argnamelc; - scratch.opcode = EEOP_JSONEXPR; - scratch.d.jsonexpr.jsestate = jsestate; - - jsestate->jsexpr = jexpr; - - jsestate->formatted_expr = - palloc(sizeof(*jsestate->formatted_expr)); - - ExecInitExprRec((Expr *) jexpr->formatted_expr, state, - &jsestate->formatted_expr->value, - &jsestate->formatted_expr->isnull); - - jsestate->pathspec = - palloc(sizeof(*jsestate->pathspec)); - - ExecInitExprRec((Expr *) jexpr->path_spec, state, - &jsestate->pathspec->value, - &jsestate->pathspec->isnull); - - jsestate->res_expr = - palloc(sizeof(*jsestate->res_expr)); - - jsestate->result_expr = jexpr->result_coercion - ? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr, - state->parent, - &jsestate->res_expr->value, - &jsestate->res_expr->isnull) - : NULL; - - jsestate->default_on_empty = !jexpr->on_empty ? NULL : - ExecInitExpr((Expr *) jexpr->on_empty->default_expr, - state->parent); - - jsestate->default_on_error = - ExecInitExpr((Expr *) jexpr->on_error->default_expr, - state->parent); + ExecInitJsonExpr(&scratch, jexpr, state, resv, resnull); + break; + } - if (jexpr->omit_quotes || - (jexpr->result_coercion && jexpr->result_coercion->via_io)) - { - Oid typinput; + case T_JsonItemCoercions: + { + JsonItemCoercions *coercions = castNode(JsonItemCoercions, + node); - /* lookup the result type's input function */ - getTypeInputInfo(jexpr->returning->typid, &typinput, - &jsestate->input.typioparam); - fmgr_info(typinput, &jsestate->input.func); - } + ExecInitJsonItemCoercions(&scratch, coercions, state, resv, + resnull); + break; + } - jsestate->args = NIL; + case T_JsonCoercion: + { + JsonCoercion *coercion = castNode(JsonCoercion, node); + Datum *save_innermost_caseval; + bool *save_innermost_isnull; - forboth(argexprlc, jexpr->passing_values, - argnamelc, jexpr->passing_names) - { - Expr *argexpr = (Expr *) lfirst(argexprlc); - String *argname = lfirst_node(String, argnamelc); - JsonPathVariableEvalContext *var = palloc(sizeof(*var)); - - var->name = pstrdup(argname->sval); - var->typid = exprType((Node *) argexpr); - var->typmod = exprTypmod((Node *) argexpr); - var->estate = ExecInitExpr(argexpr, state->parent); - var->econtext = NULL; - var->mcxt = NULL; - var->evaluated = false; - var->value = (Datum) 0; - var->isnull = true; - - jsestate->args = - lappend(jsestate->args, var); - } + /* + * Only ever get here for JsonExpr.result_expression and the + * caller should have checked this. + */ + Assert(coercion->expr); - jsestate->cache = NULL; + /* + * Push a step to read the value provided by the parent + * JsonExpr via a CaseTestExpr. + */ + scratch.opcode = EEOP_CASE_TESTVAL; + scratch.d.casetest.value = state->innermost_caseval; + scratch.d.casetest.isnull = state->innermost_casenull; + ExprEvalPushStep(state, &scratch); - if (jexpr->coercions) - { - JsonCoercion **coercion; - struct JsonCoercionState *cstate; - Datum *caseval; - bool *casenull; + /* Push step(s) to compute coercion->expr. */ + save_innermost_caseval = state->innermost_caseval; + save_innermost_isnull = state->innermost_casenull; - jsestate->coercion_expr = - palloc(sizeof(*jsestate->coercion_expr)); + state->innermost_caseval = resv; + state->innermost_casenull = resnull; - caseval = &jsestate->coercion_expr->value; - casenull = &jsestate->coercion_expr->isnull; + ExecInitExprRec((Expr *) coercion->expr, state, resv, resnull); - for (cstate = &jsestate->coercions.null, - coercion = &jexpr->coercions->null; - coercion <= &jexpr->coercions->composite; - coercion++, cstate++) - { - cstate->coercion = *coercion; - cstate->estate = *coercion ? - ExecInitExprWithCaseValue((Expr *) (*coercion)->expr, - state->parent, - caseval, casenull) : NULL; - } - } + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_isnull; - ExprEvalPushStep(state, &scratch); break; } @@ -4257,3 +4205,314 @@ ExecBuildParamSetEqual(TupleDesc desc, return state; } + +/* + * Push steps to evaluate a JsonExpr and its various subsidiary expressions. + */ +static void +ExecInitJsonExpr(ExprEvalStep *scratch, JsonExpr *jexpr, ExprState *state, + Datum *resv, bool *resnull) +{ + JsonExprState *jsestate = palloc0(sizeof(JsonExprState)); + JsonExprPreEvalState *pre_eval = &jsestate->pre_eval; + ListCell *argexprlc; + ListCell *argnamelc; + int skip_step_off; + int passing_args_step_off; + int behavior_step_off; + int onempty_default_step_off; + int onerror_default_step_off; + List *adjust_jumps = NIL; + ListCell *lc; + int coercion_step_off; + ExprEvalStep *as; + bool throwErrors = (jexpr->on_error->btype == JSON_BEHAVIOR_ERROR); + + jsestate->jsexpr = jexpr; + + /* + * Add steps to compute formatted_expr, pathspec, and PASSING arg + * expressions as things that must be evaluated *before* the actual JSON + * path expression. + */ + ExecInitExprRec((Expr *) jexpr->formatted_expr, state, + &pre_eval->formatted_expr.value, + &pre_eval->formatted_expr.isnull); + ExecInitExprRec((Expr *) jexpr->path_spec, state, + &pre_eval->pathspec.value, + &pre_eval->pathspec.isnull); + + /* + * Before pushing steps for PASSING args, push a step to decide whether to + * skip evaluating the args and the JSON path expression depending on + * whether either of formatted_expr and pathspec is NULL. + */ + scratch->opcode = EEOP_JSONEXPR_SKIP; + scratch->d.jsonexpr_skip.jsestate = jsestate; + skip_step_off = state->steps_len; + ExprEvalPushStep(state, scratch); + + /* PASSING args. */ + jsestate->pre_eval.args = NIL; + passing_args_step_off = state->steps_len; + forboth(argexprlc, jexpr->passing_values, + argnamelc, jexpr->passing_names) + { + Expr *argexpr = (Expr *) lfirst(argexprlc); + String *argname = lfirst_node(String, argnamelc); + JsonPathVariableEvalContext *var = palloc(sizeof(*var)); + + var->name = pstrdup(argname->sval); + var->typid = exprType((Node *) argexpr); + var->typmod = exprTypmod((Node *) argexpr); + + /* + * A separate ExprState is not necessary for these expressions when + * being evaluated for a JsonExpr, like in this case, because they + * will evaluated as the steps of the JsonExpr. + */ + var->estate = NULL; + var->econtext = NULL; + var->mcxt = NULL; + + /* + * Mark these as always evaluated because they must have been evaluated + * before JSON path evaluation begins, because we haven't pushed the + * step for the latter yet. + */ + var->evaluated = true; + + ExecInitExprRec((Expr *) argexpr, state, &var->value, &var->isnull); + + pre_eval->args = lappend(pre_eval->args, var); + } + + /* Step for the actual JSON path evaluation. */ + scratch->opcode = EEOP_JSONEXPR_PATH; + scratch->d.jsonexpr.jsestate = jsestate; + ExprEvalPushStep(state, scratch); + + /* + * Push steps to control the evaluation of expressions based + * on the result of JSON path evaluation. + */ + + /* Step to handle ON ERROR and ON EMPTY behavior */ + scratch->opcode = EEOP_JSONEXPR_BEHAVIOR; + scratch->d.jsonexpr_behavior.jsestate = jsestate; + behavior_step_off = state->steps_len; + ExprEvalPushStep(state, scratch); + + /* Step(s) to evaluate ON EMPTY default expression */ + onempty_default_step_off = state->steps_len; + if (jexpr->on_empty && jexpr->on_empty->default_expr) + { + ExecInitExprRec((Expr *) jexpr->on_empty->default_expr, + state, resv, resnull); + + /* + * Emit JUMP step to jump to end of JsonExpr code, because evaluating + * the default expression gives the final result and there's nothing + * more to do. + */ + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, scratch); + + /* + * Don't know address for that jump yet, compute once the whole + * JsonExpr is built. + */ + adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1); + } + + /* Step(s) to evaluate ON ERROR default expression */ + onerror_default_step_off = state->steps_len; + if (jexpr->on_error && jexpr->on_error->default_expr) + { + ExecInitExprRec((Expr *) jexpr->on_error->default_expr, + state, resv, resnull); + + /* See the comment above. */ + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, scratch); + adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1); + } + + /* + * Step to handle applying coercion to the JSON item returned by + * EEOP_JSONEXPR_PATH or to the ON EMPTY/ERROR expression as + * EEOP_JSONEXPR_BEHAVIOR decides. + */ + scratch->opcode = EEOP_JSONEXPR_COERCION; + scratch->d.jsonexpr_coercion.jsestate = jsestate; + coercion_step_off = state->steps_len; + ExprEvalPushStep(state, scratch); + + /* + * Adjust jump target addresses in various post-eval steps now that we have + * all the steps in place. + */ + + /* EEOP_JSONEXPR_SKIP */ + as = &state->steps[skip_step_off]; + as->d.jsonexpr_skip.jump_coercion = coercion_step_off; + as->d.jsonexpr_skip.jump_passing_args = passing_args_step_off; + + /* EEOP_JSONEXPR_BEHAVIOR */ + as = &state->steps[behavior_step_off]; + as->d.jsonexpr_behavior.jump_onerror_default = onerror_default_step_off; + as->d.jsonexpr_behavior.jump_onempty_default = onempty_default_step_off; + as->d.jsonexpr_behavior.jump_coercion = coercion_step_off; + as->d.jsonexpr_behavior.jump_skip_coercion = coercion_step_off + 1; + + /* EEOP_JSONEXPR_COERCION */ + as = &state->steps[coercion_step_off]; + as->d.jsonexpr_coercion.jump_coercion_error = behavior_step_off; + as->d.jsonexpr_coercion.jump_coercion_done = coercion_step_off + 1; + + /* + * EEOP_JUMP steps added after ON EMPTY and ON ERROR default expression + * should jump to the current step address. + */ + foreach(lc, adjust_jumps) + { + as = &state->steps[lfirst_int(lc)]; + as->d.jump.jumpdone = state->steps_len; + } + + /* + * Set if we must use a sub-transaction around path evaluation + * that can be aborted on error instead of aborting the parent + * query. + */ + jsestate->needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, true); + + /* + * Set if coercion, which runs separately from path evaluation and whose + * errors must be caught and handled per ON ERROR behavior, must use a + * sub-transaction. + */ + jsestate->coercionNeedSubtrans = !throwErrors; + + /* + * Set RETURNING type's input function used by ExecEvalJsonExprCoercion(). + */ + if (jexpr->omit_quotes || + (jexpr->result_coercion && jexpr->result_coercion->via_io)) + { + Oid typinput; + FmgrInfo *finfo; + + /* lookup the result type's input function */ + getTypeInputInfo(jexpr->returning->typid, &typinput, + &jsestate->input.typioparam); + finfo = palloc0(sizeof(FmgrInfo)); + fmgr_info(typinput, finfo); + jsestate->input.finfo = finfo; + } + + /* + * Initialize coercion expression(s). These can't be inlined into the + * parent ExprState, because of the need to use a sub-transaction around + * their evaluation. + */ + if (jexpr->result_coercion && jexpr->result_coercion->expr) + jsestate->result_coercion = + ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion, + state->parent, resv, resnull); + if (jexpr->coercions) + jsestate->coercions = + ExecInitExprWithCaseValue((Expr *) jexpr->coercions, + state->parent, resv, resnull); +} + +/* + * Push steps to evaluate a JsonItemCoercions, which contains the state for + * evaluating all possible coercions that may need to be applied to a JSON + * item coming from evaluating the parent JsonExpr. + */ +static void +ExecInitJsonItemCoercions(ExprEvalStep *scratch, JsonItemCoercions *coercions, + ExprState *state, Datum *resv, bool *resnull) +{ + JsonItemCoercionsState *jcstate = palloc0(sizeof(JsonItemCoercionsState)); + JsonCoercion **coercion; + JsonItemCoercionState *cstate; + ExprEvalStep *as; + int json_item_coercion_step_id; + List *adjust_jumps = NIL; + ListCell *lc; + + /* + * Push a step to read the JSON item value passed by the parent JsonExpr + * via a CaseTestExpr. + */ + scratch->opcode = EEOP_CASE_TESTVAL; + scratch->d.casetest.value = state->innermost_caseval; + scratch->d.casetest.isnull = state->innermost_casenull; + ExprEvalPushStep(state, scratch); + + /* Push the control step. */ + scratch->opcode = EEOP_JSON_ITEM_COERCION; + scratch->d.jsonexpr_item_coercion.jcstate = jcstate; + json_item_coercion_step_id = state->steps_len; + ExprEvalPushStep(state, scratch); + /* Will set jump_skip_coercion target address below. */ + + /* Now push the steps of individual coercion's expression, if needed. */ + for (cstate = &jcstate->null, + coercion = &coercions->null; + coercion <= &coercions->composite; + coercion++, cstate++) + { + cstate->coercion = *coercion; + if (cstate->coercion && cstate->coercion->expr) + { + Datum *save_innermost_caseval; + bool *save_innermost_isnull; + + cstate->jump_eval_expr = state->steps_len; + + /* Push step(s) to compute (*coercion)->expr. */ + save_innermost_caseval = state->innermost_caseval; + save_innermost_isnull = state->innermost_casenull; + + state->innermost_caseval = resv; + state->innermost_casenull = resnull; + + ExecInitExprRec((Expr *) cstate->coercion->expr, + state, resv, resnull); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_isnull; + } + else + cstate->jump_eval_expr = -1; + + /* Emit JUMP step to skip past other coercions' steps. */ + scratch->opcode = EEOP_JUMP; + + /* + * Remember JUMP step address to set the actual jump target addreess + * below. + */ + adjust_jumps = lappend_int(adjust_jumps, state->steps_len); + ExprEvalPushStep(state, scratch); + } + + /* + * Adjust the jump target address in the control step and all + * the jumps we added in the above loop to jump here. + */ + as = &state->steps[json_item_coercion_step_id]; + as->d.jsonexpr_item_coercion.jump_skip_item_coercion = state->steps_len; + foreach(lc, adjust_jumps) + { + int jump_step_id = lfirst_int(lc); + + as = &state->steps[jump_step_id]; + as->d.jump.jumpdone = state->steps_len; + } +} diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 636794ca6f..6648c2b9b8 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -158,6 +158,15 @@ static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, bool *changed); static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, ExprContext *econtext, bool checkisnull); +static Datum ExecEvalJsonBehavior(JsonBehavior *behavior, bool *is_null); +typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext, + Datum item, bool *resnull, void *p, bool *error); +static Datum ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op, + ExprContext *econtext, + Datum res, bool *resnull, + void *p, bool *error, bool subtrans); +static Datum ExecEvalJsonCoercion(ExprEvalStep *op, ExprContext *econtext, + Datum res, bool *isNull, void *p, bool *error); /* fast-path evaluation functions */ static Datum ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull); @@ -490,7 +499,11 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_SUBPLAN, &&CASE_EEOP_JSON_CONSTRUCTOR, &&CASE_EEOP_IS_JSON, - &&CASE_EEOP_JSONEXPR, + &&CASE_EEOP_JSONEXPR_SKIP, + &&CASE_EEOP_JSONEXPR_PATH, + &&CASE_EEOP_JSONEXPR_BEHAVIOR, + &&CASE_EEOP_JSONEXPR_COERCION, + &&CASE_EEOP_JSON_ITEM_COERCION, &&CASE_EEOP_AGG_STRICT_DESERIALIZE, &&CASE_EEOP_AGG_DESERIALIZE, &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS, @@ -1841,13 +1854,37 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } - EEO_CASE(EEOP_JSONEXPR) + EEO_CASE(EEOP_JSONEXPR_PATH) { /* too complex for an inline implementation */ ExecEvalJson(state, op, econtext); EEO_NEXT(); } + EEO_CASE(EEOP_JSONEXPR_SKIP) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalJsonExprSkip(state, op)); + } + + EEO_CASE(EEOP_JSONEXPR_BEHAVIOR) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalJsonExprBehavior(state, op)); + } + + EEO_CASE(EEOP_JSONEXPR_COERCION) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalJsonExprCoercion(state, op, econtext)); + } + + EEO_CASE(EEOP_JSON_ITEM_COERCION) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalJsonExprItemCoercion(state, op)); + } + EEO_CASE(EEOP_LAST) { /* unreachable */ @@ -4704,8 +4741,7 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, * Evaluate a JSON error/empty behavior result. */ static Datum -ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, - ExprState *default_estate, bool *is_null) +ExecEvalJsonBehavior(JsonBehavior *behavior, bool *is_null) { *is_null = false; @@ -4730,7 +4766,9 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, return (Datum) 0; case JSON_BEHAVIOR_DEFAULT: - return ExecEvalExpr(default_estate, econtext, is_null); + /* Always handled in the caller. */ + Assert(false); + return (Datum) 0; default: elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype); @@ -4742,17 +4780,18 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, * Evaluate a coercion of a JSON item to the target type. */ static Datum -ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, - Datum res, bool *isNull, void *p, bool *error) +ExecEvalJsonCoercion(ExprEvalStep *op, ExprContext *econtext, + Datum res, bool *isNull, void *p, bool *error) { - ExprState *estate = p; - JsonExprState *jsestate; + JsonExprState *jsestate = op->d.jsonexpr_coercion.jsestate; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + ExprState *estate = post_eval->coercion; + + post_eval->coercion_error = false; if (estate) /* coerce using specified expression */ return ExecEvalExpr(estate, econtext, isNull); - jsestate = op->d.jsonexpr.jsestate; - if (jsestate->jsexpr->op != JSON_EXISTS_OP) { JsonCoercion *coercion = jsestate->jsexpr->result_coercion; @@ -4766,7 +4805,7 @@ ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, /* strip quotes and call typinput function */ char *str = *isNull ? NULL : JsonbUnquote(jb); - return InputFunctionCall(&jsestate->input.func, str, + return InputFunctionCall(jsestate->input.finfo, str, jsestate->input.typioparam, jexpr->returning->typmod); } @@ -4774,73 +4813,85 @@ ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, return json_populate_type(res, JSONBOID, jexpr->returning->typid, jexpr->returning->typmod, - &jsestate->cache, + &post_eval->cache, econtext->ecxt_per_query_memory, isNull); } - if (jsestate->result_expr) + /* Coerce with jexpr->result_coercion if there's one */ + if (jsestate->result_coercion) { - jsestate->res_expr->value = res; - jsestate->res_expr->isnull = *isNull; - - res = ExecEvalExpr(jsestate->result_expr, econtext, isNull); + *op->resvalue = res; + *op->resnull = *isNull; + return ExecEvalExpr(jsestate->result_coercion, econtext, isNull); } return res; } /* - * Evaluate a JSON path variable caching computed value. + * Return a coercion among those given in 'coercions' for given + * JSON item. */ -int -EvalJsonPathVar(void *cxt, char *varName, int varNameLen, - JsonbValue *val, JsonbValue *baseObject) +JsonCoercion * +ExecGetJsonItemCoercion(JsonbValue *item, JsonItemCoercions *coercions) { - JsonPathVariableEvalContext *var = NULL; - List *vars = cxt; - ListCell *lc; - int id = 1; + if (item->type == jbvBinary && + JsonContainerIsScalar(item->val.binary.data)) + { + JsonbValue buf; + bool res PG_USED_FOR_ASSERTS_ONLY; - if (!varName) - return list_length(vars); + res = JsonbExtractScalar(item->val.binary.data, &buf); + item = &buf; + Assert(res); + } - foreach(lc, vars) + /* get coercion state reference and datum of the corresponding SQL type */ + switch (item->type) { - var = lfirst(lc); - - if (!strncmp(var->name, varName, varNameLen)) - break; + case jbvNull: + return coercions->null; - var = NULL; - id++; - } + case jbvString: + return coercions->string; - if (!var) - return -1; + case jbvNumeric: + return coercions->numeric; - if (!var->evaluated) - { - MemoryContext oldcxt = var->mcxt ? - MemoryContextSwitchTo(var->mcxt) : NULL; + case jbvBool: + return coercions->boolean; - var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull); - var->evaluated = true; + case jbvDatetime: + switch (item->val.datetime.typid) + { + case DATEOID: + return coercions->date; + case TIMEOID: + return coercions->time; + case TIMETZOID: + return coercions->timetz; + case TIMESTAMPOID: + return coercions->timestamp; + case TIMESTAMPTZOID: + return coercions->timestamptz; + default: + elog(ERROR, "unexpected jsonb datetime type oid %u", + item->val.datetime.typid); + } + break; - if (oldcxt) - MemoryContextSwitchTo(oldcxt); - } + case jbvArray: + case jbvObject: + case jbvBinary: + return coercions->composite; - if (var->isnull) - { - val->type = jbvNull; - return 0; + default: + elog(ERROR, "unexpected jsonb value type %d", item->type); } - JsonItemFromDatum(var->value, var->typid, var->typmod, val); - - *baseObject = *val; - return id; + Assert(false); + return NULL; } /* @@ -4848,14 +4899,14 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen, * corresponding SQL type and a pointer to the coercion state. */ Datum -ExecPrepareJsonItemCoercion(JsonbValue *item, - JsonReturning *returning, - struct JsonCoercionsState *coercions, - struct JsonCoercionState **pcoercion) +ExecPrepareJsonItemCoercion(Datum itemval, + JsonItemCoercionsState *coercions, + JsonItemCoercionState **pcoercion) { - struct JsonCoercionState *coercion; + JsonbValue *item = DatumGetJsonbValueP(itemval); + JsonItemCoercionState *coercion; Datum res; - JsonbValue buf; + JsonbValue buf; if (item->type == jbvBinary && JsonContainerIsScalar(item->val.binary.data)) @@ -4934,9 +4985,6 @@ ExecPrepareJsonItemCoercion(JsonbValue *item, return res; } -typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext, - Datum item, bool *resnull, void *p, bool *error); - static Datum ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op, ExprContext *econtext, @@ -5007,7 +5055,6 @@ typedef struct { JsonPath *path; bool *error; - bool coercionInSubtrans; } ExecEvalJsonExprContext; static Datum @@ -5018,16 +5065,17 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, ExecEvalJsonExprContext *cxt = pcxt; JsonPath *path = cxt->path; JsonExprState *jsestate = op->d.jsonexpr.jsestate; + JsonExprPreEvalState *pre_eval = &jsestate->pre_eval; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; JsonExpr *jexpr = jsestate->jsexpr; - ExprState *estate = NULL; - bool empty = false; + bool *empty = &post_eval->empty; Datum res = (Datum) 0; switch (jexpr->op) { case JSON_QUERY_OP: - res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error, - jsestate->args); + res = JsonPathQuery(item, path, jexpr->wrapper, empty, error, + pre_eval->args); if (error && *error) { *resnull = true; @@ -5038,17 +5086,20 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, case JSON_VALUE_OP: { - struct JsonCoercionState *jcstate; - JsonbValue *jbv = JsonPathValue(item, path, &empty, error, - jsestate->args); + JsonCoercion *coercion; + JsonbValue *jbv = JsonPathValue(item, path, empty, error, + pre_eval->args); if (error && *error) + { + *resnull = true; return (Datum) 0; + } if (!jbv) /* NULL or empty */ break; - Assert(!empty); + Assert(!*empty); *resnull = false; @@ -5061,19 +5112,17 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, break; } - /* Use coercion from SQL/JSON item type to the output type */ - res = ExecPrepareJsonItemCoercion(jbv, - jsestate->jsexpr->returning, - &jsestate->coercions, - &jcstate); - - if (jcstate->coercion && - (jcstate->coercion->via_io || - jcstate->coercion->via_populate)) + /* + * Error out if no cast exists to coerce SQL/JSON item to the + * the output type + */ + coercion = ExecGetJsonItemCoercion(jbv, jsestate->jsexpr->coercions); + if (coercion && (coercion->via_io || coercion->via_populate)) { if (error) { *error = true; + *resnull = true; return (Datum) 0; } @@ -5085,32 +5134,42 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE), errmsg("SQL/JSON item cannot be cast to target type"))); } - else if (!jcstate->estate) - return res; /* no coercion */ - - /* coerce using specific expression */ - estate = jcstate->estate; - jsestate->coercion_expr->value = res; - jsestate->coercion_expr->isnull = *resnull; + else + { + /* + * Coerce using a specific coercion (JsonItemCoercion). + * Note we are passing the JsonbValue as is, without + * converting it to Jsonb, because + * ExecPrepareJsonItemCoercion() expects the JSON item + * to be in that format. + */ + res = JsonbValuePGetDatum(jbv); + post_eval->coercion = jsestate->coercions; + } break; } case JSON_EXISTS_OP: { bool exists = JsonPathExists(item, path, - jsestate->args, + pre_eval->args, error); *resnull = error && *error; res = BoolGetDatum(exists); - if (!jsestate->result_expr) + if (jexpr->result_coercion == NULL) + { + /* No coercion needed */ + post_eval->coercion_done = true; return res; - - /* coerce using result expression */ - estate = jsestate->result_expr; - jsestate->res_expr->value = res; - jsestate->res_expr->isnull = *resnull; + } + else + { + /* coerce using result expression */ + Assert(jsestate->result_coercion); + post_eval->coercion = jsestate->result_coercion; + } break; } @@ -5123,7 +5182,11 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, return (Datum) 0; } - if (empty) + /* + * If the ON EMPTY behavior is to cause an error, do so here. Other + * behaviors will be handled by the caller. + */ + if (*empty) { Assert(jexpr->on_empty); /* it is not JSON_EXISTS */ @@ -5139,29 +5202,13 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, (errcode(ERRCODE_NO_SQL_JSON_ITEM), errmsg("no SQL/JSON item"))); } - - if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT) - - /* - * Execute DEFAULT expression as a coercion expression, because - * its result is already coerced to the target type. - */ - estate = jsestate->default_on_empty; - else - /* Execute ON EMPTY behavior */ - res = ExecEvalJsonBehavior(econtext, jexpr->on_empty, - jsestate->default_on_empty, - resnull); } - return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext, - res, resnull, estate, error, - cxt->coercionInSubtrans); + return res; } bool -ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, - struct JsonCoercionsState *coercions) +ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, bool consider_coerce) { if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR) return false; @@ -5169,12 +5216,155 @@ ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion) return false; - if (!coercions) + if (!consider_coerce) return true; return false; } +/* Skip calling ExecEvalJson() on a JsonExpr? */ +int +ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate; + + /* + * Skip if either of the input expressions has turned out to be NULL, + * though do execute domain checks for NULLs, which are handled by the + * coercion step. + */ + if (jsestate->pre_eval.formatted_expr.isnull || + jsestate->pre_eval.pathspec.isnull) + { + *op->resvalue = 0; + *op->resnull = true; + Assert(jsestate->post_eval.coercion == NULL); + /* A signal to the coercion step to not use a sub-trancaction. */ + jsestate->post_eval.coercion_error = true; + return op->d.jsonexpr_skip.jump_coercion; + } + + /* + * Go evaluate the PASSING args if any and subsequently JSON path + * itself. + */ + return op->d.jsonexpr_skip.jump_passing_args; +} + +int +ExecEvalJsonExprBehavior(ExprState *state, ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr_behavior.jsestate; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + JsonBehavior *behavior = NULL; + int jump_to; + bool error = (post_eval->error || post_eval->coercion_error); + + /* + * Set if the coercion step, if will run afterwards, should use a sub- + * transaction to be aborted on error instead of throwing the error. + * Don't use a sub-transaction if coercing the ON ERROR expression, + * though. + * + * XXX - doesn't that violate ON ERROR behavior? Actually, that is + * how it behaved even before this expression eval logic rewrite! + */ + post_eval->coercion_use_subtrans = + (jsestate->coercionNeedSubtrans && !error); + + /* + * If no error or the JSON item is not empty, directly go to the coercion + * step to coerce the item as is, or skip the coercion step if the item is + * already coerced by ExecEvalJson(). + */ + if (!error && !post_eval->empty) + return !post_eval->coercion_done ? + op->d.jsonexpr_behavior.jump_coercion : + op->d.jsonexpr_behavior.jump_skip_coercion; + + if (error) + { + behavior = jsestate->jsexpr->on_error; + jump_to = op->d.jsonexpr_behavior.jump_onerror_default; + } + else if (post_eval->empty) + { + behavior = jsestate->jsexpr->on_empty; + jump_to = op->d.jsonexpr_behavior.jump_onempty_default; + } + + Assert(behavior); + + /* + * If a non-default behavior is specified, get the appropriate value and go + * to the coercion step. + */ + if (behavior->btype != JSON_BEHAVIOR_DEFAULT) + { + *op->resvalue = ExecEvalJsonBehavior(behavior, op->resnull); + + if (post_eval->coercion_error) + { + post_eval->coercion_done = false; + post_eval->coercion = NULL; + } + + jump_to = op->d.jsonexpr_behavior.jump_coercion; + } + + /* + * Else evaluate the default ON ERROR or ON EMPTY expression, with no + * coercion needed afterwards given that the expression is already + * coerced appropriately in the parser. + */ + + Assert(jump_to >= 0); + return jump_to; +} + +/* + * Apply coercion to a JSON item using either JsonExpr.result_coercion or one + * of JsonExpr.coercions, possibly using a sub-transaction. + */ +int +ExecEvalJsonExprCoercion(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + JsonExprState *jsestate = op->d.jsonexpr_coercion.jsestate; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + + *op->resvalue = + ExecEvalJsonExprSubtrans(ExecEvalJsonCoercion, op, econtext, + *op->resvalue, op->resnull, NULL, + &post_eval->coercion_error, + post_eval->coercion_use_subtrans); + if (post_eval->coercion_error) + return op->d.jsonexpr_coercion.jump_coercion_error; + + return op->d.jsonexpr_coercion.jump_coercion_done; +} + +/* + * Apply a coercion to a JSON item, choosing one among those present in + * JsonItemCoercions. + */ +int +ExecEvalJsonExprItemCoercion(ExprState *state, ExprEvalStep *op) +{ + JsonItemCoercionsState *jcstate = op->d.jsonexpr_item_coercion.jcstate; + JsonItemCoercionState *cstate = NULL; + + *op->resvalue = + ExecPrepareJsonItemCoercion(*op->resvalue, jcstate, &cstate); + if (cstate->coercion && cstate->coercion->expr) + { + Assert(cstate->jump_eval_expr >= 0); + return cstate->jump_eval_expr; + } + + /* Skip over all of the steps added for this JsonItemCoercions. */ + return op->d.jsonexpr_item_coercion.jump_skip_item_coercion; +} + /* ---------------------------------------------------------------- * ExecEvalJson * ---------------------------------------------------------------- @@ -5184,64 +5374,30 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { ExecEvalJsonExprContext cxt; JsonExprState *jsestate = op->d.jsonexpr.jsestate; + JsonExprPreEvalState *pre_eval = &jsestate->pre_eval; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; JsonExpr *jexpr = jsestate->jsexpr; Datum item; Datum res = (Datum) 0; JsonPath *path; - ListCell *lc; - bool error = false; - bool needSubtrans; bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR; *op->resnull = true; /* until we get a result */ *op->resvalue = (Datum) 0; - if (jsestate->formatted_expr->isnull || jsestate->pathspec->isnull) - { - /* execute domain checks for NULLs */ - (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull, - NULL, NULL); + item = pre_eval->formatted_expr.value; + path = DatumGetJsonPathP(pre_eval->pathspec.value); - Assert(*op->resnull); - return; - } - - item = jsestate->formatted_expr->value; - path = DatumGetJsonPathP(jsestate->pathspec->value); - - /* reset JSON path variable contexts */ - foreach(lc, jsestate->args) - { - JsonPathVariableEvalContext *var = lfirst(lc); - - var->econtext = econtext; - var->evaluated = false; - } - - needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions); + /* Reset JsonExprPostEvalState for this evaluation. */ + memset(post_eval, 0, sizeof(*post_eval)); cxt.path = path; - cxt.error = throwErrors ? NULL : &error; - cxt.coercionInSubtrans = !needSubtrans && !throwErrors; - Assert(!needSubtrans || cxt.error); + cxt.error = throwErrors ? NULL : &post_eval->error; + Assert(!jsestate->needSubtrans || cxt.error); res = ExecEvalJsonExprSubtrans(ExecEvalJsonExpr, op, econtext, item, op->resnull, &cxt, cxt.error, - needSubtrans); - - if (error) - { - /* Execute ON ERROR behavior */ - res = ExecEvalJsonBehavior(econtext, jexpr->on_error, - jsestate->default_on_error, - op->resnull); - - /* result is already coerced in DEFAULT behavior case */ - if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT) - res = ExecEvalJsonExprCoercion(op, econtext, res, - op->resnull, - NULL, NULL); - } + jsestate->needSubtrans); *op->resvalue = res; } diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index bd3965143d..2637f9c41d 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2407,12 +2407,230 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; - case EEOP_JSONEXPR: + case EEOP_JSONEXPR_PATH: build_EvalXFunc(b, mod, "ExecEvalJson", v_state, op, v_econtext); LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_JSONEXPR_SKIP: + { + LLVMValueRef params[2]; + LLVMValueRef v_ret; + + /* + * Call ExecEvalJsonExprSkip() to decide if JSON path + * evaluation can be skipped. This returns the step + * address to jump to. + */ + params[0] = v_state; + params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + v_ret = LLVMBuildCall(b, + llvm_pg_func(mod, "ExecEvalJsonExprSkip"), + params, lengthof(params), ""); + + /* + * Jump to coercion step if the returned address is the + * same as jsonexpr_skip.jump_coercion, which signifies + * skipping of JSON path evaluation, else to the next step + * which must point to the steps to evaluate PASSING args, + * if any, or to the JSON path evaluation. + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_skip.jump_coercion), + ""), + opblocks[op->d.jsonexpr_skip.jump_coercion], + opblocks[opno + 1]); + break; + } + + case EEOP_JSONEXPR_BEHAVIOR: + { + LLVMValueRef params[2]; + LLVMValueRef v_ret; + LLVMBasicBlockRef b_jump_skip_coercion, + b_jump_onerror_default; + + /* + * Call ExecEvalJsonExprBehavior() to decide if ON EMPTY or + * ON ERROR behavior must be invoked depending on what JSON + * path evaluation returned. This returns the step address + * to jump to. + */ + params[0] = v_state; + params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + v_ret = LLVMBuildCall(b, + llvm_pg_func(mod, "ExecEvalJsonExprBehavior"), + params, lengthof(params), ""); + + b_jump_skip_coercion = + l_bb_before_v(opblocks[opno + 1], + "op.%d.jsonexpr_behavior_jump_skip_coercion", opno); + b_jump_onerror_default = + l_bb_before_v(opblocks[opno + 1], + "op.%d.jsonexpr_behavior_jump_onerror_default", opno); + + /* + * Jump to coercion step if the returned address is the same as + * jsonexpr_behavior.jump_coercion, else to the block that checks + * whether to skip coercion. + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_behavior.jump_coercion), + ""), + opblocks[op->d.jsonexpr_behavior.jump_coercion], + b_jump_skip_coercion); + + /* + * Block that checks whether to skip coercion. + * + * Jump to skip coercion if the returned address is the + * same as jsonexpr_behavior.jump_skip_coercion, else to + * the block that checks whether the ON ERROR or the ON + * EMPTY default expression must be evaluated instead. + */ + LLVMPositionBuilderAtEnd(b, b_jump_skip_coercion); + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_behavior.jump_skip_coercion), + ""), + opblocks[op->d.jsonexpr_behavior.jump_skip_coercion], + b_jump_onerror_default); + + /* + * Block that checks whether to evaluate the ON ERROR + * default expression. + * + * Jump to evaluate the ON ERROR default expression if the + * returned address is the same as + * jsonexpr_behavior.jump_onerror_default, else jump to + * evaluate the ON EMPTY default expression. + */ + LLVMPositionBuilderAtEnd(b, b_jump_onerror_default); + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_behavior.jump_onerror_default), + ""), + opblocks[op->d.jsonexpr_behavior.jump_onerror_default], + opblocks[op->d.jsonexpr_behavior.jump_onempty_default]); + break; + } + case EEOP_JSONEXPR_COERCION: + { + LLVMValueRef params[3]; + LLVMValueRef v_ret; + + /* + * Call ExecEvalJsonExprCoercion() to evaluate appropriate + * coercion. This will return the step address to jump + * to. + */ + params[0] = v_state; + params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + params[2] = v_econtext; + v_ret = LLVMBuildCall(b, + llvm_pg_func(mod, "ExecEvalJsonExprCoercion"), + params, lengthof(params), ""); + + /* + * Jump to handle a coercion error if the returned address + * is the same as jsonexpr_coercion.jump_coercion_error, + * else to the step after coercion (coercion done!). + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_coercion.jump_coercion_error), + ""), + opblocks[op->d.jsonexpr_coercion.jump_coercion_error], + opblocks[op->d.jsonexpr_coercion.jump_coercion_done]); + break; + } + case EEOP_JSON_ITEM_COERCION: + { + JsonItemCoercionsState *jcstate = op->d.jsonexpr_item_coercion.jcstate; + JsonItemCoercionState *cstate; + LLVMValueRef params[2]; + LLVMValueRef v_ret; + int n_coercions = (int) (&jcstate->composite - &jcstate->null) + 1; + int i; + LLVMBasicBlockRef *b_coercions; + + /* + * Call ExecEvalJsonExprItemCoercion() to inspect the JSON + * item obtained by path evaluation and pick the coercion + * to apply accordingly. That will set + * jsonexpr_item_coercion.jumpdone to an address as + * described below. + */ + params[0] = v_state; + params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + v_ret = LLVMBuildCall(b, + llvm_pg_func(mod, "ExecEvalJsonExprItemCoercion"), + params, lengthof(params), ""); + + /* + * Will create a block for each coercion below to check + * whether to evaluate the coercion's expression if there's + * one or to skip to the end if not. + */ + b_coercions = palloc((n_coercions + 1) * sizeof(LLVMBasicBlockRef)); + for (i = 0; i < n_coercions + 1; i++) + b_coercions[i] = + l_bb_before_v(opblocks[opno + 1], + "op.%d.json_item_coercion.%d", + opno, i); + + /* Jump to check first coercion */ + LLVMBuildBr(b, b_coercions[0]); + + /* Add conditional branches for individual coercion's expressions */ + for (cstate = &jcstate->null, i = 0; + cstate <= &jcstate->composite; + cstate++, i++) + { + /* Block for this coercion */ + LLVMPositionBuilderAtEnd(b, b_coercions[i]); + + /* + * Jump to evaluate the coercion's expression if the + * address returned by ExecEvalJsonExprItemCoercion() + * is the same as cstate->jump_eval_expr, if it is + * valid (skip to end if not), else to the next + * coercion's block. + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(cstate->jump_eval_expr), + ""), + cstate->jump_eval_expr >= 0 ? + opblocks[cstate->jump_eval_expr] : + opblocks[op->d.jsonexpr_item_coercion.jump_skip_item_coercion], + b_coercions[i + 1]); + } + + /* + * A placeholder block that the last coercion's block might + * jump to, which unconditionally jumps to end of + * coercions. + */ + LLVMPositionBuilderAtEnd(b, b_coercions[i]); + LLVMBuildBr(b, opblocks[op->d.jsonexpr_item_coercion.jump_skip_item_coercion]); + break; + } case EEOP_LAST: Assert(false); break; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 373471ad27..42c0e421d3 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -135,6 +135,10 @@ void *referenced_functions[] = ExecEvalXmlExpr, ExecEvalJsonConstructor, ExecEvalJsonIsPredicate, + ExecEvalJsonExprSkip, + ExecEvalJsonExprBehavior, + ExecEvalJsonExprCoercion, + ExecEvalJsonExprItemCoercion, ExecEvalJson, MakeExpandedObjectReadOnlyInternal, slot_getmissingattrs, diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 533df86ff7..12ed0373d0 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -901,7 +901,11 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context) { JsonExpr *jsexpr = (JsonExpr *) node; - if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL)) + /* + * XXX - don't really know why it makes sense to ignore the coercion + * part here. + */ + if (ExecEvalJsonNeedsSubTransaction(jsexpr, false)) { context->max_hazard = PROPARALLEL_UNSAFE; return true; diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 10c7e64aab..c3fc77cf51 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -286,6 +286,8 @@ static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueList *found, JsonPathBool res); static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *value); +static int EvalJsonPathVar(void *vars, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject); static void getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, JsonbValue *value); static int getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, @@ -2172,6 +2174,72 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, } } +/* + * Evaluate a JSON path variable caching computed value. + */ +int +EvalJsonPathVar(void *cxt, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject) +{ + JsonPathVariableEvalContext *var = NULL; + List *vars = cxt; + ListCell *lc; + int id = 1; + + if (!varName) + return list_length(vars); + + foreach(lc, vars) + { + var = lfirst(lc); + + if (!strncmp(var->name, varName, varNameLen)) + break; + + var = NULL; + id++; + } + + if (!var) + return -1; + + /* + * When belonging to a JsonExpr, path variables are computed with the + * JsonExpr's ExprState (var->estate is NULL), so don't need to be computed + * here. In some other cases, such as when the path variables belonging + * to a JsonTable instead, those variables must be evaluated on their own, + * without the enclosing JsonExpr itself needing to be evaluated, so must + * be handled here. + */ + if (var->estate && !var->evaluated) + { + MemoryContext oldcxt = var->mcxt ? + MemoryContextSwitchTo(var->mcxt) : NULL; + + Assert(var->econtext != NULL); + var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull); + var->evaluated = true; + + if (oldcxt) + MemoryContextSwitchTo(oldcxt); + } + else + { + Assert(var->evaluated); + } + + if (var->isnull) + { + val->type = jbvNull; + return 0; + } + + JsonItemFromDatum(var->value, var->typid, var->typmod, val); + + *baseObject = *val; + return id; +} + /* * Get the value of variable passed to jsonpath executor */ diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index c8ef917ffe..64820b4cf9 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -244,7 +244,11 @@ typedef enum ExprEvalOp EEOP_SUBPLAN, EEOP_JSON_CONSTRUCTOR, EEOP_IS_JSON, - EEOP_JSONEXPR, + EEOP_JSONEXPR_SKIP, + EEOP_JSONEXPR_PATH, + EEOP_JSONEXPR_BEHAVIOR, + EEOP_JSONEXPR_COERCION, + EEOP_JSON_ITEM_COERCION, /* aggregation related nodes */ EEOP_AGG_STRICT_DESERIALIZE, @@ -692,12 +696,53 @@ typedef struct ExprEvalStep JsonIsPredicate *pred; /* original expression node */ } is_json; - /* for EEOP_JSONEXPR */ + /* for EEOP_JSONEXPR_PATH */ struct { struct JsonExprState *jsestate; } jsonexpr; + /* for EEOP_JSONEXPR_SKIP */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + + /* See ExecEvalJsonExprSkip() */ + int jump_coercion; + int jump_passing_args; + } jsonexpr_skip; + + /* for EEOP_JSONEXPR_BEHAVIOR */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + + /* See ExecEvalJsonExprBehavior() */ + int jump_onerror_default; + int jump_onempty_default; + int jump_coercion; + int jump_skip_coercion; + } jsonexpr_behavior; + + /* for EEOP_JSONEXPR_COERCION */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + + /* See ExecEvalJsonExprCoercion() */ + int jump_coercion_error; + int jump_coercion_done; + } jsonexpr_coercion; + + struct + { + /* See ExecEvalJsonExprItemCoercion() */ + struct JsonItemCoercionsState *jcstate; + int jump_skip_item_coercion; + } jsonexpr_item_coercion; } d; } ExprEvalStep; @@ -757,49 +802,117 @@ typedef struct JsonConstructorExprState int nargs; } JsonConstructorExprState; +/* + * Information computed before evaluating a JsonExpr expression. + */ +typedef struct JsonExprPreEvalState +{ + /* value/isnull for JsonExpr.formatted_expr */ + NullableDatum formatted_expr; + + /* value/isnull for JsonExpr.pathspec */ + NullableDatum pathspec; + + /* JsonPathVariableEvalContext entries for JsonExpr.passing_values */ + List *args; +} JsonExprPreEvalState; + +/* + * State for some post-JsonExpr-evaluation processing steps that gets filled + * in JsonExpr evaluation. + */ +typedef struct JsonExprPostEvalState +{ + /* Is JSON item empty? */ + bool empty; + + /* Did JSON item evaluation cause an error? */ + bool error; + + /* Did coercion evaluation cause an error? */ + bool coercion_error; + + /* Has the result been coerced properly? */ + bool coercion_done; + + /* Use a sub-transaction when evaluating the coercion */ + bool coercion_use_subtrans; + + /* Cache for json_populate_type() called for coercion in some cases */ + void *cache; + + /* + * State for evaluating a JSON item coercion. Points to either + * JsonExprState.coercions or JsonExprState.result_coercion; might be + * set to NULL if it is to be left for ExecEvalJsonCoercion() to decide + * how to perform the coercion. + */ + ExprState *coercion; +} JsonExprPostEvalState; + /* EEOP_JSONEXPR state, too big to inline */ typedef struct JsonExprState { JsonExpr *jsexpr; /* original expression node */ + JsonExprPreEvalState pre_eval; + JsonExprPostEvalState post_eval; + + /* + * Should use a sub-transaction for path evaluation and subsequent + * coercion evaluation, if any? + */ + bool needSubtrans; + bool coercionNeedSubtrans; + struct { - FmgrInfo func; /* typinput function for output type */ + FmgrInfo *finfo; /* typinput function for output type */ Oid typioparam; } input; /* I/O info for output type */ - NullableDatum - *formatted_expr, /* formatted context item value */ - *res_expr, /* result item */ - *coercion_expr, /* input for JSON item coercion */ - *pathspec; /* path specification value */ + /* + * Either of the following two is used by ExecEvalJsonCoercion() to apply + * coercion to the final result if needed. + */ + ExprState *result_coercion; + ExprState *coercions; +} JsonExprState; - ExprState *result_expr; /* coerced to output type */ - ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */ - ExprState *default_on_error; /* ON ERROR DEFAULT expression */ - List *args; /* passing arguments */ +/* + * State for a given member of JsonItemCoercions. + */ +typedef struct JsonItemCoercionState +{ + /* Expression used to evaluate the coercion */ + JsonCoercion *coercion; - void *cache; /* cache for json_populate_type() */ + /* ExprEvalStep to compute this coercion's expression */ + int jump_eval_expr; +} JsonItemCoercionState; - struct JsonCoercionsState - { - struct JsonCoercionState - { - JsonCoercion *coercion; /* coercion expression */ - ExprState *estate; /* coercion expression state */ - } null, - string, - numeric , - boolean, - date, - time, - timetz, - timestamp, - timestamptz, - composite; - } coercions; /* states for coercion from SQL/JSON item - * types directly to the output type */ -} JsonExprState; +/* + * State for evaluating the coercion for a given JSON item using one of + * the following coercions. + * + * Note that while ExecInitExprRec() for JsonItemCoercions will initialize + * ExprEvalSteps for all of the members that need it, only one will get run + * during a given evaluation of the enclosing JsonExpr depending on the type + * of the result JSON item. + */ +typedef struct JsonItemCoercionsState +{ + JsonItemCoercionState null; + JsonItemCoercionState string; + JsonItemCoercionState numeric; + JsonItemCoercionState boolean; + JsonItemCoercionState date; + JsonItemCoercionState time; + JsonItemCoercionState timetz; + JsonItemCoercionState timestamp; + JsonItemCoercionState timestamptz; + JsonItemCoercionState composite; +} JsonItemCoercionsState; /* functions in execExpr.c */ extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s); @@ -860,14 +973,18 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern int ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op); +extern int ExecEvalJsonExprBehavior(ExprState *state, ExprEvalStep *op); +extern int ExecEvalJsonExprCoercion(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern int ExecEvalJsonExprItemCoercion(ExprState *state, ExprEvalStep *op); extern void ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext); -extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item, - JsonReturning *returning, - struct JsonCoercionsState *coercions, - struct JsonCoercionState **pjcstate); -extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, - struct JsonCoercionsState *); +JsonCoercion *ExecGetJsonItemCoercion(struct JsonbValue *item, JsonItemCoercions *coercions); +extern Datum ExecPrepareJsonItemCoercion(Datum itemval, + JsonItemCoercionsState *coercions, + JsonItemCoercionState **pjcstate); +extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, bool consider_coerce); extern Datum ExecEvalExprPassingCaseValue(ExprState *estate, ExprContext *econtext, bool *isnull, Datum caseval_datum, diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index bae466b523..6bdd9f5121 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -69,8 +69,10 @@ typedef enum /* Convenience macros */ #define DatumGetJsonbP(d) ((Jsonb *) PG_DETOAST_DATUM(d)) +#define DatumGetJsonbValueP(d) ((JsonbValue *) DatumGetPointer(d)) #define DatumGetJsonbPCopy(d) ((Jsonb *) PG_DETOAST_DATUM_COPY(d)) #define JsonbPGetDatum(p) PointerGetDatum(p) +#define JsonbValuePGetDatum(p) PointerGetDatum(p) #define PG_GETARG_JSONB_P(x) DatumGetJsonbP(PG_GETARG_DATUM(x)) #define PG_GETARG_JSONB_P_COPY(x) DatumGetJsonbPCopy(PG_GETARG_DATUM(x)) #define PG_RETURN_JSONB_P(x) PG_RETURN_POINTER(x) diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 8e79b8dc9f..fa1d3eae6c 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -280,9 +280,6 @@ extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, extern JsonbValue *JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars); -extern int EvalJsonPathVar(void *vars, char *varName, int varNameLen, - JsonbValue *val, JsonbValue *baseObject); - extern PGDLLIMPORT const TableFuncRoutine JsonbTableRoutine; #endif -- 2.35.3