From c0d4ead7c31919ebb9147275b27685ca1f556698 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <andrew@dunslane.net>
Date: Wed, 22 Jun 2022 12:00:47 -0400
Subject: [PATCH v3] Some improvements to JsonExpr evaluation

* Store a pointer to the returning type input function FmgrInfo,
not the struct itself, in JsonExprState

* Evaluate various JsonExpr sub-expressions using parent ExprState
These include PASSING args, ON ERROR, ON EMPTY default expressions.
PASSING args are simple, because they all must be evaluated before
the JSON path evaluation can occur anyway, so pushing those
expressions to be uncondionally evaluated before the step to
evaluate JsonExpr proper suffices.  Evaluation of the ON ERROR and
the ON EMPTY expressions is conditional on what JSON item pops out
of the path evaluation (ExecEvalJson()), so needs more logic to
gate the steps for their expressions that are pushed into the parent
ExprState, rather than be imlemented using separate ExprStates.
To that end, this moves the ON ERROR, ON EMPTY behavior decision logic
out of ExecEvalJson() and ExecEvalJsonExpr() into a new function
ExecEvalJsonExprBehavior().

* Final result coercion is now also computed as a separate step that
gets pushed into the parent ExprState.  Though given that any
coercion evaluation errors must be handled the same way as the errors
of JSON item evaluation, that is, to be handled according the ON ERROR
behavior specification, a sub-transaction must be used around coercion
evaluation, so the ExprState to use for the same must have to be a
different one from the JsonExpr's. However, instead of using one
ExprState for each JsonCoercion member of JsonItemCoercions, only one
is used for the whole JsonItemCoercions. The individual JsonCoercion
members (or their Exprs) are handled by steps pushed into that
ExprState, of which only the one that applies to a given JSON item is
chosen at the runtime and others "jumped" over.

TODO: update llvm_compile_expr()
---
 src/backend/executor/execExpr.c       | 377 ++++++++++++++++++----
 src/backend/executor/execExprInterp.c | 431 ++++++++++++++++++--------
 src/backend/jit/llvm/llvmjit_expr.c   |  31 +-
 src/backend/jit/llvm/llvmjit_types.c  |   1 +
 src/backend/optimizer/util/clauses.c  |   6 +-
 src/include/executor/execExpr.h       | 176 ++++++++---
 src/include/utils/jsonb.h             |   2 +
 7 files changed, 792 insertions(+), 232 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c8d7145fe3..d7cda5b850 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2558,113 +2558,356 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_JsonCoercion:
+			{
+				JsonCoercion *coercion = castNode(JsonCoercion, node);
+				Datum	   *save_innermost_caseval;
+				bool	   *save_innermost_isnull;
+
+				/*
+				 * Only ever get here for JsonExpr.result_expression and the
+				 * caller should have checked this.
+				 */
+				Assert(coercion->expr);
+
+				/*
+				 * First push a step to read the value provided by the parent
+				 * JsonExpr via a CaseTestExpr.
+				 */
+				scratch.opcode = EEOP_CASE_TESTVAL;
+				scratch.d.casetest.value = state->innermost_caseval;
+				scratch.d.casetest.isnull = state->innermost_casenull;
+				ExprEvalPushStep(state, &scratch);
+
+				/* Push step(s) to compute coercion->expr. */
+				save_innermost_caseval = state->innermost_caseval;
+				save_innermost_isnull = state->innermost_casenull;
+
+				state->innermost_caseval = resv;
+				state->innermost_casenull = resnull;
+
+				ExecInitExprRec((Expr *) coercion->expr, state, resv, resnull);
+
+				state->innermost_caseval = save_innermost_caseval;
+				state->innermost_casenull = save_innermost_isnull;
+
+				break;
+			}
+
 		case T_JsonExpr:
 			{
 				JsonExpr   *jexpr = castNode(JsonExpr, node);
 				JsonExprState *jsestate = palloc0(sizeof(JsonExprState));
+				JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;
 				ListCell   *argexprlc;
 				ListCell   *argnamelc;
+				int			skip_step_off;
+				int			behavior_step_off;
+				int			onempty_default_step_off;
+				int			onerror_default_step_off;
+				List	   *adjust_jumps = NIL;
+				ListCell   *lc;
+				int			coercion_step_off;
+				ExprEvalStep *as;
+				bool		throwErrors =
+					(jexpr->on_error->btype == JSON_BEHAVIOR_ERROR);
+
+				jsestate->jsexpr = jexpr;
+
+				/*
+				 * Add steps to compute formatted_expr, pathspec, and
+				 * PASSING arg expressions as things that must be
+				 * evaluated before the "main" JSON path evaluation.
+				 */
+				ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
+								&pre_eval->formatted_expr.value,
+								&pre_eval->formatted_expr.isnull);
+				ExecInitExprRec((Expr *) jexpr->path_spec, state,
+								&pre_eval->pathspec.value,
+								&pre_eval->pathspec.isnull);
 
-				scratch.opcode = EEOP_JSONEXPR;
+				/*
+				 * However, before pushing steps for PASSING args, push a step
+				 * that will decide whether to skip evaluating the args and
+				 * JSON path evaluation depending on whether either of
+				 * formatted_expr and pathspec is NULL.
+				 */
+				scratch.opcode = EEOP_JSONEXPR_SKIP;
+				scratch.d.jsonexpr_skip.jsestate = jsestate;
+				skip_step_off = state->steps_len;
+				ExprEvalPushStep(state, &scratch);
+
+				jsestate->pre_eval.args = NIL;
+				forboth(argexprlc, jexpr->passing_values,
+						argnamelc, jexpr->passing_names)
+				{
+					Expr	   *argexpr = (Expr *) lfirst(argexprlc);
+					String	   *argname = lfirst_node(String, argnamelc);
+					JsonPathVariableEvalContext *var = palloc(sizeof(*var));
+
+					var->name = pstrdup(argname->sval);
+					var->typid = exprType((Node *) argexpr);
+					var->typmod = exprTypmod((Node *) argexpr);
+
+					/*
+					 * Not necessary when being evaluated for a JsonExpr, like
+					 * in this case.
+					 */
+					var->estate = NULL;
+					var->econtext = NULL;
+					var->mcxt = NULL;
+
+					/*
+					 * Mark these as always evaluated because they must have
+					 * been evaluated before JSON path evaluation begins,
+					 * because we haven't pushed the step for the latter yet.
+					 */
+					var->evaluated = true;
+
+					ExecInitExprRec((Expr *) argexpr, state,
+									&var->value,
+									&var->isnull);
+
+					pre_eval->args = lappend(pre_eval->args, var);
+				}
+
+				/* Now push the step for the actual JSON path evaluation. */
+				scratch.opcode = EEOP_JSONEXPR_PATH;
 				scratch.d.jsonexpr.jsestate = jsestate;
