From bcaf5c0a90101205b698107d2bf87300be5b061d Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Tue, 5 Jul 2022 16:09:40 +0900
Subject: [PATCH v2 2/3] Evaluate various JsonExpr sub-expressions using parent
 ExprState

These include PASSING args, ON ERROR, ON EMPTY default expressions,
and result_coercion expression.

To do so, this moves a bunch of code from ExecEvalJson(),
ExecEvalJsonExpr(), and ExecEvalJsonExprCoercion(), all of which
would previously run under the single step EEOP_JSONEXPR steps into
a number of new (sub-) ExprEvalSteps that are now added for a given
JsonExpr.

TODO: update llvm_compile_expr() to handle newly added
EEOP_JSONEXPR_* steps.
---
 src/backend/executor/execExpr.c       | 278 +++++++++++++++----
 src/backend/executor/execExprInterp.c | 374 ++++++++++++++++++--------
 src/backend/jit/llvm/llvmjit_expr.c   |  35 ++-
 src/include/executor/execExpr.h       | 136 ++++++++--
 4 files changed, 642 insertions(+), 181 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index a55e5000e2..4dbb24aaee 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2562,46 +2562,238 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				JsonExpr   *jexpr = castNode(JsonExpr, node);
 				JsonExprState *jsestate = palloc0(sizeof(JsonExprState));
+				JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;
+				JsonExprPostEvalState *post_eval = &jsestate->post_eval;
 				ListCell   *argexprlc;
 				ListCell   *argnamelc;
+				int			skip_step_off;
+				int			onempty_step_off;
+				int			onerror_step_off;
+				int			coercion_step_off;
+				int			coercion_error_step_off;
+				int			result_step_off;
+				ExprEvalStep *as;
+				bool		throwErrors =
+					(jexpr->on_error->btype == JSON_BEHAVIOR_ERROR);
 
-				scratch.opcode = EEOP_JSONEXPR;
+				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);
+
+				/*
+				 * 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.pre_eval = pre_eval;
+				scratch.d.jsonexpr_skip.post_eval = post_eval;
+				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 EMPTY behavior correctly.  Might be
+				 * skipped over (along with default expression steps added
+				 * below) if 1) the JSON path evaluation result is not empty,
+				 * 2) ON ERROR behavior must be applied instead.  Jump target
+				 * addressesnecessary to do so will be set later.
+				 */
+				onempty_step_off = -1;
+				if (jexpr->on_empty)
+				{
+					scratch.opcode = EEOP_JSONEXPR_ONEMPTY;
+					scratch.d.jsonexpr_onempty.post_eval = post_eval;
+					scratch.d.jsonexpr_onempty.jexpr = jexpr;
+					onempty_step_off = state->steps_len;
+					ExprEvalPushStep(state, &scratch);
 
-				ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
-								&jsestate->formatted_expr->value,
-								&jsestate->formatted_expr->isnull);
+					/* 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));
+				/*
+				 * Step to handle ON ERROR behavior correctly.  Might be
+				 * skipped over (along with default expression steps added
+				 * below) if no error occurs. Jump target address necessary
+				 * to do so will be set later.
+				 */
+				Assert(jexpr->on_error != NULL);
+				scratch.opcode = EEOP_JSONEXPR_ONERROR;
+				scratch.d.jsonexpr_onerror.post_eval = post_eval;
+				scratch.d.jsonexpr_onerror.jexpr = jexpr;
+				onerror_step_off = state->steps_len;
+				ExprEvalPushStep(state, &scratch);
 
-				ExecInitExprRec((Expr *) jexpr->path_spec, state,
-								&jsestate->pathspec->value,
-								&jsestate->pathspec->isnull);
+				/* 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->res_expr =
-					palloc(sizeof(*jsestate->res_expr));
+				/*
+				 * Step to handle applying coercion correctly.  Might be
+				 * skipped if no coercion needs to be applied separately
+				 * from JSON path evaluation itself.  Jump target address
+				 * necessary to do so will be set later.
+				 */
+				scratch.opcode = EEOP_JSONEXPR_COERCION;
+				scratch.d.jsonexpr_coercion.jsestate = jsestate;
+				scratch.d.jsonexpr_coercion.jump_skip_coercion = state->steps_len + 1;
+				coercion_step_off = state->steps_len;
+				ExprEvalPushStep(state, &scratch);
 
