From c0d4ead7c31919ebb9147275b27685ca1f556698 Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Wed, 22 Jun 2022 12:00:47 -0400 Subject: [PATCH v3] 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. TODO: update llvm_compile_expr() --- src/backend/executor/execExpr.c | 377 ++++++++++++++++++---- src/backend/executor/execExprInterp.c | 431 ++++++++++++++++++-------- src/backend/jit/llvm/llvmjit_expr.c | 31 +- src/backend/jit/llvm/llvmjit_types.c | 1 + src/backend/optimizer/util/clauses.c | 6 +- src/include/executor/execExpr.h | 176 ++++++++--- src/include/utils/jsonb.h | 2 + 7 files changed, 792 insertions(+), 232 deletions(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index c8d7145fe3..d7cda5b850 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2558,113 +2558,356 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_JsonCoercion: + { + JsonCoercion *coercion = castNode(JsonCoercion, node); + Datum *save_innermost_caseval; + bool *save_innermost_isnull; + + /* + * Only ever get here for JsonExpr.result_expression and the + * caller should have checked this. + */ + Assert(coercion->expr); + + /* + * First 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); + + /* 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 *) coercion->expr, state, resv, resnull); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_isnull; + + break; + } + case T_JsonExpr: { JsonExpr *jexpr = castNode(JsonExpr, node); JsonExprState *jsestate = palloc0(sizeof(JsonExprState)); + JsonExprPreEvalState *pre_eval = &jsestate->pre_eval; ListCell *argexprlc; ListCell *argnamelc; + int skip_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 "main" JSON path evaluation. + */ + 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); - scratch.opcode = EEOP_JSONEXPR; + /* + * However, before pushing steps for PASSING args, push a step + * that will decide whether to skip evaluating the args and + * JSON path evaluation 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); + + jsestate->pre_eval.args = NIL; + 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); + + /* + * Not necessary when being evaluated for a JsonExpr, like + * in this case. + */ + 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); + } + + /* Now push the step for the actual JSON path evaluation. */ + scratch.opcode = EEOP_JSONEXPR_PATH; scratch.d.jsonexpr.jsestate = jsestate; + ExprEvalPushStep(state, &scratch); - jsestate->jsexpr = jexpr; + /* + * Now push steps to control the expressions evaluated based + * on the result of JSON path evaluation. + */ - jsestate->formatted_expr = - palloc(sizeof(*jsestate->formatted_expr)); + /* + * Step to handle ON ERROR and ON EMPTY behavior correctly. + */ + scratch.opcode = EEOP_JSONEXPR_BEHAVIOR; + scratch.d.jsonexpr_behavior.jsestate = jsestate; + behavior_step_off = state->steps_len; + ExprEvalPushStep(state, &scratch); - ExecInitExprRec((Expr *) jexpr->formatted_expr, state, - &jsestate->formatted_expr->value, - &jsestate->formatted_expr->isnull); + onempty_default_step_off = state->steps_len; + if (jexpr->on_empty) + { + /* Push step(s) for the default ON EMPTY expression. */ + if (jexpr->on_empty->default_expr) + { + ExecInitExprRec((Expr *) jexpr->on_empty->default_expr, + state, resv, resnull); - jsestate->pathspec = - palloc(sizeof(*jsestate->pathspec)); + /* Emit JUMP step to jump to end of JsonExpr code */ + scratch.opcode = EEOP_JUMP; + scratch.d.jump.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, &scratch); - ExecInitExprRec((Expr *) jexpr->path_spec, state, - &jsestate->pathspec->value, - &jsestate->pathspec->isnull); + /* + * 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); + } + } - jsestate->res_expr = - palloc(sizeof(*jsestate->res_expr)); + onerror_default_step_off = state->steps_len; + if (jexpr->on_error) + { + /* Push step(s) for the default ON ERROR expression. */ + if (jexpr->on_error->default_expr) + { + ExecInitExprRec((Expr *) jexpr->on_error->default_expr, + state, resv, resnull); - jsestate->result_expr = jexpr->result_coercion - ? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr, - state->parent, - &jsestate->res_expr->value, - &jsestate->res_expr->isnull) - : NULL; + /* Emit JUMP step to jump to end of JsonExpr code */ + scratch.opcode = EEOP_JUMP; + scratch.d.jump.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, &scratch); - jsestate->default_on_empty = !jexpr->on_empty ? NULL : - ExecInitExpr((Expr *) jexpr->on_empty->default_expr, - state->parent); + /* + * 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 to handle applying coercion correctly. Might be + * skipped if no coercion needs to be applied separately + * necessary to do so will be set later. + */ + 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; - jsestate->default_on_error = - ExecInitExpr((Expr *) jexpr->on_error->default_expr, - state->parent); + /* 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; + + 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 /* coerce */); + jsestate->coercionNeedSubtrans = + (!jsestate->needSubtrans && !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); - fmgr_info(typinput, &jsestate->input.func); + finfo = palloc0(sizeof(FmgrInfo)); + fmgr_info(typinput, finfo); + jsestate->input.finfo = finfo; } - jsestate->args = NIL; + if (jexpr->result_coercion && jexpr->result_coercion->expr) + jsestate->result_coercion = + ExecInitExprWithCaseValue((Expr *) + jexpr->result_coercion, + state->parent, + resv, resnull); - 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)); + /* Set up coercion related data structures. */ + if (jexpr->coercions) + jsestate->coercions = + ExecInitExprWithCaseValue((Expr *) jexpr->coercions, + state->parent, + resv, resnull); - 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; + break; + } - jsestate->args = - lappend(jsestate->args, var); - } + case T_JsonItemCoercions: + { + JsonItemCoercions *coercions = castNode(JsonItemCoercions, + node); + JsonItemCoercionsState *jcstate = + palloc0(sizeof(JsonItemCoercionsState)); + JsonCoercion **coercion; + JsonItemCoercionState *cstate; + ExprEvalStep *as; + int json_item_coercion_step_id; + List *adjust_jumps = NIL; + ListCell *lc; - jsestate->cache = NULL; + /* + * First 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) + /* Push the control step. */ + scratch.opcode = EEOP_JSON_ITEM_COERCION; + scratch.d.json_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++) { - JsonCoercion **coercion; - struct JsonCoercionState *cstate; - Datum *caseval; - bool *casenull; + cstate->coercion = *coercion; + if (cstate->coercion && cstate->coercion->expr) + { + Datum *save_innermost_caseval; + bool *save_innermost_isnull; - jsestate->coercion_expr = - palloc(sizeof(*jsestate->coercion_expr)); + cstate->jump_eval_expr = state->steps_len; - caseval = &jsestate->coercion_expr->value; - casenull = &jsestate->coercion_expr->isnull; + /* Push step(s) to compute (*coercion)->expr. */ + save_innermost_caseval = state->innermost_caseval; + save_innermost_isnull = state->innermost_casenull; - 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 = 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 jump to end of coercions code */ + scratch.opcode = EEOP_JUMP; + + /* + * Remember JUMP step address to set the actual jump + * target address below. + */ + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len); + ExprEvalPushStep(state, &scratch); + } + + /* + * Adjust the jump target address of the control step and all + * the jumps we added in the above loop to make them point to + * the step after the last step that would have been added + * above. + */ + as = &state->steps[json_item_coercion_step_id]; + as->d.json_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; } - ExprEvalPushStep(state, &scratch); break; } diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 723770fda0..02bfed84fc 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 ExecEvalJsonExprCoercion(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, @@ -1817,13 +1830,68 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } - EEO_CASE(EEOP_JSONEXPR) + EEO_CASE(EEOP_JSONEXPR_SKIP) + { + /* + * Skip JSON evaluation 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 (ExecEvalJsonSkip(op)) + EEO_JUMP(op->d.jsonexpr_skip.jump_coercion); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_JSONEXPR_PATH) { /* too complex for an inline implementation */ ExecEvalJson(state, op, econtext); EEO_NEXT(); } + EEO_CASE(EEOP_JSONEXPR_BEHAVIOR) + { + int jumpaddr = ExecEvalJsonExprBehavior(op); + + EEO_JUMP(jumpaddr); + } + + EEO_CASE(EEOP_JSONEXPR_COERCION) + { + JsonExprState *jsestate = op->d.jsonexpr_coercion.jsestate; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + + post_eval->coercion_error = false; + *op->resvalue = + ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext, + *op->resvalue, + op->resnull, NULL, + &post_eval->coercion_error, + post_eval->coercion_use_subtrans); + if (post_eval->coercion_error) + EEO_JUMP(op->d.jsonexpr_coercion.jump_coercion_error); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_JSON_ITEM_COERCION) + { + JsonItemCoercionsState *jcstate = op->d.json_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); + EEO_JUMP(cstate->jump_eval_expr); + } + + /* Skip over all of the steps added for this JsonItemCoercions. */ + EEO_JUMP(op->d.json_item_coercion.jump_skip_item_coercion); + } + EEO_CASE(EEOP_LAST) { /* unreachable */ @@ -4602,8 +4670,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; @@ -4628,7 +4695,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); @@ -4643,18 +4712,17 @@ static Datum ExecEvalJsonExprCoercion(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; + JsonExpr *jexpr = jsestate->jsexpr; + ExprState *estate = post_eval->coercions; if (estate) /* coerce using specified expression */ return ExecEvalExpr(estate, econtext, isNull); - jsestate = op->d.jsonexpr.jsestate; - - if (jsestate->jsexpr->op != JSON_EXISTS_OP) + if (jexpr->op != JSON_EXISTS_OP) { - JsonCoercion *coercion = jsestate->jsexpr->result_coercion; - JsonExpr *jexpr = jsestate->jsexpr; + JsonCoercion *coercion = jexpr->result_coercion; Jsonb *jb = *isNull ? NULL : DatumGetJsonbP(res); if ((coercion && coercion->via_io) || @@ -4664,7 +4732,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); } @@ -4672,17 +4740,20 @@ 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) + /* + * Let the caller know that no coercion was done here, so it can + * 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; @@ -4717,17 +4788,30 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen, if (!var) return -1; - if (!var->evaluated) + /* + * 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) { @@ -4741,19 +4825,84 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen, return id; } +/* + * Return a coercion among those given in 'coercions' for given + * JSON item. + */ +JsonCoercion * +ExecGetJsonItemCoercion(JsonbValue *item, JsonItemCoercions *coercions) +{ + if (item->type == jbvBinary && + JsonContainerIsScalar(item->val.binary.data)) + { + JsonbValue buf; + bool res PG_USED_FOR_ASSERTS_ONLY; + + res = JsonbExtractScalar(item->val.binary.data, &buf); + item = &buf; + Assert(res); + } + + /* get coercion state reference and datum of the corresponding SQL type */ + switch (item->type) + { + case jbvNull: + return coercions->null; + + case jbvString: + return coercions->string; + + case jbvNumeric: + return coercions->numeric; + + case jbvBool: + return coercions->boolean; + + 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; + + case jbvArray: + case jbvObject: + case jbvBinary: + return coercions->composite; + + default: + elog(ERROR, "unexpected jsonb value type %d", item->type); + } + + Assert(false); + return NULL; +} + /* * Prepare SQL/JSON item coercion to the output type. Returned a datum of the * 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)) @@ -4832,9 +4981,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, @@ -4905,7 +5051,6 @@ typedef struct { JsonPath *path; bool *error; - bool coercionInSubtrans; } ExecEvalJsonExprContext; static Datum @@ -4916,16 +5061,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; @@ -4936,17 +5082,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; @@ -4959,19 +5108,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 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; } @@ -4983,32 +5130,30 @@ 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 specific expression. */ + res = JsonbValuePGetDatum(jbv); + post_eval->coercions = 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; + } break; } @@ -5021,7 +5166,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 */ @@ -5037,29 +5186,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 coerce) { if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR) return false; @@ -5067,12 +5200,96 @@ ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion) return false; - if (!coercions) + if (!coerce) + return true; + + return false; +} + +/* Skip calling ExecEvalJson() on a JsonExpr? */ +bool +ExecEvalJsonSkip(ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate; + + if (jsestate->pre_eval.formatted_expr.isnull || + jsestate->pre_eval.pathspec.isnull) + { + *op->resvalue = 0; + *op->resnull = true; + Assert(jsestate->post_eval.coercions == NULL); + /* A signal to the coercion step to not use a sub-trancaction. */ + jsestate->post_eval.coercion_error = true; return true; + } return false; } +int +ExecEvalJsonExprBehavior(ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr_behavior.jsestate; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + JsonBehavior *behavior = NULL; + int jump_to = -1; + bool error = (post_eval->error || post_eval->coercion_error); + + /* + * Don't use a sub-transaction if coercing the ON ERROR expression, so + * that any errors encountered downstream this time are thrown there, + * that is, not rethrown to be handled by the caller. + * + * 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); + + /* + * Directly go to the coercion step to coerce JSON item as is, os skip + * the coercion step if JSON 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 && jump_to >= 0); + + /* + * 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->coercions = NULL; + } + + return op->d.jsonexpr_behavior.jump_coercion; + } + + /* Else evaluate the default ON ERROR or ON EMPTY expression. */ + + return jump_to; +} + /* ---------------------------------------------------------------- * ExecEvalJson * ---------------------------------------------------------------- @@ -5082,64 +5299,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); - - 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; - } + item = pre_eval->formatted_expr.value; + path = DatumGetJsonPathP(pre_eval->pathspec.value); - 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 b6b6512ef1..b96c77a3ab 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2359,12 +2359,39 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; - case EEOP_JSONEXPR: + case EEOP_JSONEXPR_SKIP: + { + int jumpaddr = op->d.jsonexpr_skip.jump_coercion; + LLVMValueRef v_params[1]; + LLVMValueRef v_ret; + + v_params[0] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + v_ret = LLVMBuildCall(b, + llvm_pg_func(mod, "ExecEvalJsonSkip"), + v_params, lengthof(v_params), ""); + v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, ""); + + LLVMBuildCondBr(b, + LLVMBuildICmp(b, LLVMIntEQ, v_ret, + l_sbool_const(1), ""), + opblocks[jumpaddr], + opblocks[opno + 1]); + break; + } + case EEOP_JSONEXPR_PATH: build_EvalXFunc(b, mod, "ExecEvalJson", v_state, op, v_econtext); LLVMBuildBr(b, opblocks[opno + 1]); break; - + case EEOP_JSONEXPR_BEHAVIOR: + /* XXX - handle! */ + break; + case EEOP_JSONEXPR_COERCION: + /* XXX - handle! */ + break; + case EEOP_JSON_ITEM_COERCION: + /* XXX - handle! */ + 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 b2bda86889..b30f20dd2d 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -133,6 +133,7 @@ void *referenced_functions[] = ExecEvalXmlExpr, ExecEvalJsonConstructor, ExecEvalJsonIsPredicate, + ExecEvalJsonSkip, ExecEvalJson, MakeExpandedObjectReadOnlyInternal, slot_getmissingattrs, diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 533df86ff7..9c02218155 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 /* coerce */)) { context->max_hazard = PROPARALLEL_UNSAFE; return true; diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 1e3f1bbee8..4921589260 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, @@ -682,12 +686,44 @@ 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; + int jump_coercion; + } jsonexpr_skip; + + /* for EEOP_JSONEXPR_BEHAVIOR */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + 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; + int jump_coercion_error; + } jsonexpr_coercion; + + struct + { + struct JsonItemCoercionsState *jcstate; + int jump_skip_item_coercion; + } json_item_coercion; } d; } ExprEvalStep; @@ -747,49 +783,112 @@ typedef struct JsonConstructorExprState int nargs; } JsonConstructorExprState; +/* + * State for EEOP_JSONEXPR_BEHAVIOR and EEOP_JSONEXPR_COERCION steps that gets + * filled in during EEOP_JSONEXPR_PATH. + */ +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; + + /* Coercion state; same as parent JsonExprState.coercions when not NULL */ + ExprState *coercions; +} JsonExprPostEvalState; + +/* + * 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; + /* 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 ExecEvalJsonExprCoercion() 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); @@ -850,14 +949,15 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern bool ExecEvalJsonSkip(ExprEvalStep *op); +extern int ExecEvalJsonExprBehavior(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 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) -- 2.35.3