+				ExprEvalPushStep(state, &scratch);
 
-				jsestate->jsexpr = jexpr;
+				/*
+				 * Now push steps to control the expressions evaluated based
+				 * on the result of JSON path evaluation.
+				 */
 
-				jsestate->formatted_expr =
-					palloc(sizeof(*jsestate->formatted_expr));
+				/*
+				 * Step to handle ON ERROR and ON EMPTY behavior correctly.
+				 */
+				scratch.opcode = EEOP_JSONEXPR_BEHAVIOR;
+				scratch.d.jsonexpr_behavior.jsestate = jsestate;
+				behavior_step_off = state->steps_len;
+				ExprEvalPushStep(state, &scratch);
 
-				ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
-								&jsestate->formatted_expr->value,
-								&jsestate->formatted_expr->isnull);
+				onempty_default_step_off = state->steps_len;
+				if (jexpr->on_empty)
+				{
+					/* Push step(s) for the default ON EMPTY expression. */
+					if (jexpr->on_empty->default_expr)
+					{
+						ExecInitExprRec((Expr *) jexpr->on_empty->default_expr,
+										 state, resv, resnull);
 
-				jsestate->pathspec =
-					palloc(sizeof(*jsestate->pathspec));
+						/* Emit JUMP step to jump to end of JsonExpr code */
+						scratch.opcode = EEOP_JUMP;
+						scratch.d.jump.jumpdone = -1;	/* computed later */
+						ExprEvalPushStep(state, &scratch);
 
-				ExecInitExprRec((Expr *) jexpr->path_spec, state,
-								&jsestate->pathspec->value,
-								&jsestate->pathspec->isnull);
+						/*
+						 * Don't know address for that jump yet, compute once the
+						 * whole JsonExpr is built.
+						 */
+						adjust_jumps = lappend_int(adjust_jumps,
+												   state->steps_len - 1);
+					}
+				}
 
-				jsestate->res_expr =
-					palloc(sizeof(*jsestate->res_expr));
+				onerror_default_step_off = state->steps_len;
+				if (jexpr->on_error)
+				{
+					/* Push step(s) for the default ON ERROR expression. */
+					if (jexpr->on_error->default_expr)
+					{
+						ExecInitExprRec((Expr *) jexpr->on_error->default_expr,
+										 state, resv, resnull);
 
-				jsestate->result_expr = jexpr->result_coercion
-					? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,
-												state->parent,
-												&jsestate->res_expr->value,
-												&jsestate->res_expr->isnull)
-					: NULL;
+						/* Emit JUMP step to jump to end of JsonExpr code */
+						scratch.opcode = EEOP_JUMP;
+						scratch.d.jump.jumpdone = -1;	/* computed later */
+						ExprEvalPushStep(state, &scratch);
 
-				jsestate->default_on_empty = !jexpr->on_empty ? NULL :
-					ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
-								 state->parent);
+						/*
+						 * Don't know address for that jump yet, compute once the
+						 * whole JsonExpr is built.
+						 */
+						adjust_jumps = lappend_int(adjust_jumps,
+												   state->steps_len - 1);
+					}
+				}
+
+				/*
+				 * Step to handle applying coercion correctly.  Might be
+				 * skipped if no coercion needs to be applied separately
+				 * necessary to do so will be set later.
+				 */
+				scratch.opcode = EEOP_JSONEXPR_COERCION;
+				scratch.d.jsonexpr_coercion.jsestate = jsestate;
+				coercion_step_off = state->steps_len;
+				ExprEvalPushStep(state, &scratch);
+
+				/*
+				 * Adjust jump target addresses in various post-eval steps now
+				 * that we have all the steps in place.
+				 */
+
+				/* EEOP_JSONEXPR_SKIP */
+				as = &state->steps[skip_step_off];
+				as->d.jsonexpr_skip.jump_coercion = coercion_step_off;
 
-				jsestate->default_on_error =
-					ExecInitExpr((Expr *) jexpr->on_error->default_expr,
-								 state->parent);
+				/* EEOP_JSONEXPR_BEHAVIOR */
+				as = &state->steps[behavior_step_off];
+				as->d.jsonexpr_behavior.jump_onerror_default = onerror_default_step_off;
+				as->d.jsonexpr_behavior.jump_onempty_default = onempty_default_step_off;
+				as->d.jsonexpr_behavior.jump_coercion = coercion_step_off;
+				as->d.jsonexpr_behavior.jump_skip_coercion = coercion_step_off + 1;
 
+				/* EEOP_JSONEXPR_COERCION */
+				as = &state->steps[coercion_step_off];
+				as->d.jsonexpr_coercion.jump_coercion_error = behavior_step_off;
+
+				foreach(lc, adjust_jumps)
+				{
+					as = &state->steps[lfirst_int(lc)];
+
+					as->d.jump.jumpdone = state->steps_len;
+				}
+
+				/*
+				 * Set if we must use a sub-transaction around path evaluation
+				 * that can be aborted on error instead of aborting the parent
+				 * query.
+				 */
+				jsestate->needSubtrans =
+					ExecEvalJsonNeedsSubTransaction(jexpr,
+													true /* coerce */);
+				jsestate->coercionNeedSubtrans =
+					(!jsestate->needSubtrans && !throwErrors);
+
+				/*
+				 * Set RETURNING type's input function used by
+				 * ExecEvalJsonExprCoercion().
+				 */
 				if (jexpr->omit_quotes ||
 					(jexpr->result_coercion && jexpr->result_coercion->via_io))
 				{
 					Oid			typinput;
+					FmgrInfo   *finfo;
 
 					/* lookup the result type's input function */
 					getTypeInputInfo(jexpr->returning->typid, &typinput,
 									 &jsestate->input.typioparam);
-					fmgr_info(typinput, &jsestate->input.func);
+					finfo = palloc0(sizeof(FmgrInfo));
+					fmgr_info(typinput, finfo);
+					jsestate->input.finfo = finfo;
 				}
 