-				jsestate->result_expr = jexpr->result_coercion
-					? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,
-												state->parent,
-												&jsestate->res_expr->value,
-												&jsestate->res_expr->isnull)
-					: NULL;
+				/*
+				 * Step to apply ON ERROR behavior if an error occurs when
+				 * applying coercion.  Will be skipped over (along with default
+				 * expression steps added) if no error occurs.  Jump target
+				 * address necessary to do so will be set later.
+				 */
+				scratch.opcode = EEOP_JSONEXPR_COERCION_ERROR;
+				scratch.d.jsonexpr_coercion_error.post_eval = post_eval;
+				scratch.d.jsonexpr_coercion_error.jexpr = jexpr;
+				coercion_error_step_off = state->steps_len;
+				ExprEvalPushStep(state, &scratch);
+
+				/* Push steps to evaluate the default ON ERROR expression. */
+				if (jexpr->on_error->default_expr)
+					ExecInitExprRec((Expr *) jexpr->on_error->default_expr,
+									 state, resv, resnull);
+
+				/*
+				 * Step to handle applying result_coercion if needed.  Might
+				 * be skipped if computing result_coercion is rendered
+				 * unnecessary by earlier steps.  Jump target address to do
+				 * so will be set later.
+				 */
+				result_step_off = state->steps_len;
+				if (jexpr->result_coercion && jexpr->result_coercion->expr)
+				{
+					Datum	   *save_innermost_caseval;
+					bool	   *save_innermost_isnull;
+
+					scratch.opcode = EEOP_JSONEXPR_RESULT;
+					scratch.d.jsonexpr_result.post_eval = post_eval;
+					scratch.d.jsonexpr_result.jexpr = jexpr;
+					ExprEvalPushStep(state, &scratch);
+
+					/* Steps to compute result_coercion expression. */
+					save_innermost_caseval = state->innermost_caseval;
+					save_innermost_isnull = state->innermost_casenull;
+
+					state->innermost_caseval = resv;
+					state->innermost_casenull = resnull;
+
+					ExecInitExprRec((Expr *) jexpr->result_coercion->expr,
+									 state, resv, resnull);
+
+					state->innermost_caseval = save_innermost_caseval;
+					state->innermost_casenull = save_innermost_isnull;
+				}
+
+				/*
+				 * 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;
+
+				/* EEOP_JSONEXPR_ONEMPTY */
+				if (onempty_step_off >= 0)
+				{
+					as = &state->steps[onempty_step_off];
+					as->d.jsonexpr_onempty.jump_error = onerror_step_off;
+					as->d.jsonexpr_onempty.jump_coercion = coercion_step_off;
+				}
+
+				/* EEOP_JSONEXPR_ONERROR */
+				as = &state->steps[onerror_step_off];
+				as->d.jsonexpr_onerror.jump_coercion = coercion_step_off;
 
-				jsestate->default_on_empty = !jexpr->on_empty ? NULL :
-					ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
-								 state->parent);
+				/* EEOP_JSONEXPR_COERCION */
+				as = &state->steps[coercion_step_off];
+				as->d.jsonexpr_coercion.jump_coercion_error = coercion_error_step_off;
 
-				jsestate->default_on_error =
-					ExecInitExpr((Expr *) jexpr->on_error->default_expr,
-								 state->parent);
+				/* EEOP_JSONEXPR_COERCION_ERROR */
+				as = &state->steps[coercion_error_step_off];
+				as->d.jsonexpr_coercion_error.jump_coercion = coercion_step_off;
+				as->d.jsonexpr_coercion_error.jump_skip_coercion_error = result_step_off;
 
