From d2d1164710518d83e44f5f89ad9c1e7d535b9667 Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Wed, 22 Jun 2022 12:00:47 -0400 Subject: [PATCH v4] 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. --- src/backend/executor/execExpr.c | 381 +++++++++++++++++---- src/backend/executor/execExprInterp.c | 474 +++++++++++++++++++------- src/backend/jit/llvm/llvmjit_expr.c | 137 +++++++- src/backend/jit/llvm/llvmjit_types.c | 4 + src/backend/optimizer/util/clauses.c | 6 +- src/include/executor/execExpr.h | 206 ++++++++--- src/include/utils/jsonb.h | 2 + 7 files changed, 977 insertions(+), 233 deletions(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index c8d7145fe3..9e1f7b2b1a 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2558,113 +2558,360 @@ 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 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 "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; + 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); + + /* + * 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; + as->d.jsonexpr_skip.jump_passing_args = passing_args_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; + as->d.jsonexpr_coercion.jump_coercion_done = coercion_step_off + 1; + + 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.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++) { - 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.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; } - ExprEvalPushStep(state, &scratch); break; } diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 723770fda0..af24800d09 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, @@ -1817,13 +1830,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) + { + ExecEvalJsonExprSkip(state, op); + EEO_JUMP(op->d.jsonexpr_skip.jumpdone); + } + + EEO_CASE(EEOP_JSONEXPR_BEHAVIOR) + { + ExecEvalJsonExprBehavior(state, op); + EEO_JUMP(op->d.jsonexpr_behavior.jumpdone); + } + + EEO_CASE(EEOP_JSONEXPR_COERCION) + { + ExecEvalJsonExprCoercion(state, op, econtext); + EEO_JUMP(op->d.jsonexpr_coercion.jumpdone); + } + + EEO_CASE(EEOP_JSON_ITEM_COERCION) + { + ExecEvalJsonExprItemCoercion(state, op); + EEO_JUMP(op->d.jsonexpr_item_coercion.jumpdone); + } + EEO_CASE(EEOP_LAST) { /* unreachable */ @@ -4602,8 +4639,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 +4664,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); @@ -4640,21 +4678,22 @@ 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; + JsonExpr *jexpr = jsestate->jsexpr; + ExprState *estate = post_eval->coercions; + + 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) + 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 +4703,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 +4711,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 +4759,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 +4796,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 +4952,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 +5022,6 @@ typedef struct { JsonPath *path; bool *error; - bool coercionInSubtrans; } ExecEvalJsonExprContext; static Datum @@ -4916,16 +5032,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 +5053,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 +5079,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 +5101,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 +5137,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 +5157,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 +5171,164 @@ 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? */ +void +ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate; + int *jump_to = &op->d.jsonexpr_skip.jumpdone; + + /* + * 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.coercions == NULL); + /* A signal to the coercion step to not use a sub-trancaction. */ + jsestate->post_eval.coercion_error = true; + *jump_to = op->d.jsonexpr_skip.jump_coercion; + } + else + { + /* + * Go evaluate the PASSING args if any and subsequently JSON path + * itself. + */ + *jump_to = op->d.jsonexpr_skip.jump_passing_args; + } +} + +void +ExecEvalJsonExprBehavior(ExprState *state, ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr_behavior.jsestate; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + JsonBehavior *behavior = NULL; + bool error = (post_eval->error || post_eval->coercion_error); + int *jump_to = &op->d.jsonexpr_behavior.jumpdone; + + /* + * 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) + { + *jump_to = !post_eval->coercion_done ? + op->d.jsonexpr_behavior.jump_coercion : + op->d.jsonexpr_behavior.jump_skip_coercion; + return; + } + + 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->coercions = 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(op->d.jsonexpr_behavior.jumpdone >= 0); +} + +/* + * Apply coercion to a JSON item using either JsonExpr.result_coercion or one + * of JsonExpr.coercions, possibly using a sub-transaction. + */ +void +ExecEvalJsonExprCoercion(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + JsonExprState *jsestate = op->d.jsonexpr_coercion.jsestate; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + int *jump_to = &op->d.jsonexpr_coercion.jumpdone; + + *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) + *jump_to = op->d.jsonexpr_coercion.jump_coercion_error; + else + *jump_to = op->d.jsonexpr_coercion.jump_coercion_done; +} + +/* + * Apply a coercion to a JSON item, choosing one among those present in + * JsonItemCoercions. + */ +void +ExecEvalJsonExprItemCoercion(ExprState *state, ExprEvalStep *op) +{ + JsonItemCoercionsState *jcstate = op->d.jsonexpr_item_coercion.jcstate; + JsonItemCoercionState *cstate = NULL; + int *jump_to = &op->d.jsonexpr_item_coercion.jumpdone; + + *op->resvalue = + ExecPrepareJsonItemCoercion(*op->resvalue, jcstate, &cstate); + if (cstate->coercion && cstate->coercion->expr) + { + Assert(cstate->jump_eval_expr >= 0); + *jump_to = cstate->jump_eval_expr; + } + else + { + /* Skip over all of the steps added for this JsonItemCoercions. */ + *jump_to = op->d.jsonexpr_item_coercion.jump_skip_item_coercion; + } +} + /* ---------------------------------------------------------------- * ExecEvalJson * ---------------------------------------------------------------- @@ -5082,64 +5338,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); + item = pre_eval->formatted_expr.value; + path = DatumGetJsonPathP(pre_eval->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 b6b6512ef1..443ac8761d 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2359,12 +2359,147 @@ 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 v_jumpaddrp; + LLVMValueRef v_jumpaddr; + + build_EvalXFunc(b, mod, "ExecEvalJsonExprSkip", + v_state, op); + v_jumpaddrp = l_ptr_const(&op->d.jsonexpr_skip.jumpdone, l_ptr(TypeSizeT)); + v_jumpaddr = LLVMBuildLoad(b, v_jumpaddrp, ""); + + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_jumpaddr, + 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 v_jumpaddrp; + LLVMValueRef v_jumpaddr; + LLVMBasicBlockRef b_jump_skip_coercion, + b_jump_onerror_default; + + build_EvalXFunc(b, mod, "ExecEvalJsonExprBehavior", + v_state, op); + v_jumpaddrp = l_ptr_const(&op->d.jsonexpr_behavior.jumpdone, l_ptr(TypeSizeT)); + v_jumpaddr = LLVMBuildLoad(b, v_jumpaddrp, ""); + + 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); + + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_jumpaddr, + l_int32_const(op->d.jsonexpr_behavior.jump_coercion), + ""), + opblocks[op->d.jsonexpr_behavior.jump_coercion], + b_jump_skip_coercion); + + LLVMPositionBuilderAtEnd(b, b_jump_skip_coercion); + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_jumpaddr, + l_int32_const(op->d.jsonexpr_behavior.jump_skip_coercion), + ""), + opblocks[op->d.jsonexpr_behavior.jump_skip_coercion], + b_jump_onerror_default); + + LLVMPositionBuilderAtEnd(b, b_jump_onerror_default); + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_jumpaddr, + 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 v_jumpaddrp; + LLVMValueRef v_jumpaddr; + + build_EvalXFunc(b, mod, "ExecEvalJsonExprCoercion", + v_state, op, v_econtext); + v_jumpaddrp = l_ptr_const(&op->d.jsonexpr_coercion.jumpdone, l_ptr(TypeSizeT)); + v_jumpaddr = LLVMBuildLoad(b, v_jumpaddrp, ""); + + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_jumpaddr, + 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 v_jumpaddrp; + LLVMValueRef v_jumpaddr; + int n_coercions = (int) (&jcstate->composite - &jcstate->null) + 1; + int i; + LLVMBasicBlockRef *b_coercions; + + build_EvalXFunc(b, mod, "ExecEvalJsonExprItemCoercion", + v_state, op); + v_jumpaddrp = l_ptr_const(&op->d.jsonexpr_item_coercion.jumpdone, l_ptr(TypeSizeT)); + v_jumpaddr = LLVMBuildLoad(b, v_jumpaddrp, ""); + + 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); + + LLVMBuildBr(b, b_coercions[0]); + + /* Add conditional branches for individual coercion's expressions */ + for (cstate = &jcstate->null, i = 0; + cstate <= &jcstate->composite; + cstate++, i++) + { + LLVMPositionBuilderAtEnd(b, b_coercions[i]); + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_jumpaddr, + 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]); + } + 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 b2bda86889..34e13b4a7e 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -133,6 +133,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..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..a40ffd78d0 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,71 @@ 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; + int jump_passing_args; + + /* + * ExecEvalJsonExprSkip() sets this to one of the above + * addresses. + */ + int jumpdone; + } 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; + + /* + * ExecEvalJsonExprBehavior() sets this to one of the above + * addresses. + */ + int jumpdone; + } jsonexpr_behavior; + + /* for EEOP_JSONEXPR_COERCION */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + int jump_coercion_error; + int jump_coercion_done; + + /* + * ExecEvalJsonExprCoercion() sets this to one of the above + * addresses. + */ + int jumpdone; + } jsonexpr_coercion; + + struct + { + struct JsonItemCoercionsState *jcstate; + int jump_skip_item_coercion; + + /* + * ExecEvalJsonExprItemCoercion() sets this to either the "skip" + * address above or to the coercion expression's address given in + * one of the JsonItemCoercionStates found in 'jcstate'. + */ + int jumpdone; + } jsonexpr_item_coercion; } d; } ExprEvalStep; @@ -747,49 +810,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 +976,18 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op); +extern void ExecEvalJsonExprBehavior(ExprState *state, ExprEvalStep *op); +extern void ExecEvalJsonExprCoercion(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void 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 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