-				jsestate->args = NIL;
+				if (jexpr->result_coercion && jexpr->result_coercion->expr)
+					jsestate->result_coercion =
+						ExecInitExprWithCaseValue((Expr *)
+												  jexpr->result_coercion,
+												  state->parent,
+												  resv, resnull);
 
-				forboth(argexprlc, jexpr->passing_values,
-						argnamelc, jexpr->passing_names)
-				{
-					Expr	   *argexpr = (Expr *) lfirst(argexprlc);
-					String	   *argname = lfirst_node(String, argnamelc);
-					JsonPathVariableEvalContext *var = palloc(sizeof(*var));
+				/* Set up coercion related data structures. */
+				if (jexpr->coercions)
+					jsestate->coercions =
+						ExecInitExprWithCaseValue((Expr *) jexpr->coercions,
+												  state->parent,
+												  resv, resnull);
 
-					var->name = pstrdup(argname->sval);
-					var->typid = exprType((Node *) argexpr);
-					var->typmod = exprTypmod((Node *) argexpr);
-					var->estate = ExecInitExpr(argexpr, state->parent);
-					var->econtext = NULL;
-					var->mcxt = NULL;
-					var->evaluated = false;
-					var->value = (Datum) 0;
-					var->isnull = true;
+				break;
+			}
 
-					jsestate->args =
-						lappend(jsestate->args, var);
-				}
+		case T_JsonItemCoercions:
+			{
+				JsonItemCoercions *coercions = castNode(JsonItemCoercions,
+														node);
+				JsonItemCoercionsState *jcstate =
+					palloc0(sizeof(JsonItemCoercionsState));
+				JsonCoercion **coercion;
+				JsonItemCoercionState *cstate;
+				ExprEvalStep *as;
+				int			json_item_coercion_step_id;
+				List	   *adjust_jumps = NIL;
+				ListCell   *lc;
 
-				jsestate->cache = NULL;
+				/*
+				 * First push a step to read the value provided by the parent
+				 * JsonExpr via a CaseTestExpr.
+				 */
+				scratch.opcode = EEOP_CASE_TESTVAL;
+				scratch.d.casetest.value = state->innermost_caseval;
+				scratch.d.casetest.isnull = state->innermost_casenull;
+				ExprEvalPushStep(state, &scratch);
 
-				if (jexpr->coercions)
+				/* Push the control step. */
+				scratch.opcode = EEOP_JSON_ITEM_COERCION;
+				scratch.d.json_item_coercion.jcstate = jcstate;
+				json_item_coercion_step_id = state->steps_len;
+				ExprEvalPushStep(state, &scratch);
+				/* Will set jump_skip_coercion target address below. */
+
+				/*
+				 * Now push the steps of individual coercion's expression, if
+				 * needed.
+				 */
+				for (cstate = &jcstate->null,
+					 coercion = &coercions->null;
+					 coercion <= &coercions->composite;
+					 coercion++, cstate++)
 				{
-					JsonCoercion **coercion;
-					struct JsonCoercionState *cstate;
-					Datum	   *caseval;
-					bool	   *casenull;
+					cstate->coercion = *coercion;
+					if (cstate->coercion && cstate->coercion->expr)
+					{
+						Datum	   *save_innermost_caseval;
+						bool	   *save_innermost_isnull;
 
-					jsestate->coercion_expr =
-						palloc(sizeof(*jsestate->coercion_expr));
+						cstate->jump_eval_expr = state->steps_len;
 
-					caseval = &jsestate->coercion_expr->value;
-					casenull = &jsestate->coercion_expr->isnull;
+						/* Push step(s) to compute (*coercion)->expr. */
+						save_innermost_caseval = state->innermost_caseval;
+						save_innermost_isnull = state->innermost_casenull;
 
-					for (cstate = &jsestate->coercions.null,
-						 coercion = &jexpr->coercions->null;
-						 coercion <= &jexpr->coercions->composite;
-						 coercion++, cstate++)
-					{
-						cstate->coercion = *coercion;
-						cstate->estate = *coercion ?
-							ExecInitExprWithCaseValue((Expr *) (*coercion)->expr,
-													  state->parent,
-													  caseval, casenull) : NULL;
+						state->innermost_caseval = resv;
+						state->innermost_casenull = resnull;
+
+						ExecInitExprRec((Expr *) cstate->coercion->expr,
+										state, resv, resnull);
+
+						state->innermost_caseval = save_innermost_caseval;
+						state->innermost_casenull = save_innermost_isnull;
 					}
+					else
+						cstate->jump_eval_expr = -1;
+
+					/* Emit JUMP step to jump to end of coercions code */
+					scratch.opcode = EEOP_JUMP;
+
+					/*
+					 * Remember JUMP step address to set the actual jump
+					 * target address below.
+					 */
+					adjust_jumps = lappend_int(adjust_jumps,
+											   state->steps_len);
+					ExprEvalPushStep(state, &scratch);
+				}
+
+				/*
+				 * Adjust the jump target address of the control step and all
+				 * the jumps we added in the above loop to make them point to
+				 * the step after the last step that would have been added
+				 * above.
+				 */
+				as = &state->steps[json_item_coercion_step_id];
+				as->d.json_item_coercion.jump_skip_item_coercion = state->steps_len;
+				foreach(lc, adjust_jumps)
+				{
+					int		jump_step_id = lfirst_int(lc);
+
+					as = &state->steps[jump_step_id];
+					as->d.jump.jumpdone = state->steps_len;
 				}
 
-				ExprEvalPushStep(state, &scratch);
 				break;
 			}
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 723770fda0..02bfed84fc 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -158,6 +158,15 @@ static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
 									bool *changed);
 static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
 							   ExprContext *econtext, bool checkisnull);
+static Datum ExecEvalJsonBehavior(JsonBehavior *behavior, bool *is_null);
+typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext,
+						   Datum item, bool *resnull, void *p, bool *error);
+static Datum ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
+						 ExprContext *econtext,
+						 Datum res, bool *resnull,
+						 void *p, bool *error, bool subtrans);
+static Datum ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
+						 Datum res, bool *isNull, void *p, bool *error);
 
 /* fast-path evaluation functions */
 static Datum ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull);
@@ -490,7 +499,11 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_SUBPLAN,
 		&&CASE_EEOP_JSON_CONSTRUCTOR,
 		&&CASE_EEOP_IS_JSON,
