From 5e119f3bc16c3755709dd8ab9d0a3c9d4bb81319 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <andrew@dunslane.net>
Date: Wed, 22 Jun 2022 12:00:47 -0400
Subject: [PATCH v5] 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       | 385 +++++++++++++++++----
 src/backend/executor/execExprInterp.c | 477 +++++++++++++++++++-------
 src/backend/jit/llvm/llvmjit_expr.c   | 219 +++++++++++-
 src/backend/jit/llvm/llvmjit_types.c  |   4 +
 src/backend/optimizer/util/clauses.c  |   6 +-
 src/include/executor/execExpr.h       | 211 ++++++++++--
 src/include/utils/jsonb.h             |   2 +
 7 files changed, 1073 insertions(+), 231 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c8d7145fe3..d4b5102707 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -178,7 +178,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,
@@ -2562,109 +2562,358 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				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 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));
 
-				scratch.opcode = EEOP_JSONEXPR;
+					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);
+				}
+
+				/* Step for the actual JSON path evaluation. */
+				scratch.opcode = EEOP_JSONEXPR_PATH;
 				scratch.d.jsonexpr.jsestate = jsestate;
+				ExprEvalPushStep(state, &scratch);
 
-				jsestate->jsexpr = jexpr;
+				/*
+				 * Push steps to control the evaluation of expressions 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 */
+				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);
+				/* 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);
 
-				jsestate->pathspec =
-					palloc(sizeof(*jsestate->pathspec));
+					/*
+					 * 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);
 
-				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));
+				/* 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);
 
-				jsestate->result_expr = jexpr->result_coercion
-					? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,
-												state->parent,
-												&jsestate->res_expr->value,
-												&jsestate->res_expr->isnull)
-					: NULL;
+					/* 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 */
+				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_empty = !jexpr->on_empty ? NULL :
-					ExecInitExpr((Expr *) jexpr->on_empty->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;
 
-				jsestate->default_on_error =
-					ExecInitExpr((Expr *) jexpr->on_error->default_expr,
-								 state->parent);
+				/* 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 /* coerce */);
+
+				/*
+				 * 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);
-					fmgr_info(typinput, &jsestate->input.func);
+					finfo = palloc0(sizeof(FmgrInfo));
+					fmgr_info(typinput, finfo);
+					jsestate->input.finfo = finfo;
 				}
 
-				jsestate->args = NIL;
+				/*
+				 * 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);
 
-				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));
+				break;
+			}
 
-					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;
+		case T_JsonCoercion:
+			{
+				JsonCoercion *coercion = castNode(JsonCoercion, node);
+				Datum	   *save_innermost_caseval;
+				bool	   *save_innermost_isnull;
 
-					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;
+
+				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_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->coercion_expr =
-						palloc(sizeof(*jsestate->coercion_expr));
+				/*
+				 * 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);
 
-					caseval = &jsestate->coercion_expr->value;
-					casenull = &jsestate->coercion_expr->isnull;
+				/* 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. */
 
-					for (cstate = &jsestate->coercions.null,
-						 coercion = &jexpr->coercions->null;
-						 coercion <= &jexpr->coercions->composite;
-						 coercion++, cstate++)
+				/*
+				 * 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)
 					{
-						cstate->coercion = *coercion;
-						cstate->estate = *coercion ?
-							ExecInitExprWithCaseValue((Expr *) (*coercion)->expr,
-													  state->parent,
-													  caseval, casenull) : NULL;
+						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 jump to end of coercions code,
+					 * because evaluating the coercion expression gives the
+					 * final result and there's nothing more to do.
+					 */
+					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 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;
 				}
 
-				ExprEvalPushStep(state, &scratch);
 				break;
 			}
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 723770fda0..964b0088a4 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,17 +4678,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;
@@ -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,17 @@ 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;
@@ -4717,17 +4756,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 +4793,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 +4949,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 +5019,6 @@ typedef struct
 {
 	JsonPath   *path;
 	bool	   *error;
-	bool		coercionInSubtrans;
 } ExecEvalJsonExprContext;
 
 static Datum
@@ -4916,16 +5029,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 +5050,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 +5076,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;
 					}
 