+				/* EEOP_JSONEXPR_RESULT */
+				as = &state->steps[result_step_off];
+				as->d.jsonexpr_result.jump_skip_result = 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, &jsestate->coercions);
+				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))
 				{
@@ -2616,31 +2808,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					jsestate->input.finfo = finfo;
 				}
 
-				jsestate->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);
-					var->estate = ExecInitExpr(argexpr, state->parent);
-					var->econtext = NULL;
-					var->mcxt = NULL;
-					var->evaluated = false;
-					var->value = (Datum) 0;
-					var->isnull = true;
-
-					jsestate->args =
-						lappend(jsestate->args, var);
-				}
-
-				jsestate->cache = NULL;
-
+				/* Set up coercion related data structures. */
 				if (jexpr->coercions)
 				{
 					JsonCoercion **coercion;
@@ -2648,11 +2816,8 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					Datum	   *caseval;
 					bool	   *casenull;
 
-					jsestate->coercion_expr =
-						palloc(sizeof(*jsestate->coercion_expr));
-
-					caseval = &jsestate->coercion_expr->value;
-					casenull = &jsestate->coercion_expr->isnull;
+					caseval = resv;
+					casenull = resnull;
 
 					for (cstate = &jsestate->coercions.null,
 						 coercion = &jexpr->coercions->null;
@@ -2667,7 +2832,6 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					}
 				}
 
-				ExprEvalPushStep(state, &scratch);
 				break;
 			}
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0512a81c7c..5309017027 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,13 @@ 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_ONERROR,
+		&&CASE_EEOP_JSONEXPR_ONEMPTY,
+		&&CASE_EEOP_JSONEXPR_COERCION,
+		&&CASE_EEOP_JSONEXPR_COERCION_ERROR,
+		&&CASE_EEOP_JSONEXPR_RESULT,
 		&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
 		&&CASE_EEOP_AGG_DESERIALIZE,
 		&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1817,13 +1832,182 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