-		&&CASE_EEOP_JSONEXPR,
+		&&CASE_EEOP_JSONEXPR_SKIP,
+		&&CASE_EEOP_JSONEXPR_PATH,
+		&&CASE_EEOP_JSONEXPR_BEHAVIOR,
+		&&CASE_EEOP_JSONEXPR_COERCION,
+		&&CASE_EEOP_JSON_ITEM_COERCION,
 		&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
 		&&CASE_EEOP_AGG_DESERIALIZE,
 		&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1817,13 +1830,68 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
-		EEO_CASE(EEOP_JSONEXPR)
+		EEO_CASE(EEOP_JSONEXPR_SKIP)
+		{
+			/*
+			 * Skip JSON evaluation if either of the input expressions has
+			 * turned out to be NULL, though do execute domain checks for
+			 * NULLs, which are handled by the coercion step.
+			 */
+			if (ExecEvalJsonSkip(op))
+				EEO_JUMP(op->d.jsonexpr_skip.jump_coercion);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JSONEXPR_PATH)
 		{
 			/* too complex for an inline implementation */
 			ExecEvalJson(state, op, econtext);
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_JSONEXPR_BEHAVIOR)
+		{
+			int		jumpaddr = ExecEvalJsonExprBehavior(op);
+
+			EEO_JUMP(jumpaddr);
+		}
+
+		EEO_CASE(EEOP_JSONEXPR_COERCION)
+		{
+			JsonExprState *jsestate = op->d.jsonexpr_coercion.jsestate;
+			JsonExprPostEvalState *post_eval = &jsestate->post_eval;
+
+			post_eval->coercion_error = false;
+			*op->resvalue =
+				ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
+										 *op->resvalue,
+										 op->resnull, NULL,
+										 &post_eval->coercion_error,
+										 post_eval->coercion_use_subtrans);
+			if (post_eval->coercion_error)
+				EEO_JUMP(op->d.jsonexpr_coercion.jump_coercion_error);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JSON_ITEM_COERCION)
+		{
+			JsonItemCoercionsState *jcstate = op->d.json_item_coercion.jcstate;
+			JsonItemCoercionState *cstate = NULL;
+
+			*op->resvalue =
+				ExecPrepareJsonItemCoercion(*op->resvalue, jcstate, &cstate);
+			if (cstate->coercion && cstate->coercion->expr)
+			{
+				Assert(cstate->jump_eval_expr >= 0);
+				EEO_JUMP(cstate->jump_eval_expr);
+			}
+
+			/* Skip over all of the steps added for this JsonItemCoercions. */
+			EEO_JUMP(op->d.json_item_coercion.jump_skip_item_coercion);
+		}
+
 		EEO_CASE(EEOP_LAST)
 		{
 			/* unreachable */
@@ -4602,8 +4670,7 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
  * Evaluate a JSON error/empty behavior result.
  */
 static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
-					 ExprState *default_estate, bool *is_null)
+ExecEvalJsonBehavior(JsonBehavior *behavior, bool *is_null)
 {
 	*is_null = false;
 
@@ -4628,7 +4695,9 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
 			return (Datum) 0;
 
 		case JSON_BEHAVIOR_DEFAULT:
-			return ExecEvalExpr(default_estate, econtext, is_null);
+			/* Always handled in the caller. */
+			Assert(false);
+			return (Datum) 0;
 
 		default:
 			elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
@@ -4643,18 +4712,17 @@ static Datum
 ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
 						 Datum res, bool *isNull, void *p, bool *error)
 {
-	ExprState  *estate = p;
-	JsonExprState *jsestate;
+	JsonExprState *jsestate = op->d.jsonexpr_coercion.jsestate;
+	JsonExprPostEvalState *post_eval = &jsestate->post_eval;
+	JsonExpr   *jexpr = jsestate->jsexpr;
+	ExprState  *estate = post_eval->coercions;
 
 	if (estate)					/* coerce using specified expression */
 		return ExecEvalExpr(estate, econtext, isNull);
 
-	jsestate = op->d.jsonexpr.jsestate;
-
-	if (jsestate->jsexpr->op != JSON_EXISTS_OP)
+	if (jexpr->op != JSON_EXISTS_OP)
 	{
-		JsonCoercion *coercion = jsestate->jsexpr->result_coercion;
-		JsonExpr   *jexpr = jsestate->jsexpr;
+		JsonCoercion *coercion = jexpr->result_coercion;
 		Jsonb	   *jb = *isNull ? NULL : DatumGetJsonbP(res);
 
 		if ((coercion && coercion->via_io) ||
@@ -4664,7 +4732,7 @@ ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
 			/* strip quotes and call typinput function */
 			char	   *str = *isNull ? NULL : JsonbUnquote(jb);
 
-			return InputFunctionCall(&jsestate->input.func, str,
+			return InputFunctionCall(jsestate->input.finfo, str,
 									 jsestate->input.typioparam,
 									 jexpr->returning->typmod);
 		}
@@ -4672,17 +4740,20 @@ ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
 			return json_populate_type(res, JSONBOID,
 									  jexpr->returning->typid,
 									  jexpr->returning->typmod,
-									  &jsestate->cache,
+									  &post_eval->cache,
 									  econtext->ecxt_per_query_memory,
 									  isNull);
 	}
 
-	if (jsestate->result_expr)
+	/*
+	 * Let the caller know that no coercion was done here, so it can
+	 * coerce with jexpr->result_coercion if there's one.
+	 */
+	if (jsestate->result_coercion)
 	{
-		jsestate->res_expr->value = res;
-		jsestate->res_expr->isnull = *isNull;
-
-		res = ExecEvalExpr(jsestate->result_expr, econtext, isNull);
+		*op->resvalue = res;
+		*op->resnull = *isNull;
+		return ExecEvalExpr(jsestate->result_coercion, econtext, isNull);
 	}
 
 	return res;
@@ -4717,17 +4788,30 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
 	if (!var)
 		return -1;
 
-	if (!var->evaluated)
+	/*
+	 * When belonging to a JsonExpr, path variables are computed with the
+	 * JsonExpr's ExprState (var->estate is NULL), so don't need to be computed
+	 * here.  In some other cases, such as when the path variables belonging
+	 * to a JsonTable instead, those variables must be evaluated on their own,
+	 * without the enclosing JsonExpr itself needing to be evaluated, so must
+	 * be handled here.
+	 */
+	if (var->estate && !var->evaluated)
 	{
 		MemoryContext oldcxt = var->mcxt ?
 		MemoryContextSwitchTo(var->mcxt) : NULL;
 
+		Assert(var->econtext != NULL);
 		var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
 		var->evaluated = true;
 
 		if (oldcxt)
 			MemoryContextSwitchTo(oldcxt);
 	}
+	else
+	{
+		Assert(var->evaluated);
+	}
 
 	if (var->isnull)
 	{
@@ -4741,19 +4825,84 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
 	return id;
 }
 
+/*
+ * Return a coercion among those given in 'coercions' for given
+ * JSON item.
+ */
+JsonCoercion *
+ExecGetJsonItemCoercion(JsonbValue *item, JsonItemCoercions *coercions)
+{
+	if (item->type == jbvBinary &&
+		JsonContainerIsScalar(item->val.binary.data))
+	{
+		JsonbValue	buf;
+		bool		res PG_USED_FOR_ASSERTS_ONLY;
+
+		res = JsonbExtractScalar(item->val.binary.data, &buf);
+		item = &buf;
+		Assert(res);
+	}
+
+	/* get coercion state reference and datum of the corresponding SQL type */
+	switch (item->type)
+	{
+		case jbvNull:
+			return coercions->null;
+
+		case jbvString:
+			return coercions->string;
+
+		case jbvNumeric:
+			return coercions->numeric;
+
+		case jbvBool:
+			return coercions->boolean;
+
+		case jbvDatetime:
+			switch (item->val.datetime.typid)
+			{
+				case DATEOID:
+					return coercions->date;
+				case TIMEOID:
+					return coercions->time;
+				case TIMETZOID:
+					return coercions->timetz;
+				case TIMESTAMPOID:
+					return coercions->timestamp;
+				case TIMESTAMPTZOID:
+					return coercions->timestamptz;
+				default:
+					elog(ERROR, "unexpected jsonb datetime type oid %u",
+						 item->val.datetime.typid);
+			}
+			break;
+
+		case jbvArray:
+		case jbvObject:
+		case jbvBinary:
+			return coercions->composite;
+
+		default:
+			elog(ERROR, "unexpected jsonb value type %d", item->type);
+	}
+
+	Assert(false);
+	return NULL;
+}
+
 /*
  * Prepare SQL/JSON item coercion to the output type. Returned a datum of the
  * corresponding SQL type and a pointer to the coercion state.
  */
 Datum
-ExecPrepareJsonItemCoercion(JsonbValue *item,
-							JsonReturning *returning,
-							struct JsonCoercionsState *coercions,
-							struct JsonCoercionState **pcoercion)
+ExecPrepareJsonItemCoercion(Datum itemval,
+							JsonItemCoercionsState *coercions,
+							JsonItemCoercionState **pcoercion)
 {
-	struct JsonCoercionState *coercion;
+	JsonbValue *item = DatumGetJsonbValueP(itemval);
+	JsonItemCoercionState *coercion;
 	Datum		res;
-	JsonbValue	buf;
+	JsonbValue 	buf;
 
 	if (item->type == jbvBinary &&
 		JsonContainerIsScalar(item->val.binary.data))
@@ -4832,9 +4981,6 @@ ExecPrepareJsonItemCoercion(JsonbValue *item,
 	return res;
 }
 
-typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext,
-						   Datum item, bool *resnull, void *p, bool *error);
-
 static Datum
 ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
 						 ExprContext *econtext,
@@ -4905,7 +5051,6 @@ typedef struct
 {
 	JsonPath   *path;
 	bool	   *error;
-	bool		coercionInSubtrans;
 } ExecEvalJsonExprContext;
 
 static Datum
@@ -4916,16 +5061,17 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 	ExecEvalJsonExprContext *cxt = pcxt;
 	JsonPath   *path = cxt->path;
 	JsonExprState *jsestate = op->d.jsonexpr.jsestate;
+	JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;
+	JsonExprPostEvalState *post_eval = &jsestate->post_eval;
 	JsonExpr   *jexpr = jsestate->jsexpr;
-	ExprState  *estate = NULL;
-	bool		empty = false;
+	bool	   *empty = &post_eval->empty;
 	Datum		res = (Datum) 0;
 
 	switch (jexpr->op)
 	{
 		case JSON_QUERY_OP:
-			res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
-								jsestate->args);
+			res = JsonPathQuery(item, path, jexpr->wrapper, empty, error,
+								pre_eval->args);
 			if (error && *error)
 			{
 				*resnull = true;
@@ -4936,17 +5082,20 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 
 		case JSON_VALUE_OP:
 			{
-				struct JsonCoercionState *jcstate;
-				JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
-												jsestate->args);
+				JsonCoercion *coercion;
+				JsonbValue *jbv = JsonPathValue(item, path, empty, error,
+												pre_eval->args);
 
 				if (error && *error)
+				{
+					*resnull = true;
 					return (Datum) 0;
+				}
 
 				if (!jbv)		/* NULL or empty */
 					break;
 
-				Assert(!empty);
+				Assert(!*empty);
 
 				*resnull = false;
 
@@ -4959,19 +5108,17 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 					break;
 				}
 
-				/* Use coercion from SQL/JSON item type to the output type */
-				res = ExecPrepareJsonItemCoercion(jbv,
-												  jsestate->jsexpr->returning,
-												  &jsestate->coercions,
-												  &jcstate);
-
-				if (jcstate->coercion &&
-					(jcstate->coercion->via_io ||
-					 jcstate->coercion->via_populate))
+				/*
+				 * Error out no cast exists to coerce SQL/JSON item to the
+				 * the output type
+				 */
+				coercion = ExecGetJsonItemCoercion(jbv, jsestate->jsexpr->coercions);
+				if (coercion && (coercion->via_io || coercion->via_populate))
 				{
 					if (error)
 					{
 						*error = true;
+						*resnull = true;
 						return (Datum) 0;
 					}
 
@@ -4983,32 +5130,30 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 							(errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
 							 errmsg("SQL/JSON item cannot be cast to target type")));
 				}
-				else if (!jcstate->estate)
-					return res; /* no coercion */
-
-				/* coerce using specific expression */
-				estate = jcstate->estate;
-				jsestate->coercion_expr->value = res;
-				jsestate->coercion_expr->isnull = *resnull;
+				else
+				{
+					/* Coerce using specific expression. */
+					res = JsonbValuePGetDatum(jbv);
+					post_eval->coercions = jsestate->coercions;
+				}
 				break;
 			}
 
 		case JSON_EXISTS_OP:
 			{
 				bool		exists = JsonPathExists(item, path,
-													jsestate->args,
+													pre_eval->args,
 													error);
 
 				*resnull = error && *error;
 				res = BoolGetDatum(exists);
 
-				if (!jsestate->result_expr)
+				if (jexpr->result_coercion == NULL)
+				{
+					/* No coercion needed */
+					post_eval->coercion_done = true;
 					return res;
-
-				/* coerce using result expression */
-				estate = jsestate->result_expr;
-				jsestate->res_expr->value = res;
-				jsestate->res_expr->isnull = *resnull;
+				}
 				break;
 			}
 