@@ -4983,32 +5098,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 JsobbValue 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;
 			}
 
@@ -5021,7 +5146,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 +5166,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 +5180,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.coercion == 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->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(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 +5347,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 b6b6512ef1..c76302fba3 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2359,12 +2359,229 @@ 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;
+
+					/*
+					 * Call ExecEvalJsonExprSkip() to decide if JSON path
+					 * evaluation can be skipped and and set
+					 * jsonexpr_skip.jumpdone if so.
+					 */
+					build_EvalXFunc(b, mod, "ExecEvalJsonExprSkip",
+									v_state, op);
+					/* Read jsonexpr_behavior.jumpdone into v_jumpaddr. */
+					v_jumpaddrp = l_ptr_const(&op->d.jsonexpr_skip.jumpdone, l_ptr(TypeSizeT));
+					v_jumpaddr = LLVMBuildLoad(b, v_jumpaddrp, "");
+
+					/*
+					 * Jump to coercion step if
+					 * jsonexpr_skip.jumpdone == 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_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;
+
+					/*
+					 * Call ExecEvalJsonExprBehavior() to decide if ON EMPTY or
+					 * ON ERROR behavior must be invoked depending on what JSON
+					 * path evaluation returned.  This sets
+					 * jsonexpr_behavior.jumpdone to an address as described
+					 * below.
+					 */
+					build_EvalXFunc(b, mod, "ExecEvalJsonExprBehavior",
+									v_state, op);
+					/* Read jsonexpr_behavior.jumpdone into v_jumpaddr. */
+					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);
+
+					/*
+					 * Jump to coercion step if
+					 * jsonexpr_behavior.jumpdone == jsonexpr_behavior.jump_coercion,
+					 * else to the block that checks whether to skip coercion.
+					 */
+					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);
+
+					/*
+					 * Block that checks whether to skip coercion.
+					 *
+					 * Jump to skip coercion if
+					 * jsonexpr_behavior.jumpdone == jsonexpr_behavior.jump_skip_coercion,
+					 * else to check 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_jumpaddr,
+												  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 ON ERROR default expression if
+					 * jsonexpr_behavior.jumpdone == jsonexpr_behavior.jump_onerror_default,
+					 * else jump to evaluate ON EMPTY default expression.
+					 */
+					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;
+
+					/*
+					 * Call ExecEvalJsonExprCoercion() to evaluate appropriate
+					 * coercion.  That will set jsonexpr_coercion.jumpdone to
+					 * an address as described below.
+					 */
+					build_EvalXFunc(b, mod, "ExecEvalJsonExprCoercion",
+									v_state, op, v_econtext);
+					/* Read jsonexpr_coercion.jumpdone into v_jumpaddr. */
+					v_jumpaddrp = l_ptr_const(&op->d.jsonexpr_coercion.jumpdone, l_ptr(TypeSizeT));
+					v_jumpaddr = LLVMBuildLoad(b, v_jumpaddrp, "");
+
+					/*
+					 * Jump to handle coercion error if
+					 * jsonexpr_coercion.jumpdone == jsonexpr_coercion.jump_coercion_error,
+					 * else to the step after coercion (coercion done!).
+					 */
+					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;
+
+					/*
+					 * 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.
+					 */
+					build_EvalXFunc(b, mod, "ExecEvalJsonExprItemCoercion",
+									v_state, op);
+					/* Read jsonexpr_item_coercion.jumpdone into v_jumpaddr. */
+					v_jumpaddrp = l_ptr_const(&op->d.jsonexpr_item_coercion.jumpdone, l_ptr(TypeSizeT));
+					v_jumpaddr = LLVMBuildLoad(b, v_jumpaddrp, "");
+
+					/*
+					 * 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
+						 * jsonexpr_item_coercion.jumpdone == cstate->jump_eval_expr,
+						 * that is, if cstate->jump_eval_expr is valid (skip to end
+						 * if not), else to the next coercion's block.
+						 */
+						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]);
+					}
+
+					/*
+					 * 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 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..b2e6b813b8 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,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);
@@ -850,14 +981,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