-		EEO_CASE(EEOP_JSONEXPR)
+		EEO_CASE(EEOP_JSONEXPR_SKIP)
+		{
+			JsonExprPreEvalState *pre_eval = op->d.jsonexpr_skip.pre_eval;
+			JsonExprPostEvalState *post_eval = op->d.jsonexpr_skip.post_eval;
+
+			/*
+			 * 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 (pre_eval->formatted_expr.isnull || pre_eval->pathspec.isnull)
+			{
+				post_eval->coercion = NULL;
+				/* A signal to coercion step to not use a sub-trancaction. */
+				post_eval->coercion_error = true;
+				*op->resvalue = 0;
+				*op->resnull = true;
+				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_ONEMPTY)
+		{
+			JsonExprPostEvalState *post_eval = op->d.jsonexpr_onempty.post_eval;
+			JsonExpr *jexpr = op->d.jsonexpr_onempty.jexpr;
+
+			/*
+			 * Skip ON EMPTY handling if not empty or if it must be handled
+			 * with a ON ERROR behavior instead.
+			 */
+			if (post_eval->error)
+				EEO_JUMP(op->d.jsonexpr_onempty.jump_error);
+			else if (!post_eval->empty)
+				EEO_JUMP(op->d.jsonexpr_onempty.jump_coercion);
+
+			/*
+			 * If a non-default behavior is specified, get the appropriate
+			 * value and skip over to the coercion step.
+			 */
+			if (jexpr->on_empty->btype != JSON_BEHAVIOR_DEFAULT)
+			{
+				*op->resvalue = ExecEvalJsonBehavior(jexpr->on_empty,
+													 op->resnull);
+				EEO_JUMP(op->d.jsonexpr_onempty.jump_coercion);
+			}
+
+			/*
+			 * Evaluate the default ON EMPTY expression.
+			 *
+			 * Nothing for _COERCION and _RESULT steps to do as the
+			 * expression we are about to compute has been coerced
+			 * appropriately by the parser.
+			 */
+			post_eval->coercion_done = true;
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JSONEXPR_ONERROR)
+		{
+			JsonExprPostEvalState *post_eval = op->d.jsonexpr_onerror.post_eval;
+			JsonExpr *jexpr = op->d.jsonexpr_onerror.jexpr;
+
+			/* Nothing to do if no error has been found to begin with. */
+			if (!post_eval->error)
+				EEO_JUMP(op->d.jsonexpr_onerror.jump_coercion);
+
+			/*
+			 * If a non-default behavior is specified, get the appropriate
+			 * value and skip over to the coercion step.
+			 */
+			if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT)
+			{
+				/* Execute non-default ON ERROR behavior */
+				*op->resvalue = ExecEvalJsonBehavior(jexpr->on_error,
+													 op->resnull);
+				EEO_JUMP(op->d.jsonexpr_onerror.jump_coercion);
+			}
+
+			/*
+			 * Evaluate the default ON ERROR expression.  See the note
+			 * above regarding coercion.
+			 */
+			post_eval->coercion_done = true;
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JSONEXPR_COERCION_ERROR)
+		{
+			JsonExprPostEvalState *post_eval = op->d.jsonexpr_coercion_error.post_eval;
+			JsonExpr *jexpr = op->d.jsonexpr_coercion_error.jexpr;
+
+			/* Nothing to if no error was found when applying coercion. */
+			if (!post_eval->coercion_error)
+				EEO_JUMP(op->d.jsonexpr_coercion_error.jump_skip_coercion_error);
+
+			/*
+			 * If a non-default behavior is specified, get the appropriate
+			 * value and skip over to the coercion step.
+			 */
+			if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT)
+			{
+				/* Execute non-default ON ERROR behavior */
+				*op->resvalue = ExecEvalJsonBehavior(jexpr->on_error,
+													 op->resnull);
+
+				/* Coercion the ON ERROR expression. */
+				post_eval->coercion_done = false;
+				post_eval->coercion = NULL;
+				EEO_JUMP(op->d.jsonexpr_coercion_error.jump_coercion);
+			}
+
+			/*
+			 * Evaluate the default ON ERROR expression.  See the note
+			 * above regarding coercion.
+			 */
+			post_eval->coercion_done = true;
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JSONEXPR_COERCION)
+		{
+			JsonExprState *jsestate = op->d.jsonexpr_coercion.jsestate;
+			JsonExprPostEvalState *post_eval = &jsestate->post_eval;
+			bool	coercionUseSubtrans;
+
+			/*
+			 * Don't use a sub-transaction if coercing the ON ERROR expression
+			 * after the previous coercion attempt caused an error or if got
+			 * here from EEOP_JSONEXPR_SKIP, so that any that any errors that
+			 * are encountered downstream this time are thrown there, that is,
+			 * not rethrown to be handled here.
+			 *
+			 * XXX - doesn't that violate ON ERROR behavior?  Actually, that is
+			 * how it behaved even before this expression eval logic rewrite!
+			 */
+			coercionUseSubtrans =
+				(jsestate->coercionNeedSubtrans &&
+				 !post_eval->coercion_error);
+
+			/* ExecEvalJson() may have found coercion to be unnecessary. */
+			if (post_eval->coercion_done)
+				EEO_JUMP(op->d.jsonexpr_coercion.jump_skip_coercion);
+
+			post_eval->coercion_error = false;
+			*op->resvalue =
+				ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
+										 *op->resvalue,
+										 op->resnull, NULL,
+										 &post_eval->coercion_error,
+										 coercionUseSubtrans);
+			if (post_eval->coercion_error)
+				EEO_JUMP(op->d.jsonexpr_coercion.jump_coercion_error);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JSONEXPR_RESULT)
+		{
+			JsonExprPostEvalState *post_eval = op->d.jsonexpr_result.post_eval;
+
+			if (post_eval->coercion_done)
+				EEO_JUMP(op->d.jsonexpr_result.jump_skip_result);
+
+			/* Evaluate result_coercion expression. */
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_LAST)
 		{
 			/* unreachable */
@@ -4602,8 +4786,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 +4811,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 +4828,25 @@ 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->coercion ?
+						post_eval->coercion->estate : NULL;
+
+	/*
+	 * Initially assume we will indeed perform the coercion either
+	 * using post_eval->coercion, with the input function for the
+	 * output type, or with json_populate_type().
+	 */
+	post_eval->coercion_done = true;
 
 	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) ||