@@ -5021,7 +5166,11 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 			return (Datum) 0;
 	}
 
-	if (empty)
+	/*
+	 * If the ON EMPTY behavior is to cause an error, do so here.  Other
+	 * behaviors will be handled by the caller.
+	 */
+	if (*empty)
 	{
 		Assert(jexpr->on_empty);	/* it is not JSON_EXISTS */
 
@@ -5037,29 +5186,13 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 					(errcode(ERRCODE_NO_SQL_JSON_ITEM),
 					 errmsg("no SQL/JSON item")));
 		}
-
-		if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)
-
-			/*
-			 * Execute DEFAULT expression as a coercion expression, because
-			 * its result is already coerced to the target type.
-			 */
-			estate = jsestate->default_on_empty;
-		else
-			/* Execute ON EMPTY behavior */
-			res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,
-									   jsestate->default_on_empty,
-									   resnull);
 	}
 
-	return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
-									res, resnull, estate, error,
-									cxt->coercionInSubtrans);
+	return res;
 }
 
 bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
-								struct JsonCoercionsState *coercions)
+ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, bool coerce)
 {
 	if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
 		return false;
@@ -5067,12 +5200,96 @@ ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
 	if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
 		return false;
 
-	if (!coercions)
+	if (!coerce)
+		return true;
+
+	return false;
+}
+
+/* Skip calling ExecEvalJson() on a JsonExpr? */
+bool
+ExecEvalJsonSkip(ExprEvalStep *op)
+{
+	JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate;
+
+	if (jsestate->pre_eval.formatted_expr.isnull ||
+		jsestate->pre_eval.pathspec.isnull)
+	{
+		*op->resvalue = 0;
+		*op->resnull = true;
+		Assert(jsestate->post_eval.coercions == NULL);
+		/* A signal to the coercion step to not use a sub-trancaction. */
+		jsestate->post_eval.coercion_error = true;
 		return true;
+	}
 
 	return false;
 }
 
+int
+ExecEvalJsonExprBehavior(ExprEvalStep *op)
+{
+	JsonExprState *jsestate = op->d.jsonexpr_behavior.jsestate;
+	JsonExprPostEvalState *post_eval = &jsestate->post_eval;
+	JsonBehavior *behavior = NULL;
+	int		jump_to = -1;
+	bool	error = (post_eval->error || post_eval->coercion_error);
+
+	/*
+	 * Don't use a sub-transaction if coercing the ON ERROR expression, so
+	 * that any errors encountered downstream this time are thrown there,
+	 * that is, not rethrown to be handled by the caller.
+	 *
+	 * XXX - doesn't that violate ON ERROR behavior?  Actually, that is
+	 * how it behaved even before this expression eval logic rewrite!
+	 */
+	post_eval->coercion_use_subtrans =
+				(jsestate->coercionNeedSubtrans && !error);
+
+	/*
+	 * Directly go to the coercion step to coerce JSON item as is, os skip
+	 * the coercion step if JSON item is already coerced by ExecEvalJson().
+	 */
+	if (!error && !post_eval->empty)
+		return !post_eval->coercion_done ?
+			   op->d.jsonexpr_behavior.jump_coercion :
+			   op->d.jsonexpr_behavior.jump_skip_coercion;
+
+	if (error)
+	{
+		behavior = jsestate->jsexpr->on_error;
+		jump_to = op->d.jsonexpr_behavior.jump_onerror_default;
+	}
+	else if (post_eval->empty)
+	{
+		behavior = jsestate->jsexpr->on_empty;
+		jump_to = op->d.jsonexpr_behavior.jump_onempty_default;
+	}
+
+	Assert(behavior && jump_to >= 0);
+
+	/*
+	 * If a non-default behavior is specified, get the appropriate
+	 * value and go to the coercion step.
+	 */
+	if (behavior->btype != JSON_BEHAVIOR_DEFAULT)
+	{
+		*op->resvalue = ExecEvalJsonBehavior(behavior, op->resnull);
+
+		if (post_eval->coercion_error)
+		{
+			post_eval->coercion_done = false;
+			post_eval->coercions = NULL;
+		}
+
+		return op->d.jsonexpr_behavior.jump_coercion;
+	}
+
+	/* Else evaluate the default ON ERROR or ON EMPTY expression. */
+
+	return jump_to;
+}
+
 /* ----------------------------------------------------------------
  *		ExecEvalJson
  * ----------------------------------------------------------------
@@ -5082,64 +5299,30 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
 	ExecEvalJsonExprContext cxt;
 	JsonExprState *jsestate = op->d.jsonexpr.jsestate;
+	JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;
+	JsonExprPostEvalState *post_eval = &jsestate->post_eval;
 	JsonExpr   *jexpr = jsestate->jsexpr;
 	Datum		item;
 	Datum		res = (Datum) 0;
 	JsonPath   *path;
-	ListCell   *lc;
-	bool		error = false;
-	bool		needSubtrans;
 	bool		throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
 
 	*op->resnull = true;		/* until we get a result */
 	*op->resvalue = (Datum) 0;
 
-	if (jsestate->formatted_expr->isnull || jsestate->pathspec->isnull)
-	{
-		/* execute domain checks for NULLs */
-		(void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
-										NULL, NULL);
-
-		Assert(*op->resnull);
-		return;
-	}
-
-	item = jsestate->formatted_expr->value;
-	path = DatumGetJsonPathP(jsestate->pathspec->value);
-
-	/* reset JSON path variable contexts */
-	foreach(lc, jsestate->args)
-	{
-		JsonPathVariableEvalContext *var = lfirst(lc);
-
-		var->econtext = econtext;
-		var->evaluated = false;
-	}
+	item = pre_eval->formatted_expr.value;
+	path = DatumGetJsonPathP(pre_eval->pathspec.value);
 