@@ -4672,18 +4864,16 @@ 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)
-	{
-		jsestate->res_expr->value = res;
-		jsestate->res_expr->isnull = *isNull;
-
-		res = ExecEvalExpr(jsestate->result_expr, econtext, isNull);
-	}
+	/*
+	 * Let the caller know that no coercion was done here, so it can
+	 * coerce with jexpr->result_coercion if there's one.
+	 */
+	post_eval->coercion_done = false;
 
 	return res;
 }
@@ -4717,17 +4907,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)
 	{
@@ -4832,9 +5035,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 +5105,6 @@ typedef struct
 {
 	JsonPath   *path;
 	bool	   *error;
-	bool		coercionInSubtrans;
 } ExecEvalJsonExprContext;
 
 static Datum
@@ -4916,16 +5115,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;
@@ -4937,16 +5137,19 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 		case JSON_VALUE_OP:
 			{
 				struct JsonCoercionState *jcstate;
-				JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
-												jsestate->args);
+				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;
 
@@ -4964,7 +5167,6 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 												  jsestate->jsexpr->returning,
 												  &jsestate->coercions,
 												  &jcstate);
-
 				if (jcstate->coercion &&
 					(jcstate->coercion->via_io ||
 					 jcstate->coercion->via_populate))
@@ -4972,6 +5174,7 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 					if (error)
 					{
 						*error = true;
+						*resnull = true;
 						return (Datum) 0;
 					}
 
@@ -4983,32 +5186,34 @@ 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 if (jcstate->estate == NULL)
+				{
+					/* No coercion needed */
+					post_eval->coercion_done = true;
+				}
+				else
+				{
+					/* Coerce using specific expression */
+					post_eval->coercion = jcstate;
+				}
 				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 +5226,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,24 +5246,9 @@ 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
@@ -5082,64 +5276,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 = pre_eval->formatted_expr.value;
+	path = DatumGetJsonPathP(pre_eval->pathspec.value);
 
-	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..c133600005 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2359,11 +2359,44 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
-			case EEOP_JSONEXPR:
+			case EEOP_JSONEXPR_SKIP:
+				/* XXX - handle! */
+				break;
+			case EEOP_JSONEXPR_PATH:
 				build_EvalXFunc(b, mod, "ExecEvalJson",
 								v_state, op, v_econtext);
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
+			case EEOP_JSONEXPR_ONERROR:
+				/* XXX - handle! */
+				break;
+			case EEOP_JSONEXPR_ONEMPTY:
+				/* XXX - handle! */
+				break;
+			case EEOP_JSONEXPR_COERCION:
+				/* XXX - handle! */
+				break;
+			case EEOP_JSONEXPR_COERCION_ERROR:
+				/* XXX - handle! */
+				break;
+			case EEOP_JSONEXPR_RESULT:
+				{
+					/*
+					 * Perform the step at jsonexpr_result.jumpnext if the
+					 * coercion already done, which effectively skips the
+					 * step(s) to perform the coercion using the result
+					 * expression.
+					 * XXX - Hoping this DTRT.
+					 */
+					LLVMBuildCondBr(b,
+									LLVMBuildICmp(b, LLVMIntEQ,
+												  l_sbool_const(op->d.jsonexpr_result.post_eval->coercion_done),
+												  l_sbool_const(1),
+												  ""),
+									opblocks[op->d.jsonexpr_result.jump_skip_result],
+									opblocks[opno + 1]);
+				}
+				break;
 
 			case EEOP_LAST:
 				Assert(false);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index e55a572854..358b831f7a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -244,7 +244,13 @@ typedef enum ExprEvalOp
 	EEOP_SUBPLAN,
 	EEOP_JSON_CONSTRUCTOR,
 	EEOP_IS_JSON,