-	needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions);
+	/* Reset JsonExprPostEvalState for this evaluation. */
+	memset(post_eval, 0, sizeof(*post_eval));
 
 	cxt.path = path;
-	cxt.error = throwErrors ? NULL : &error;
-	cxt.coercionInSubtrans = !needSubtrans && !throwErrors;
-	Assert(!needSubtrans || cxt.error);
+	cxt.error = throwErrors ? NULL : &post_eval->error;
+	Assert(!jsestate->needSubtrans || cxt.error);
 
 	res = ExecEvalJsonExprSubtrans(ExecEvalJsonExpr, op, econtext, item,
 								   op->resnull, &cxt, cxt.error,
-								   needSubtrans);
-
-	if (error)
-	{
-		/* Execute ON ERROR behavior */
-		res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
-								   jsestate->default_on_error,
-								   op->resnull);
-
-		/* result is already coerced in DEFAULT behavior case */
-		if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT)
-			res = ExecEvalJsonExprCoercion(op, econtext, res,
-										   op->resnull,
-										   NULL, NULL);
-	}
+								   jsestate->needSubtrans);
 
 	*op->resvalue = res;
 }
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index b6b6512ef1..b96c77a3ab 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2359,12 +2359,39 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
-			case EEOP_JSONEXPR:
+			case EEOP_JSONEXPR_SKIP:
+				{
+					int			jumpaddr = op->d.jsonexpr_skip.jump_coercion;
+					LLVMValueRef v_params[1];
+					LLVMValueRef v_ret;
+
+					v_params[0] = l_ptr_const(op, l_ptr(StructExprEvalStep));
+					v_ret = LLVMBuildCall(b,
+										  llvm_pg_func(mod, "ExecEvalJsonSkip"),
+										  v_params, lengthof(v_params), "");
+					v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, "");
+
+					LLVMBuildCondBr(b,
+									LLVMBuildICmp(b, LLVMIntEQ, v_ret,
+												  l_sbool_const(1), ""),
+									opblocks[jumpaddr],
+									opblocks[opno + 1]);
+					break;
+				}
+			case EEOP_JSONEXPR_PATH:
 				build_EvalXFunc(b, mod, "ExecEvalJson",
 								v_state, op, v_econtext);
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
-
+			case EEOP_JSONEXPR_BEHAVIOR:
+				/* XXX - handle! */
+				break;
+			case EEOP_JSONEXPR_COERCION:
+				/* XXX - handle! */
+				break;
+			case EEOP_JSON_ITEM_COERCION:
+				/* XXX - handle! */
+				break;
 			case EEOP_LAST:
 				Assert(false);
 				break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index b2bda86889..b30f20dd2d 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -133,6 +133,7 @@ void	   *referenced_functions[] =
 	ExecEvalXmlExpr,
 	ExecEvalJsonConstructor,
 	ExecEvalJsonIsPredicate,
+	ExecEvalJsonSkip,
 	ExecEvalJson,
 	MakeExpandedObjectReadOnlyInternal,
 	slot_getmissingattrs,
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 533df86ff7..9c02218155 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -901,7 +901,11 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 	{
 		JsonExpr   *jsexpr = (JsonExpr *) node;
 
-		if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL))
+		/*
+		 * XXX - don't really know why it makes sense to ignore the coercion
+		 * part here.
+		 */
+		if (ExecEvalJsonNeedsSubTransaction(jsexpr, false /* coerce */))
 		{
 			context->max_hazard = PROPARALLEL_UNSAFE;
 			return true;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 1e3f1bbee8..4921589260 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -244,7 +244,11 @@ typedef enum ExprEvalOp
 	EEOP_SUBPLAN,
 	EEOP_JSON_CONSTRUCTOR,
 	EEOP_IS_JSON,
-	EEOP_JSONEXPR,
+	EEOP_JSONEXPR_SKIP,
+	EEOP_JSONEXPR_PATH,
+	EEOP_JSONEXPR_BEHAVIOR,
+	EEOP_JSONEXPR_COERCION,
+	EEOP_JSON_ITEM_COERCION,
 
 	/* aggregation related nodes */
 	EEOP_AGG_STRICT_DESERIALIZE,
@@ -682,12 +686,44 @@ typedef struct ExprEvalStep
 			JsonIsPredicate *pred;	/* original expression node */
 		}			is_json;
 
-		/* for EEOP_JSONEXPR */
+		/* for EEOP_JSONEXPR_PATH */
 		struct
 		{
 			struct JsonExprState *jsestate;
 		}			jsonexpr;
 
+		/* for EEOP_JSONEXPR_SKIP */
+		struct
+		{
+			/* Same as jsonexpr.jsestate */
+			struct JsonExprState *jsestate;
+			int		jump_coercion;
+		}			jsonexpr_skip;
+
+		/* for EEOP_JSONEXPR_BEHAVIOR */
+		struct
+		{
+			/* Same as jsonexpr.jsestate */
+			struct JsonExprState *jsestate;
+			int		jump_onerror_default;
+			int		jump_onempty_default;
+			int		jump_coercion;
+			int		jump_skip_coercion;
+		}		jsonexpr_behavior;
+
+		/* for EEOP_JSONEXPR_COERCION */
+		struct
+		{
+			/* Same as jsonexpr.jsestate */
+			struct JsonExprState *jsestate;
+			int		jump_coercion_error;
+		}		jsonexpr_coercion;
+
+		struct
+		{
+			struct JsonItemCoercionsState *jcstate;
+			int		jump_skip_item_coercion;
+		}		json_item_coercion;
 	}			d;
 } ExprEvalStep;
 
@@ -747,49 +783,112 @@ typedef struct JsonConstructorExprState
 	int			nargs;
 } JsonConstructorExprState;
 
+/*
+ * State for EEOP_JSONEXPR_BEHAVIOR and EEOP_JSONEXPR_COERCION steps that gets
+ * filled in during EEOP_JSONEXPR_PATH.
+ */
+typedef struct JsonExprPostEvalState
+{
+	/* Is JSON item empty? */
+	bool		empty;
+
+	/* Did JSON item evaluation cause an error? */
+	bool		error;
+
+	/* Did coercion evaluation cause an error? */
+	bool		coercion_error;
+
+	/* Has the result been coerced properly? */
+	bool		coercion_done;
+
+	/* Use a sub-transaction when evaluating the coercion */
+	bool		coercion_use_subtrans;
+
+	/* Cache for json_populate_type() called for coercion in some cases */
+	void	   *cache;
+
+	/* Coercion state; same as parent JsonExprState.coercions when not NULL */
+	ExprState   *coercions;
+}	JsonExprPostEvalState;
+
+/*
+ * Information computed before evaluating a JsonExpr expression.
+ */
+typedef struct JsonExprPreEvalState
+{
+	/* value/isnull for JsonExpr.formatted_expr */
+	NullableDatum	formatted_expr;
+
+	/* value/isnull for JsonExpr.pathspec */
+	NullableDatum	pathspec;
+
+	/* JsonPathVariableEvalContext entries for JsonExpr.passing_values */
+	List		   *args;
+}	JsonExprPreEvalState;
+
 /* EEOP_JSONEXPR state, too big to inline */
 typedef struct JsonExprState
 {
 	JsonExpr   *jsexpr;			/* original expression node */
 
+	JsonExprPreEvalState	pre_eval;
+	JsonExprPostEvalState	post_eval;
+
+	/*
+	 * Should use a sub-transaction for path evaluation and subsequent
+	 * coercion evaluation, if any?
+	 */
+	bool		needSubtrans;
+	bool		coercionNeedSubtrans;
+
 	struct
 	{
-		FmgrInfo	func;		/* typinput function for output type */
+		FmgrInfo	*finfo;	/* typinput function for output type */
 		Oid			typioparam;
 	}			input;			/* I/O info for output type */
 
-	NullableDatum
-			   *formatted_expr, /* formatted context item value */
-			   *res_expr,		/* result item */
-			   *coercion_expr,	/* input for JSON item coercion */
-			   *pathspec;		/* path specification value */
+	/*
+	 * Either of the following two is used by ExecEvalJsonExprCoercion() to
+	 * apply coercion to the final result if needed.
+	 */
+	ExprState			   *result_coercion;
+	ExprState			   *coercions;
+} JsonExprState;
 
-	ExprState  *result_expr;	/* coerced to output type */
-	ExprState  *default_on_empty;	/* ON EMPTY DEFAULT expression */
-	ExprState  *default_on_error;	/* ON ERROR DEFAULT expression */
-	List	   *args;			/* passing arguments */
+/*
+ * State for a given member of JsonItemCoercions.
+ */
+typedef struct JsonItemCoercionState
+{
+	/* Expression used to evaluate the coercion */
+	JsonCoercion   *coercion;
 
-	void	   *cache;			/* cache for json_populate_type() */
+	/* ExprEvalStep to compute this coercion's expression */
+	int				jump_eval_expr;
+} JsonItemCoercionState;
 
-	struct JsonCoercionsState
-	{
-		struct JsonCoercionState
-		{
-			JsonCoercion *coercion; /* coercion expression */
-			ExprState  *estate; /* coercion expression state */
-		}			null,
-					string,
-		numeric    ,
-					boolean,
-					date,
-					time,
-					timetz,
-					timestamp,
-					timestamptz,
-					composite;
-	}			coercions;		/* states for coercion from SQL/JSON item
-								 * types directly to the output type */
-} JsonExprState;
+/*
+ * State for evaluating the coercion for a given JSON item using one of
+ * the following coercions.
+ *
+ * Note that while ExecInitExprRec() for JsonItemCoercions will initialize
+ * ExprEvalSteps for all of the members that need it, only one will get run
+ * during a given evaluation of the enclosing JsonExpr depending on the type
+ * of the result JSON item.
+ */
+typedef struct JsonItemCoercionsState
+{
+	JsonItemCoercionState	null;
+	JsonItemCoercionState	string;
+	JsonItemCoercionState	numeric;
+	JsonItemCoercionState	boolean;
+	JsonItemCoercionState	date;
+	JsonItemCoercionState	time;
+	JsonItemCoercionState	timetz;
+	JsonItemCoercionState	timestamp;
+	JsonItemCoercionState	timestamptz;
+	JsonItemCoercionState	composite;
+} JsonItemCoercionsState;
 
 /* functions in execExpr.c */
 extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
@@ -850,14 +949,15 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
 						   ExprContext *econtext, TupleTableSlot *slot);
 extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
 									ExprContext *econtext);
+extern bool ExecEvalJsonSkip(ExprEvalStep *op);
+extern int ExecEvalJsonExprBehavior(ExprEvalStep *op);
 extern void ExecEvalJson(ExprState *state, ExprEvalStep *op,
 						 ExprContext *econtext);
-extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,
-										 JsonReturning *returning,
-										 struct JsonCoercionsState *coercions,
-										 struct JsonCoercionState **pjcstate);
-extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
-											struct JsonCoercionsState *);
+JsonCoercion *ExecGetJsonItemCoercion(struct JsonbValue *item, JsonItemCoercions *coercions);
+extern Datum ExecPrepareJsonItemCoercion(Datum itemval,
+										 JsonItemCoercionsState *coercions,
+										 JsonItemCoercionState **pjcstate);
+extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, bool coerce);
 extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
 										  ExprContext *econtext, bool *isnull,
 										  Datum caseval_datum,
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index bae466b523..6bdd9f5121 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -69,8 +69,10 @@ typedef enum
 
 /* Convenience macros */
 #define DatumGetJsonbP(d)	((Jsonb *) PG_DETOAST_DATUM(d))
+#define DatumGetJsonbValueP(d)	((JsonbValue *) DatumGetPointer(d))
 #define DatumGetJsonbPCopy(d)	((Jsonb *) PG_DETOAST_DATUM_COPY(d))
 #define JsonbPGetDatum(p)	PointerGetDatum(p)
+#define JsonbValuePGetDatum(p)	PointerGetDatum(p)
 #define PG_GETARG_JSONB_P(x)	DatumGetJsonbP(PG_GETARG_DATUM(x))
 #define PG_GETARG_JSONB_P_COPY(x)	DatumGetJsonbPCopy(PG_GETARG_DATUM(x))
 #define PG_RETURN_JSONB_P(x)	PG_RETURN_POINTER(x)
-- 
2.35.3