-	EEOP_JSONEXPR,
+	EEOP_JSONEXPR_SKIP,
+	EEOP_JSONEXPR_PATH,
+	EEOP_JSONEXPR_ONERROR,
+	EEOP_JSONEXPR_ONEMPTY,
+	EEOP_JSONEXPR_COERCION,
+	EEOP_JSONEXPR_COERCION_ERROR,
+	EEOP_JSONEXPR_RESULT,
 
 	/* aggregation related nodes */
 	EEOP_AGG_STRICT_DESERIALIZE,
@@ -682,12 +688,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 JsonExprPreEvalState *pre_eval;
+			struct JsonExprPostEvalState *post_eval;
+			int		jump_coercion;
+		}			jsonexpr_skip;
+
+		/* for EEOP_JSONEXPR_ONEMPTY */
+		struct
+		{
+			/* Points to jsonexpr.jsestate->post_eval */
+			struct JsonExprPostEvalState *post_eval;
+			/* Same as jsonexpr.jsestate->jsexpr */
+			JsonExpr *jexpr;
+			int		jump_error;
+			int		jump_coercion;
+		}		jsonexpr_onempty;
+
+		/* for EEOP_JSONEXPR_ONERROR */
+		struct
+		{
+			/* Points to jsonexpr.jsestate->post_eval */
+			struct JsonExprPostEvalState *post_eval;
+			/* Same as jsonexpr.jsestate->jsexpr */
+			JsonExpr *jexpr;
+			int		jump_coercion;
+		}		jsonexpr_onerror;
+
+		/* for EEOP_JSONEXPR_COERCION */
+		struct
+		{
+			/* Same as jsonexpr.jsestate */
+			struct JsonExprState *jsestate;
+			int		jump_skip_coercion;
+			int		jump_coercion_error;
+		}		jsonexpr_coercion;
+
+		/* for EEOP_JSONEXPR_COERCION_ERROR */
+		struct
+		{
+			/* Points to jsonexpr.jsestate->post_eval */
+			struct JsonExprPostEvalState *post_eval;
+			/* Same as jsonexpr.jsestate->jsexpr */
+			JsonExpr *jexpr;
+			int		jump_skip_coercion_error;
+			int		jump_coercion;
+		}		jsonexpr_coercion_error;
+
+		/* for EEOP_JSONEXPR_RESULT */
+		struct
+		{
+			/* Points to jsonexpr.jsestate->post_eval */
+			struct JsonExprPostEvalState *post_eval;
+			/* Same as jsonexpr.jsestate->jsexpr */
+			JsonExpr *jexpr;
+			int		jump_skip_result;
+		}		jsonexpr_result;
 	}			d;
 } ExprEvalStep;
 
@@ -747,30 +812,69 @@ typedef struct JsonConstructorExprState
 	int			nargs;
 } JsonConstructorExprState;
 
+/*
+ * State for (EEOP_JSONEXPR_) ONERROR, ONEMPTY, COERCION, COERCION_ERROR,
+ * and RESULT steps that will run after 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;
+
+	/* Cache for json_populate_type() called for coercion in some cases */
+	void	   *cache;
+
+	/*
+	 * Coercion to be applied to JSON item returned by JsonPathValue(),
+	 * set by ExecPrepareJsonItemCoercion(). */
+	struct JsonCoercionState *coercion;
+}	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	*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 */
-
-	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 */
-
-	void	   *cache;			/* cache for json_populate_type() */
-
 	struct JsonCoercionsState
 	{
 		struct JsonCoercionState
@@ -779,7 +883,7 @@ typedef struct JsonExprState
 			ExprState  *estate; /* coercion expression state */
 		}			null,
 					string,
-		numeric    ,
+					numeric,
 					boolean,
 					date,
 					time,
-- 
2.35.3

