From 5bbb775e1fb6b69d47fceae7c2c4060a309fb719 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 12 Aug 2022 19:35:56 +0300
Subject: [PATCH v7 4/4] Split JsonExpr execution into steps

Add test for errors in ON EMPTY

Add test for errors in ON EMPTY

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.

Catch errors in JsonExpr ON EMPTY behavior

Remove ExecEvalJsonExprContext

Remove local variable

Refactor JSonExpr execution: remove ItemCoercion step, use new Subtrans step

Remove JsonExprBehavior step

Use context for ExecInitJsonExpr()

Simplify json item coercions

Replace JsonExprCoerce steps with JsonExprIoCoerce and JsonExprPopulate steps

Move and rename ExecEvalJsonNeedsSubTransaction(), consider more cases in it

Inline ExecEvalJsonExpr() into ExecEvalJson()

Move local variables

Use LLVMBuildSwitch() in EEOP_JSONEXPR
---
 src/backend/executor/execExpr.c             | 691 ++++++++++++++++----
 src/backend/executor/execExprInterp.c       | 630 +++++++-----------
 src/backend/jit/llvm/llvmjit_expr.c         | 122 +++-
 src/backend/jit/llvm/llvmjit_types.c        |   3 +
 src/backend/parser/parse_expr.c             |   6 +
 src/backend/utils/adt/jsonpath_exec.c       |  68 ++
 src/include/executor/execExpr.h             | 150 +++--
 src/include/nodes/primnodes.h               |   9 +-
 src/include/utils/jsonb.h                   |   2 +
 src/include/utils/jsonpath.h                |   3 -
 src/test/regress/expected/jsonb_sqljson.out |  17 +
 src/test/regress/sql/jsonb_sqljson.sql      |   7 +
 12 files changed, 1085 insertions(+), 623 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 1c9782de15f..1aa843698ab 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -86,7 +86,8 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
 								  FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
 								  int transno, int setno, int setoff, bool ishash,
 								  bool nullcheck);
-
+static void ExecInitJsonExpr(ExprEvalStep *scratch, JsonExpr *jexpr, ExprState *state,
+				 Datum *resv, bool *resnull);
 
 static ExprState *
 ExecInitExprInternal(Expr *node, PlanState *parent, ParamListInfo ext_params,
@@ -178,7 +179,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,
@@ -2561,149 +2562,8 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_JsonExpr:
 			{
 				JsonExpr   *jexpr = castNode(JsonExpr, node);
-				JsonExprState *jsestate;
-				JsonCoercion *result_coercion = jexpr->result_coercion;
-				ListCell   *argexprlc;
-				ListCell   *argnamelc;
-
-				/* JSON_TABLE preudo-function returns context item as a result */
-				if (jexpr->op == JSON_TABLE_OP)
-				{
-					ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
-									resv, resnull);
-					break;
-				}
-
-				jsestate = palloc0(sizeof(JsonExprState));
-				jsestate->jsexpr = jexpr;
-
-				scratch.opcode = EEOP_JSONEXPR;
-				scratch.d.jsonexpr.jsestate = jsestate;
-
-				ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
-								&jsestate->formatted_expr.value,
-								&jsestate->formatted_expr.isnull);
-
-				ExecInitExprRec((Expr *) jexpr->path_spec, state,
-								&jsestate->pathspec.value,
-								&jsestate->pathspec.isnull);
-
-				if (result_coercion)
-				{
-					jsestate->result_coercion.type = result_coercion->ctype;
-
-					if (result_coercion->expr)
-						jsestate->result_coercion.estate =
-							ExecInitExprWithCaseValue((Expr *) result_coercion->expr,
-													  state->parent,
-													  &jsestate->coercion_expr.value,
-													  &jsestate->coercion_expr.isnull);
-
-					/* Skip coercion of NULLs for non-domain types */
-					jsestate->skip_null_coercion =
-						getBaseType(jexpr->returning->typid) == jexpr->returning->typid;
-				}
-				else
-					jsestate->result_coercion.type = JSON_COERCION_NONE;
-
-				/* Additional coercion of JSON_QUERY(OMIT QUOTES) */
-				jsestate->io_coercion.type = JSON_COERCION_VIA_IO;
-
-				if (jexpr->on_empty)
-				{
-					jsestate->default_on_empty =
-						ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
-									 state->parent);
-
-					jsestate->default_on_empty_can_throw =
-						ExecEvalExprCanThrowErrors(jexpr->on_empty->default_expr);
-				}
-
-				jsestate->default_on_error =
-					ExecInitExpr((Expr *) jexpr->on_error->default_expr,
-								 state->parent);
-
-
-				/* Initialize type input info if I/O coercion can be used */
-				if (jexpr->omit_quotes ||
-					jsestate->result_coercion.type == JSON_COERCION_VIA_IO)
-				{
-					Oid			typinput;
-
-					/* lookup the result type's input function */
-					getTypeInputInfo(jexpr->returning->typid, &typinput,
-									 &jsestate->input.typioparam);
-					fmgr_info(typinput, &jsestate->input.func);
-				}
-
-				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;
-
-				if (jexpr->coercions)
-				{
-					JsonCoercion **coercion;
-					JsonCoercionState *cstate;
-					Datum	   *caseval;
-					bool	   *casenull;
-
-					jsestate->item_coercions = palloc0(sizeof(*jsestate->item_coercions));
-
-					caseval = &jsestate->coercion_expr.value;
-					casenull = &jsestate->coercion_expr.isnull;
-
-					for (cstate = &jsestate->item_coercions->null,
-						 coercion = &jexpr->coercions->null;
-						 coercion <= &jexpr->coercions->composite;
-						 coercion++, cstate++)
-					{
-						if (!*coercion)
-							/* Missing coercion here means missing cast */
-							cstate->type = JSON_COERCION_ERROR;
-						else if ((*coercion)->ctype == JSON_COERCION_VIA_EXPR)
-						{
-							Assert((*coercion)->expr);
-							cstate->estate =
-								ExecInitExprWithCaseValue((Expr *) (*coercion)->expr,
-														  state->parent,
-														  caseval,
-														  casenull);
-							cstate->type = JSON_COERCION_VIA_EXPR;
-						}
-						else if ((*coercion)->ctype == JSON_COERCION_NONE)
-							cstate->type = JSON_COERCION_NONE;
-						else
-						{
-							/* There should not be other coercion types */
-							Assert(0);
-							cstate->type = JSON_COERCION_ERROR;
-						}
-					}
-				}
-
-				ExprEvalPushStep(state, &scratch);
 
+				ExecInitJsonExpr(&scratch, jexpr, state, resv, resnull);
 				break;
 			}
 
@@ -4296,3 +4156,546 @@ ExecBuildParamSetEqual(TupleDesc desc,
 
 	return state;
 }
+
+static void
+ExecInitJsonCoercionExpr(ExprState *state, Expr *coercion,
+						 Datum *resv, bool *resnull)
+{
+	Datum	   *save_innermost_caseval;
+	bool	   *save_innermost_isnull;
+
+	/* Push step(s) to compute expr using CaseTestValue. */
+	save_innermost_caseval = state->innermost_caseval;
+	save_innermost_isnull = state->innermost_casenull;
+
+	state->innermost_caseval = resv;
+	state->innermost_casenull = resnull;
+
+	ExecInitExprRec(coercion, state, resv, resnull);
+
+	state->innermost_caseval = save_innermost_caseval;
+	state->innermost_casenull = save_innermost_isnull;
+}
+
+/* Context for passing common fields used for JsonExpr initialization */
+typedef struct ExecInitJsonContext
+{
+	ExprState  *state;
+	ExprEvalStep *scratch;
+	JsonExprState *jsestate;
+	Datum	   *resv;
+	bool	   *resnull;
+	List	   *adjust_jumps;		/* list of step numbers of jump_done steps */
+	List	   *adjust_subtrans;	/* list of step numbers of subtrans steps */
+} ExecInitJsonContext;
+
+static int
+ExecInitJsonCoercion(ExecInitJsonContext *cxt, Node *expr,
+					 bool need_subtrans)
+{
+	ExprState  *state = cxt->state;
+	ExprEvalStep *scratch = cxt->scratch;
+	int			step_off = state->steps_len;
+
+	if (IsA(expr, JsonCoercion))
+	{
+		JsonCoercion *coercion = (JsonCoercion *) expr;
+
+		if (coercion->ctype == JSON_COERCION_NONE)
+			return -1;	/* no coercion is needed */
+	}
+
+	/* Check whether expression really needs sub-transactions */
+	if (need_subtrans)
+		need_subtrans = ExecEvalExprCanThrowErrors(expr);
+
+	/* Emit EEOP_JSONEXPR_SUBTRANS step */
+	if (need_subtrans)
+	{
+		cxt->adjust_subtrans =
+			lappend_int(cxt->adjust_subtrans, state->steps_len);
+
+		state->flags |= EEO_FLAG_HAVE_SUBTRANS;
+		scratch->opcode = EEOP_SUBTRANS;
+		scratch->d.subtrans.jump_next = -1;		/* computed later */
+		scratch->d.subtrans.jump_done = -1;		/* computed later */
+		scratch->d.subtrans.jump_error = -1;	/* computed later */
+		ExprEvalPushStep(state, scratch);
+	}
+
+	/* Emit expression step, if needed. */
+	if (IsA(expr, JsonCoercion))
+	{
+		JsonCoercion *coercion = (JsonCoercion *) expr;
+
+		if (coercion->ctype == JSON_COERCION_VIA_EXPR)
+			/* Push step(s) to compute coercion->expr */
+			ExecInitJsonCoercionExpr(state, (Expr *) coercion->expr,
+									 cxt->resv, cxt->resnull);
+		else if (coercion->ctype == JSON_COERCION_VIA_POPULATE)
+		{
+			/* Push step for coercion using json_populate_type() */
+			scratch->opcode = EEOP_JSONEXPR_POPULATE;
+			scratch->d.jsonexpr_populate.jsexpr = cxt->jsestate->jsexpr;
+			scratch->d.jsonexpr_populate.cache = NULL;
+			ExprEvalPushStep(state, scratch);
+		}
+		else if (coercion->ctype == JSON_COERCION_VIA_IO)
+		{
+			/* Push step for coercion via I/O */
+			FmgrInfo   *finfo;
+			Oid			typinput;
+			Oid			typioparam;
+			JsonReturning *returning = cxt->jsestate->jsexpr->returning;
+
+			/* lookup the result type's input function */
+			getTypeInputInfo(returning->typid, &typinput,
+							 &typioparam);
+			finfo = palloc0(sizeof(FmgrInfo));
+			fmgr_info(typinput, finfo);
+
+			scratch->opcode = EEOP_JSONEXPR_IOCOERCE;
+			scratch->d.jsonexpr_iocoerce.finfo = finfo;
+			scratch->d.jsonexpr_iocoerce.typioparam = typioparam;
+			scratch->d.jsonexpr_iocoerce.typmod = returning->typmod;
+			ExprEvalPushStep(state, scratch);
+		}
+		else
+		{
+			Assert(0); /* No-coercion case must be checked at the beginning */
+		}
+	}
+	else
+		/* Push step(s) to compute expr */
+		ExecInitExprRec((Expr *) expr, state, cxt->resv, cxt->resnull);
+
+	if (need_subtrans)
+	{
+		/* Step for returning back to EEOP_JSONEXPR_SUBTRANS step. */
+		scratch->opcode = EEOP_DONE;
+		ExprEvalPushStep(state, scratch);
+
+		state->steps[step_off].d.subtrans.jump_next = state->steps_len;
+	}
+	else
+	{
+		/*
+		 * Emit JUMP step to jump to end of JsonExpr code, because
+		 * evaluating the coercion or 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);
+
+		/*
+		 * Don't know address for that jump yet, compute once the whole
+		 * JsonExpr is built.  So remember JUMP step address to set
+		 * the actual jump target addreess.
+		 */
+		cxt->adjust_jumps =
+			lappend_int(cxt->adjust_jumps, state->steps_len - 1);
+	}
+
+	return step_off;
+}
+
+/*
+ * Push steps to evaluate a JsonItemCoercions, which contains the state
+ * for evaluating all possible coercions that may need to be applied to
+ * a JSON item coming from evaluating the parent JsonExpr.
+ */
+static JsonItemCoercionsState *
+ExecInitJsonItemCoercions(ExecInitJsonContext *cxt,
+						  JsonItemCoercions *coercions,
+						  bool need_subtrans)
+{
+	JsonItemCoercionsState *jcstate = palloc0(sizeof(*jcstate));
+	JsonCoercion **coercion;
+	int		   *step;
+
+	/* Push the steps of individual coercion's expression, if needed. */
+	for (step = &jcstate->null,
+		 coercion = &coercions->null;
+		 coercion <= &coercions->composite;
+		 coercion++, step++)
+	{
+		if (*coercion)
+		{
+			if ((*coercion)->ctype == JSON_COERCION_VIA_EXPR)
+				*step = ExecInitJsonCoercion(cxt, (Node *) *coercion,
+											 need_subtrans);
+			else if ((*coercion)->ctype == JSON_COERCION_NONE)
+				*step = 0;	/* No coercion is needed */
+			else
+			{
+				Assert(0);	/* There should not be other coercion types */
+				*step = -1;
+			}
+		}
+		else
+			*step = -1;  /* This means there is no cast to output type */
+	}
+
+	return jcstate;
+}
+
+/*
+ * Evaluate a JSON error/empty behavior result.
+ */
+static Datum
+ExecJsonBehavior(JsonBehavior *behavior, bool *is_null)
+{
+	*is_null = false;
+
+	switch (behavior->btype)
+	{
+		case JSON_BEHAVIOR_EMPTY_ARRAY:
+			return JsonbPGetDatum(JsonbMakeEmptyArray());
+
+		case JSON_BEHAVIOR_EMPTY_OBJECT:
+			return JsonbPGetDatum(JsonbMakeEmptyObject());
+
+		case JSON_BEHAVIOR_TRUE:
+			return BoolGetDatum(true);
+
+		case JSON_BEHAVIOR_FALSE:
+			return BoolGetDatum(false);
+
+		case JSON_BEHAVIOR_NULL:
+		case JSON_BEHAVIOR_UNKNOWN:
+		case JSON_BEHAVIOR_EMPTY:
+			*is_null = true;
+			return (Datum) 0;
+
+		case JSON_BEHAVIOR_DEFAULT:
+			/* Always handled in the caller. */
+			Assert(false);
+			return (Datum) 0;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
+			return (Datum) 0;
+	}
+}
+
+/*
+ * Push steps to evaluate ON ERROR/EMPTY behavior: it is either DEFAULT
+ * expression or constant with optional coercion.
+ *
+ * Returned first step number, or -1 if no steps.
+ */
+static int
+ExecInitJsonBehavior(ExecInitJsonContext *cxt, JsonBehavior *behavior,
+					 bool need_subtrans, int *jump_coercion_step_off)
+{
+	if (!behavior || behavior->btype == JSON_BEHAVIOR_ERROR)
+		return -1;
+
+	if (behavior->default_expr)
+		return ExecInitJsonCoercion(cxt, behavior->default_expr,
+									need_subtrans);
+	else
+	{
+		ExprState  *state = cxt->state;
+		ExprEvalStep *scratch = cxt->scratch;
+		JsonCoercion *coercion = cxt->jsestate->jsexpr->result_coercion;
+		bool		isnull;
+		Datum		val = ExecJsonBehavior(behavior, &isnull);
+		int			step_off = state->steps_len;
+
+		/* Step to emit resulting constant */
+		scratch->opcode = EEOP_CONST;
+		scratch->d.constval.value = val;
+		scratch->d.constval.isnull = isnull;
+		ExprEvalPushStep(state, scratch);
+
+		/* Step to the coercion or the end of expression */
+		scratch->opcode = EEOP_JUMP;
+		scratch->d.jump.jumpdone = -1;	/* computed later */
+		ExprEvalPushStep(state, scratch);
+
+		/*
+		 * If it is a jump to coercion, return it through the special
+		 * output parameter, else append it to jump_done list.
+		 */
+		if (coercion && coercion->ctype != JSON_COERCION_NONE)
+			*jump_coercion_step_off = state->steps_len - 1;
+		else
+			cxt->adjust_jumps =
+				lappend_int(cxt->adjust_jumps, state->steps_len - 1);
+
+		return step_off;
+	}
+}
+
+/*
+ * Push steps to evaluate a JsonExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitJsonExpr(ExprEvalStep *scratch, JsonExpr *jexpr, ExprState *state,
+				 Datum *resv, bool *resnull)
+{
+	ExecInitJsonContext cxt;
+	JsonExprState *jsestate;
+	JsonExprPreEvalState *pre_eval;
+	ExprEvalStep *as;
+	ListCell   *argexprlc;
+	ListCell   *argnamelc;
+	ListCell   *lc;
+	int			skip_step_off;
+	int			execpath_step_off;
+	int			onempty_step_off;
+	int			onerror_step_off;
+	int			onempty_jump_coercion_step_off = -1;
+	int			onerror_jump_coercion_step_off = -1;
+	int			coercion_step_off = -1;
+	int			coercion_subtrans_step_off = -1;
+	int			io_coercion_step_off = -1;
+	int			done_off;
+	bool		need_subtrans;
+	bool		skip_null_coercion;
+
+	/* JSON_TABLE preudo-function returns context item as a result */
+	if (jexpr->op == JSON_TABLE_OP)
+	{
+		ExecInitExprRec((Expr *) jexpr->formatted_expr, state, resv, resnull);
+		return;
+	}
+
+	jsestate = palloc0(sizeof(JsonExprState));
+	jsestate->jsexpr = jexpr;
+	pre_eval = &jsestate->pre_eval;
+
+	cxt.state = state;
+	cxt.scratch = scratch;
+	cxt.jsestate = jsestate;
+	cxt.resv = resv;
+	cxt.resnull = resnull;
+	cxt.adjust_jumps = NIL;
+	cxt.adjust_subtrans = NIL;
+
+	/*
+	 * Set if coercion or DEFAULT ON EMPTY expression, which runs
+	 * separately from path evaluation and whose errors must be caught
+	 * and handled per ON ERROR behavior, must use a sub-transaction.
+	 */
+	need_subtrans = jexpr->on_error->btype != JSON_BEHAVIOR_ERROR;
+
+	/*
+	 * 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;
+	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);
+
+		/*
+		 * A separate ExprState is not necessary for these expressions
+		 * when being evaluated for a JsonExpr, like in this case,
+		 * because they will evaluated as the steps of the JsonExpr.
+		 */
+		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;
+	execpath_step_off = state->steps_len;
+	ExprEvalPushStep(state, scratch);
+
+	/*
+	 * Push steps to control the evaluation of expressions based
+	 * on the result of JSON path evaluation.
+	 */
+
+	/* Step(s) to evaluate ON EMPTY behavior */
+	onempty_step_off = ExecInitJsonBehavior(&cxt, jexpr->on_empty,
+											need_subtrans,
+											&onempty_jump_coercion_step_off);
+
+	/* Step(s) to evaluate ON ERROR behavior */
+	onerror_step_off = ExecInitJsonBehavior(&cxt, jexpr->on_error,
+											false, /* sub-transactions are not needed here */
+											&onerror_jump_coercion_step_off);
+
+	Assert(!need_subtrans || onerror_step_off >= 0);
+
+	/*
+	 * Initialize coercion expression.
+	 */
+	if (jexpr->result_coercion)
+	{
+		/*
+		 * Don't use a sub-transaction for coercing the ON ERROR
+		 * expression.
+		 */
+		coercion_step_off =
+			ExecInitJsonCoercion(&cxt, (Node *) jexpr->result_coercion, false);
+
+		/*
+		 * Generate also coercion expression wrapped into a
+		 * sub-transaction, if needed.
+		 */
+		if (need_subtrans)
+			coercion_subtrans_step_off =
+				ExecInitJsonCoercion(&cxt, (Node *) jexpr->result_coercion, true);
+	}
+
+	/*
+	 * Emit I/O coercion if OMIT QUOTES is used and it was not already
+	 * emitted.
+	 */
+	if (jexpr->omit_quotes &&
+		(!jexpr->result_coercion || jexpr->result_coercion->ctype != JSON_COERCION_VIA_IO))
+	{
+		JsonCoercion io_coercion = {0};
+
+		io_coercion.type = T_JsonCoercion;
+		io_coercion.ctype = JSON_COERCION_VIA_IO;
+
+		io_coercion_step_off =
+			ExecInitJsonCoercion(&cxt, (Node *) &io_coercion, need_subtrans);
+	}
+
+	/*
+	 * Initialize "SQL/JSON item type => SQL type" coercion expressions.
+	 */
+	if (jexpr->coercions)
+		jsestate->item_coercions =
+			ExecInitJsonItemCoercions(&cxt, jexpr->coercions, need_subtrans);
+
+	/* Skip coercion of NULLs for non-domain types */
+	skip_null_coercion =
+		getBaseType(jexpr->returning->typid) == jexpr->returning->typid;
+
+	done_off = state->steps_len;
+
+	/* Redirect missing steps to the end step */
+	if (onerror_step_off < 0)
+		onerror_step_off = done_off;
+
+	if (onempty_step_off < 0)
+		onempty_step_off = done_off;
+
+	if (coercion_step_off < 0)
+		coercion_step_off = done_off;
+
+	if (coercion_subtrans_step_off < 0)
+		coercion_subtrans_step_off = coercion_step_off;
+
+	if (io_coercion_step_off < 0)
+		io_coercion_step_off = done_off;
+
+	/*
+	 * Adjust jump target addresses in various post-eval steps now that
+	 * we have all the steps in place.
+	 */
+
+	/* EEOP_JSONEXPR_PATH */
+	as = &state->steps[execpath_step_off];
+	as->d.jsonexpr.jump_done = done_off;
+	as->d.jsonexpr.jump_onerror = onerror_step_off;
+	as->d.jsonexpr.jump_onempty = onempty_step_off;
+	as->d.jsonexpr.jump_coercion = coercion_step_off;
+	as->d.jsonexpr.jump_coercion_subtrans = coercion_subtrans_step_off;
+	as->d.jsonexpr.jump_coercion_via_io = io_coercion_step_off;
+	as->d.jsonexpr.jump_coercion_null =
+		skip_null_coercion ? done_off : coercion_step_off;
+	as->d.jsonexpr.jump_coercion_null_subtrans =
+		skip_null_coercion ? done_off : coercion_subtrans_step_off;
+
+	/* EEOP_JSONEXPR_SKIP */
+	as = &state->steps[skip_step_off];
+	as->d.jsonexpr_skip.jump_coercion = coercion_step_off;
+
+	/* Adjust jump to ON EMPTY coercion, if any */
+	if (onempty_jump_coercion_step_off >= 0)
+	{
+		as = &state->steps[onempty_jump_coercion_step_off];
+		as->d.jump.jumpdone =
+			jexpr->on_empty->btype == JSON_BEHAVIOR_NULL &&
+			skip_null_coercion ? done_off : coercion_subtrans_step_off;
+	}
+
+	/* Adjust jump to ON ERROR coercion, if any */
+	if (onerror_jump_coercion_step_off >= 0)
+	{
+		as = &state->steps[onerror_jump_coercion_step_off];
+		as->d.jump.jumpdone =
+			jexpr->on_error->btype == JSON_BEHAVIOR_NULL &&
+			skip_null_coercion ? done_off : coercion_step_off;
+	}
+
+	/*
+	 * EEOP_JUMP steps added after default expressions and coercions
+	 * should jump to the expression end.
+	 */
+	foreach(lc, cxt.adjust_jumps)
+	{
+		as = &state->steps[lfirst_int(lc)];
+		as->d.jump.jumpdone = done_off;
+	}
+
+	/*
+	 * Adjust EEOP_SUBTRANS jumps to point to the expression end and to
+	 * ON ERROR handler.
+	 */
+	foreach(lc, cxt.adjust_subtrans)
+	{
+		as = &state->steps[lfirst_int(lc)];
+		as->d.subtrans.jump_done = done_off;
+		as->d.subtrans.jump_error = onerror_step_off;
+	}
+
+	/*
+	 * Verify that no subtransaction nodes were generated if
+	 * ExecJsonExprNeedsSubTransaction() returns false.
+	 */
+	Assert(cxt.adjust_subtrans == NIL ||
+		   ExecEvalJsonNeedsSubTransaction(jexpr));
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 66898198f33..af0049abd33 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -494,7 +494,10 @@ ExecInterpExprStep(ExprState *state, int stepno,
 		&&CASE_EEOP_SUBTRANS,
 		&&CASE_EEOP_JSON_CONSTRUCTOR,
 		&&CASE_EEOP_IS_JSON,
-		&&CASE_EEOP_JSONEXPR,
+		&&CASE_EEOP_JSONEXPR_SKIP,
+		&&CASE_EEOP_JSONEXPR_PATH,
+		&&CASE_EEOP_JSONEXPR_IOCOERCE,
+		&&CASE_EEOP_JSONEXPR_POPULATE,
 		&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
 		&&CASE_EEOP_AGG_DESERIALIZE,
 		&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1855,10 +1858,32 @@ ExecInterpExprStep(ExprState *state, int stepno,
 			EEO_NEXT();
 		}
 
-		EEO_CASE(EEOP_JSONEXPR)
+		EEO_CASE(EEOP_JSONEXPR_PATH)
 		{
 			/* too complex for an inline implementation */
-			ExecEvalJsonExpr(state, op, econtext);
+			EEO_JUMP(ExecEvalJsonExpr(state, op));
+		}
+
+		EEO_CASE(EEOP_JSONEXPR_SKIP)
+		{
+			/* too complex for an inline implementation */
+			if (ExecEvalJsonExprSkip(state, op))
+				EEO_JUMP(op->d.jsonexpr_skip.jump_coercion);
+			else
+				EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JSONEXPR_IOCOERCE)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalJsonExprIOCoerce(state, op);
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JSONEXPR_POPULATE)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalJsonExprPopulate(state, op, econtext);
 			EEO_NEXT();
 		}
 
@@ -4798,252 +4823,6 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
 	*op->resnull = isnull;
 }
 
-/*
- * Evaluate a JSON error/empty behavior result.
- */
-static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
-					 ExprState *default_estate, bool *is_null)
-{
-	*is_null = false;
-
-	switch (behavior->btype)
-	{
-		case JSON_BEHAVIOR_EMPTY_ARRAY:
-			return JsonbPGetDatum(JsonbMakeEmptyArray());
-
-		case JSON_BEHAVIOR_EMPTY_OBJECT:
-			return JsonbPGetDatum(JsonbMakeEmptyObject());
-
-		case JSON_BEHAVIOR_TRUE:
-			return BoolGetDatum(true);
-
-		case JSON_BEHAVIOR_FALSE:
-			return BoolGetDatum(false);
-
-		case JSON_BEHAVIOR_NULL:
-		case JSON_BEHAVIOR_UNKNOWN:
-		case JSON_BEHAVIOR_EMPTY:
-			*is_null = true;
-			return (Datum) 0;
-
-		case JSON_BEHAVIOR_DEFAULT:
-			return ExecEvalExpr(default_estate, econtext, is_null);
-
-		default:
-			elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
-			return (Datum) 0;
-	}
-}
-
-
-typedef struct EvalJsonSubtransState
-{
-	MemoryContext oldcontext;
-	ResourceOwner oldowner;
-} EvalJsonSubtransState;
-
-static void
-ExecEvalJsonStartSubtrans(EvalJsonSubtransState *state)
-{
-	/*
-	 * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
-	 * execute the corresponding ON ERROR behavior then.
-	 */
-	state->oldcontext = CurrentMemoryContext;
-	state->oldowner = CurrentResourceOwner;
-
-	BeginInternalSubTransaction(NULL);
-
-	/* Want to execute expressions inside function's memory context */
-	/*
-	 * AFIXME: I don't think that's OK, there might be lots of leaked memory
-	 * etc.
-	 */
-	MemoryContextSwitchTo(state->oldcontext);
-}
-
-static void
-ExecEvalJsonEndSubtrans(EvalJsonSubtransState *state)
-{
-	/* Commit the inner transaction, return to outer xact context */
-	ReleaseCurrentSubTransaction();
-	MemoryContextSwitchTo(state->oldcontext);
-	CurrentResourceOwner = state->oldowner;
-}
-
-static void
-ExecEvalJsonCatchError(EvalJsonSubtransState *state)
-{
-	ErrorData  *edata;
-	int			ecategory;
-
-	/* Save error info in oldcontext */
-	MemoryContextSwitchTo(state->oldcontext);
-	edata = CopyErrorData();
-	FlushErrorState();
-
-	/* Abort the inner transaction */
-	RollbackAndReleaseCurrentSubTransaction();
-	MemoryContextSwitchTo(state->oldcontext);
-	CurrentResourceOwner = state->oldowner;
-
-	ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);
-
-	if (ecategory != ERRCODE_DATA_EXCEPTION &&	/* jsonpath and other data
-												 * errors */
-		ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION)	/* domain errors */
-	{
-		ReThrowError(edata);
-	}
-}
-
-/*
- * If `error` is not NULL (we need to catch errors), execute the
- * statement passed in the third argument inside a subtransaction,
- * otherwise do it outside.  In the subtrans case an error classified as
- * being catchable by ExecEvalJsonCatchError() will set `error` to true.
- */
-#define JSE_OPT_SUBTRANS(error, jexpr, res, ...) \
-	do { \
-		bool *error_ = (error); \
-		\
-		if (!error_) \
-		{ \
-			res = __VA_ARGS__; \
-		} \
-		else \
-		{ \
-			EvalJsonSubtransState substate = {0}; \
-			\
-			/* Check correctness of ExecEvalJsonNeedsSubTransaction() */ \
-			AssertMacro(ExecEvalJsonNeedsSubTransaction(jexpr)); \
-			\
-			ExecEvalJsonStartSubtrans(&substate); \
-			\
-			PG_TRY(); \
-			{ \
-				res = __VA_ARGS__; \
-				ExecEvalJsonEndSubtrans(&substate); \
-			} \
-			PG_CATCH(); \
-			{ \
-				ExecEvalJsonCatchError(&substate); \
-				\
-				res = (Datum) 0; \
-				*error_ = true; \
-			} \
-			PG_END_TRY(); \
-		 } \
-	} while (0)
-
-/* Execute one of possible coercions or report an error if there is no cast */
-static Datum
-ExecEvalJsonExprCoercion(JsonExprState *jsestate, ExprContext *econtext,
-						 JsonCoercionState *coercion,
-						 Datum res, bool *resnull, bool *error)
-{
-	switch (coercion->type)
-	{
-		case JSON_COERCION_NONE:
-			return (Datum) res;
-
-		case JSON_COERCION_VIA_EXPR:
-			jsestate->coercion_expr.value = res;
-			jsestate->coercion_expr.isnull = *resnull;
-			return ExecEvalExpr(coercion->estate, econtext, resnull);
-
-		case JSON_COERCION_VIA_IO:
-			{
-				/* Strip quotes from jsonb strings and call typinput() */
-				char	   *str;
-
-				if (*resnull)
-					str = NULL;
-				else
-					str = JsonbUnquote(DatumGetJsonbP(res));
-
-				return InputFunctionCall(&jsestate->input.func, str,
-										 jsestate->input.typioparam,
-										 jsestate->jsexpr->returning->typmod);
-			}
-
-		case JSON_COERCION_VIA_POPULATE:
-			return json_populate_type(res, JSONBOID,
-									  jsestate->jsexpr->returning->typid,
-									  jsestate->jsexpr->returning->typmod,
-									  &jsestate->cache,
-									  econtext->ecxt_per_query_memory,
-									  resnull);
-
-		default:
-			Assert(coercion->type == JSON_COERCION_ERROR);
-
-			/* Report an error if there is no cast to the output type */
-			if (!error)
-				ereport(ERROR,
-					(errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
-					 errmsg("SQL/JSON item cannot be cast to target type")));
-
-			*error = true;
-			*resnull = true;
-			return (Datum) 0;
-	}
-}
-
-/*
- * Evaluate a JSON path variable caching computed value.
- */
-int
-EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
-				JsonbValue *val, JsonbValue *baseObject)
-{
-	JsonPathVariableEvalContext *var = NULL;
-	List	   *vars = cxt;
-	ListCell   *lc;
-	int			id = 1;
-
-	if (!varName)
-		return list_length(vars);
-
-	foreach(lc, vars)
-	{
-		var = lfirst(lc);
-
-		if (!strncmp(var->name, varName, varNameLen))
-			break;
-
-		var = NULL;
-		id++;
-	}
-
-	if (!var)
-		return -1;
-
-	if (!var->evaluated)
-	{
-		MemoryContext oldcxt = var->mcxt ?
-		MemoryContextSwitchTo(var->mcxt) : NULL;
-
-		var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
-		var->evaluated = true;
-
-		if (oldcxt)
-			MemoryContextSwitchTo(oldcxt);
-	}
-
-	if (var->isnull)
-	{
-		val->type = jbvNull;
-		return 0;
-	}
-
-	JsonItemFromDatum(var->value, var->typid, var->typmod, val);
-
-	*baseObject = *val;
-	return id;
-}
-
 /*
  * Check whether we need to override default coercion in
  * JSON_QUERY(OMIT QUOTES) case.
@@ -5064,78 +4843,84 @@ ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res, bool isnull)
 }
 
 /*
- * Prepare SQL/JSON item coercion to the JSON_VALUE output type.
- * Returned a coercion status, a datum of the corresponding SQL type
- * and a pointer to selected coercion state.
+ * Prepare SQL/JSON item coercion to the output type.  Returned a datum
+ * of the corresponding SQL type and pointers to the coercion and its
+ * state.
  */
 static Datum
-ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item,
-							 JsonCoercionState **coercion, bool *resnull)
+ExecPrepareJsonValueCoercion(JsonbValue *item, ExprEvalStep *op,
+							 JsonItemCoercionsState *steps, int *jump)
 {
-	struct JsonItemsCoercionStates *coercions = jsestate->item_coercions;
-	JsonbValue	buf;
+	JsonbValue 	buf;
 
-	*resnull = false;
-
-	if (!coercions)
+	/*
+	 * Special case for json and jsonb types for which we don't have
+	 * casts like numeric::jsonb etc.
+	 */
+	if (!steps)
 	{
+		Jsonb	   *jb = JsonbValueToJsonb(item);
+
 		/*
-		 * Special case for json[b] output types.  Simply use
-		 * default coercion from jsonb to the output type.
+		 * Use result coercion from json[b] to the output
+		 * type when casting to json[b] types or their
+		 * domains.
 		 */
-		return JsonbPGetDatum(JsonbValueToJsonb(item));
+
+		*jump = op->d.jsonexpr.jump_coercion_subtrans;
+		return JsonbPGetDatum(jb);;
 	}
 
-	/* Use coercion from SQL/JSON item type to the output type */
+	/* Extract scalar from binary scalar presudo-array */
 	if (item->type == jbvBinary &&
 		JsonContainerIsScalar(item->val.binary.data))
 	{
-		bool		res PG_USED_FOR_ASSERTS_ONLY;
+		bool		is_scalar PG_USED_FOR_ASSERTS_ONLY;
+
+		is_scalar = JsonbExtractScalar(item->val.binary.data, &buf);
+		Assert(is_scalar);
 
-		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:
-			Assert(0); /* must be handled by the caller */
-			*coercion = &coercions->null;
-			*resnull = true;
+			*jump = steps->null;
 			return (Datum) 0;
 
 		case jbvString:
-			*coercion = &coercions->string;
-			return PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
-															item->val.string.len));
+			*jump = steps->string;
+			return *jump < 0 ? (Datum) 0 :
+				PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
+														 item->val.string.len));
 
 		case jbvNumeric:
-			*coercion = &coercions->numeric;
+			*jump = steps->numeric;
 			return NumericGetDatum(item->val.numeric);
 
 		case jbvBool:
-			*coercion = &coercions->boolean;
+			*jump = steps->boolean;
 			return BoolGetDatum(item->val.boolean);
 
 		case jbvDatetime:
 			switch (item->val.datetime.typid)
 			{
 				case DATEOID:
-					*coercion = &coercions->date;
+					*jump = steps->date;
 					break;
 				case TIMEOID:
-					*coercion = &coercions->time;
+					*jump = steps->time;
 					break;
 				case TIMETZOID:
-					*coercion = &coercions->timetz;
+					*jump = steps->timetz;
 					break;
 				case TIMESTAMPOID:
-					*coercion = &coercions->timestamp;
+					*jump = steps->timestamp;
 					break;
 				case TIMESTAMPTZOID:
-					*coercion = &coercions->timestamptz;
+					*jump = steps->timestamptz;
 					break;
 				default:
 					elog(ERROR, "unexpected jsonb datetime type oid %u",
@@ -5149,8 +4934,9 @@ ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item,
 		case jbvObject:
 		case jbvBinary:
 			Assert(0);	/* non-scalars must be rejected by JsonPathValue() */
-			*coercion = &coercions->composite;
-			return JsonbPGetDatum(JsonbValueToJsonb(item));
+			*jump = steps->composite;
+			return *jump < 0 ? (Datum) 0 :
+				JsonbPGetDatum(JsonbValueToJsonb(item));
 
 		default:
 			elog(ERROR, "unexpected jsonb value type %d", item->type);
@@ -5158,129 +4944,159 @@ ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item,
 	}
 }
 
-static Datum
-ExecEvalJsonExprInternal(JsonExprState *jsestate, ExprContext *econtext,
-						 JsonPath *path, Datum item, bool *resnull,
-						 bool *error)
+/*
+ * Evaluate a SQL/JSON function.
+ */
+int
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
 {
+	JsonExprState *jsestate = op->d.jsonexpr.jsestate;
+	JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;
 	JsonExpr   *jexpr = jsestate->jsexpr;
-	JsonCoercionState *coercion = &jsestate->result_coercion;
-	bool		empty = false;
+	Datum		item = pre_eval->formatted_expr.value;
+	JsonPath   *path = DatumGetJsonPathP(pre_eval->pathspec.value);
+	bool		catch_errors =
+		jexpr->on_error->btype != JSON_BEHAVIOR_ERROR;
 	Datum		res = (Datum) 0;
+	bool		empty = false;
+	bool		error = false;
+	bool	   *p_error = catch_errors ? &error : NULL;
 
-	*resnull = true;
+	*op->resvalue = (Datum) 0;
+	*op->resnull = true;		/* until we get a result */
 
 	switch (jexpr->op)
 	{
 		case JSON_QUERY_OP:
-			res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
-								jsestate->args);
-			if (error && *error)
-				return (Datum) 0;
+			res = JsonPathQuery(item, path, jexpr->wrapper, &empty,
+								p_error, pre_eval->args);
+
+			if (error)
+				return op->d.jsonexpr.jump_onerror;
 
 			if (empty)
 				break;
 
-			*resnull = !DatumGetPointer(res);
+			*op->resnull = !DatumGetPointer(res);
+			*op->resvalue = res;
 
 			/* Override default coercion in OMIT QUOTES case */
-			if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull))
-				coercion = &jsestate->io_coercion;
+			if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *op->resnull))
+				return op->d.jsonexpr.jump_coercion_via_io;
 
-			break;
+			/* Maybe skip coercion of NULLs for non-domain types */
+			if (*op->resnull)
+				return op->d.jsonexpr.jump_coercion_null_subtrans;
+
+			/* Evaluate coercion expression inside subtransaction */
+			return op->d.jsonexpr.jump_coercion_subtrans;
 
 		case JSON_VALUE_OP:
 			{
-				JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
-												jsestate->args);
+				int			jump_item_coercion;
+				JsonbValue *jbv = JsonPathValue(item, path, &empty,
+												p_error, pre_eval->args);
 
-				if (error && *error)
-					return (Datum) 0;
+				if (error)
+					return op->d.jsonexpr.jump_onerror;
 
-				if (!jbv)		/* NULL or empty */
+				if (empty)
 					break;
 
+				/*
+				 * Execute coercion for NULL inside subtransaction, it
+				 * can be skiped for non-domain types.
+				 */
+				if (!jbv)
+					return op->d.jsonexpr.jump_coercion_null_subtrans;
+
+
 				Assert(!empty);
 				Assert(jbv->type != jbvNull);
 
-				res = ExecPrepareJsonValueCoercion(jsestate, jbv,
-												   &coercion, resnull);
-				break;
+				/*
+				 * Prepare coercion from SQL/JSON item type to the
+				 * output SQL type.
+				 */
+				res = ExecPrepareJsonValueCoercion(jbv, op,
+												   jsestate->item_coercions,
+												   &jump_item_coercion);
+
+				*op->resvalue = res;
+				*op->resnull = false;
+
+				/* Jump to zero here means jump to the end. */
+				if (jump_item_coercion == 0)
+					return op->d.jsonexpr.jump_done;
+
+				/*
+				 * Error out if no cast exists to coerce SQL/JSON item
+				 * to the the output type.
+				 */
+				if (jump_item_coercion < 0)
+				{
+					*op->resnull = true;
+
+					if (catch_errors)
+						return op->d.jsonexpr.jump_onerror;
+
+					ereport(ERROR,
+							(errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+							 errmsg("SQL/JSON item cannot be cast to target type")));
+				}
+
+				/* Coerce using a specific coercion expression */
+				return jump_item_coercion;
 			}
 
 		case JSON_EXISTS_OP:
 			{
 				bool		exists = JsonPathExists(item, path,
-													jsestate->args,
-													error);
+													pre_eval->args,
+													p_error);
 
-				*resnull = error && *error;
-				res = BoolGetDatum(exists);
-				break;	/* always use result coercion */
-			}
+				*op->resnull = error;
+				*op->resvalue = BoolGetDatum(exists);
 
-		default:
-			elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
-			return (Datum) 0;
-	}
+				if (error)
+					return op->d.jsonexpr.jump_onerror;
 
-	if (empty)
-	{
-		Assert(jexpr->on_empty);	/* it is not JSON_EXISTS */
+				/*
+				 * Execute coercion of NULL inside subtransaction, it
+				 * can be skiped for non-domain types.
+				 */
+				if (*op->resnull)
+					return op->d.jsonexpr.jump_coercion_null_subtrans;
 
-		if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
-		{
-			if (error)
-			{
-				*error = true;
-				return (Datum) 0;
+				/* Evaluate coercion inside subtransaction, if any */
+				return op->d.jsonexpr.jump_coercion_subtrans;
 			}
 
-			ereport(ERROR,
-					(errcode(ERRCODE_NO_SQL_JSON_ITEM),
-					 errmsg("no SQL/JSON item")));
-		}
-
-		if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)
-		{
-			/*
-			 * Execute DEFAULT expression in a subtransation if needed.
-			 * Coercion is not needed here, because expression is
-			 * already coerced to the target type by the parser.
-			 */
-			JSE_OPT_SUBTRANS(jsestate->default_on_empty_can_throw ? error : NULL,
-							 jexpr, res,
-							 ExecEvalExpr(jsestate->default_on_empty,
-										  econtext, resnull));
-			return res;
-		}
-
-		/* Execute ON EMPTY behavior, coercion is default */
-		res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,
-								   jsestate->default_on_empty,
-								   resnull);
+		default:
+			elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
+			return op->d.jsonexpr.jump_done;
 	}
 
-	/*
-	 * Execute resulting coercion in a subtransaction if `error` != NULL.
-	 * In case of cast errors, there is no need to use subtransaction.
-	 */
-	if (coercion->type != JSON_COERCION_NONE)
+	/* Execute ON EMPTY behavior if no SQL/JSON items were found */
+	Assert(empty);
+	Assert(jexpr->on_empty);	/* it is not JSON_EXISTS */
+
+	if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
 	{
-		/* Skip coercion of NULLs for non-domain types */
-		if (*resnull && jsestate->skip_null_coercion)
-			return (Datum) 0;
+		/* Throw an error or jump to ON ERROR handler */
+		if (catch_errors)
+			return op->d.jsonexpr.jump_onerror;
 
-		JSE_OPT_SUBTRANS(coercion->type != JSON_COERCION_ERROR ? error : NULL,
-						 jexpr, res,
-						 ExecEvalJsonExprCoercion(jsestate, econtext,
-												  coercion, res,
-												  resnull, error));
+		ereport(ERROR,
+				(errcode(ERRCODE_NO_SQL_JSON_ITEM),
+				 errmsg("no SQL/JSON item")));
 	}
 
-	return res;
+	/* Evaluate ON EMPTY behavior */
+	return op->d.jsonexpr.jump_onempty;
 }
 
+/* Skip calling ExecEvalJson() on a JsonExpr? */
 bool
 ExecEvalExprCanThrowErrors(Node *expr)
 {
@@ -5319,66 +5135,60 @@ ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr)
 	return false;			/* subtransactios should not be used */
 }
 
-/* ----------------------------------------------------------------
- *		ExecEvalJsonExpr
- * ----------------------------------------------------------------
- */
-void
-ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+bool
+ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
 {
-	JsonExprState *jsestate = op->d.jsonexpr.jsestate;
-	JsonExpr   *jexpr = jsestate->jsexpr;
-	Datum		item;
-	Datum		res;
-	JsonPath   *path;
-	ListCell   *lc;
-	bool		error = false;
-	bool		throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
+	JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate;
 
-	if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
+	/*
+	 * 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->resnull = true;
 		*op->resvalue = (Datum) 0;
-
-		/* execute domain checks for NULLs */
-		(void) ExecEvalJsonExprCoercion(jsestate, econtext,
-										&jsestate->result_coercion,
-										*op->resvalue, op->resnull, NULL);
-
-		Assert(*op->resnull);
-		return;
+		*op->resnull = true;
+		return true;
 	}
 
-	item = jsestate->formatted_expr.value;
-	path = DatumGetJsonPathP(jsestate->pathspec.value);
+	/*
+	 * Go evaluate the PASSING args if any and subsequently JSON path
+	 * itself.
+	 */
+	return false;
+}
+
+/* Apply I/O coercion to a JSON item */
+void
+ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op)
+{
+	/* Strip quotes and call typinput function */
+	char	   *str = NULL;
 
-	/* reset JSON path variable contexts */
-	foreach(lc, jsestate->args)
+	if (!*op->resnull)
 	{
-		JsonPathVariableEvalContext *var = lfirst(lc);
+		Jsonb	   *jb = DatumGetJsonbP(*op->resvalue);
 
-		var->econtext = econtext;
-		var->evaluated = false;
+		str = JsonbUnquote(jb);
 	}
 
-	res = ExecEvalJsonExprInternal(jsestate, econtext,
-								   path, item, op->resnull,
-								   throwErrors ? NULL : &error);
-	if (error)
-	{
-		Assert(jexpr->on_error->btype != JSON_BEHAVIOR_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(jsestate, econtext,
-										   &jsestate->result_coercion,
-										   res, op->resnull, NULL);
-	}
+	*op->resvalue = InputFunctionCall(op->d.jsonexpr_iocoerce.finfo, str,
+									  op->d.jsonexpr_iocoerce.typioparam,
+									  op->d.jsonexpr_iocoerce.typmod);
+}
 
-	*op->resvalue = res;
+/* Apply json_populate_type() to a JSON item */
+void
+ExecEvalJsonExprPopulate(ExprState *state, ExprEvalStep *op,
+						 ExprContext *econtext)
+{
+	*op->resvalue =
+		json_populate_type(*op->resvalue, JSONBOID,
+						   op->d.jsonexpr_populate.jsexpr->returning->typid,
+						   op->d.jsonexpr_populate.jsexpr->returning->typmod,
+						   &op->d.jsonexpr_populate.cache,
+						   econtext->ecxt_per_query_memory,
+						   op->resnull);
 }
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 6568228b996..70d84d95175 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2542,8 +2542,126 @@ llvm_compile_expr_step(ExprState *state, int start_opno)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
-			case EEOP_JSONEXPR:
-				build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
+			case EEOP_JSONEXPR_PATH:
+				{
+					LLVMValueRef v_ret;
+					LLVMValueRef v_switch;
+					JsonItemCoercionsState *jcstate =
+						op->d.jsonexpr.jsestate->item_coercions;
+					int			jumps[6 + sizeof(*jcstate) / sizeof(jcstate->null)];
+					int			njumps = 0;
+
+					/*
+					 * ExecEvalJson() returns number of the step we
+					 * need to jump.
+					 */
+					v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
+											v_state, op);
+
+					/*
+					 * Collect all unique jumps.  All jumps can be
+					 * equal to jump_done.
+					 */
+					jumps[njumps++] = op->d.jsonexpr.jump_done;
+
+					if (op->d.jsonexpr.jump_coercion != op->d.jsonexpr.jump_done)
+						jumps[njumps++] = op->d.jsonexpr.jump_coercion;
+
+					/*
+					 * jump_coercion_subtrans and jump_coercion can be
+					 * the same if there are no subtransactions.
+					 */
+					if (op->d.jsonexpr.jump_coercion_subtrans != op->d.jsonexpr.jump_done &&
+						op->d.jsonexpr.jump_coercion_subtrans != op->d.jsonexpr.jump_coercion)
+						jumps[njumps++] = op->d.jsonexpr.jump_coercion_subtrans;
+
+					/*
+					 * jump_coercion_via_io and jump_coercion_subtrans
+					 * can be the same whn only I/O coercion to output
+					 * type is available.  jump_coercion_via_io can
+					 * also be negative.
+					 */
+					if (op->d.jsonexpr.jump_coercion_via_io != op->d.jsonexpr.jump_done &&
+						op->d.jsonexpr.jump_coercion_via_io != op->d.jsonexpr.jump_coercion_subtrans &&
+						op->d.jsonexpr.jump_coercion_via_io >= 0)
+						jumps[njumps++] = op->d.jsonexpr.jump_coercion_via_io;
+
+					if (op->d.jsonexpr.jump_onempty != op->d.jsonexpr.jump_done)
+						jumps[njumps++] = op->d.jsonexpr.jump_onempty;
+
+					if (op->d.jsonexpr.jump_onerror != op->d.jsonexpr.jump_done)
+						jumps[njumps++] = op->d.jsonexpr.jump_onerror;
+
+					/*
+					 * jump_coercion_null and jump_coercion_null_subtrans
+					 * are always duplicate.
+					 */
+
+					/* Collect non-zero jumps to item coercions, if any */
+					if (jcstate)
+					{
+						for (int *p_jump = &jcstate->null;
+							 p_jump <= &jcstate->composite;
+							 p_jump++)
+						{
+							if (*p_jump > 0)
+								jumps[njumps++] = *p_jump;
+						}
+					}
+
+					/* Emit switch for all possible jumps */
+					v_switch = LLVMBuildSwitch(b, v_ret,
+											   opblocks[op->d.jsonexpr.jump_done],
+											   njumps);
+
+					for (int i = 0; i < njumps; i++)
+						LLVMAddCase(v_switch,
+									l_int32_const(jumps[i]),
+									opblocks[jumps[i]]);
+
+					break;
+				}
+				break;
+
+			case EEOP_JSONEXPR_SKIP:
+				{
+					LLVMValueRef v_ret;
+
+					/*
+					 * Call ExecEvalJsonExprSkip() to decide if JSON
+					 * path evaluation can be skipped.  This returns
+					 * boolean "skip" flag.
+					 */
+					v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExprSkip",
+											v_state, op);
+					v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, "");
+
+					/*
+					 * Jump to coercion step if true was returned,
+					 * 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_ret,
+												  l_sbool_const(0),
+												  ""),
+									opblocks[opno + 1],
+									opblocks[op->d.jsonexpr_skip.jump_coercion]);
+					break;
+				}
+
+			case EEOP_JSONEXPR_IOCOERCE:
+				build_EvalXFunc(b, mod, "ExecEvalJsonExprIOCoerce",
+									v_state, op);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
+			case EEOP_JSONEXPR_POPULATE:
+				build_EvalXFunc(b, mod, "ExecEvalJsonExprPopulate",
 								v_state, op, v_econtext);
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index bb96d46048f..d7653288dd8 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -138,6 +138,9 @@ void	   *referenced_functions[] =
 	ExecEvalJsonConstructor,
 	ExecEvalJsonIsPredicate,
 	ExecEvalJsonExpr,
+	ExecEvalJsonExprSkip,
+	ExecEvalJsonExprIOCoerce,
+	ExecEvalJsonExprPopulate,
 	MakeExpandedObjectReadOnlyInternal,
 	slot_getmissingattrs,
 	slot_getsomeattrs_int,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index aff6fc1a1c4..cad3c56a38f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4384,6 +4384,11 @@ initJsonItemCoercions(ParseState *pstate, const JsonReturning *returning,
 		{NULL, InvalidOid}
 	};
 
+	/* When returning JSON types, no need to initialize coercions */
+	/* XXX domain types on json/jsonb */
+	if (returning->typid == JSONBOID || returning->typid == JSONOID)
+		return NULL;
+
 	for (p = coercionTypids; p->coercion; p++)
 		*p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
 
@@ -4465,6 +4470,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
 			}
 			else if (jsexpr->returning->typid != BOOLOID)
 			{
+				/* We need to handle RETURNING int etc. */
 				CaseTestExpr *placeholder = makeNode(CaseTestExpr);
 				int			location = exprLocation((Node *) jsexpr);
 				Node	   *coercion_expr;
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 5b6a4805721..3390038ff4c 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -286,6 +286,8 @@ static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
 										   JsonPathItem *jsp, JsonValueList *found, JsonPathBool res);
 static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
 							JsonbValue *value);
+static int	EvalJsonPathVar(void *vars, char *varName, int varNameLen,
+							JsonbValue *val, JsonbValue *baseObject);
 static void getJsonPathVariable(JsonPathExecContext *cxt,
 								JsonPathItem *variable, JsonbValue *value);
 static int	getJsonPathVariableFromJsonb(void *varsJsonb, char *varName,
@@ -2172,6 +2174,72 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
 	}
 }
 
+/*
+ * Evaluate a JSON path variable caching computed value.
+ */
+int
+EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
+				JsonbValue *val, JsonbValue *baseObject)
+{
+	JsonPathVariableEvalContext *var = NULL;
+	List	   *vars = cxt;
+	ListCell   *lc;
+	int			id = 1;
+
+	if (!varName)
+		return list_length(vars);
+
+	foreach(lc, vars)
+	{
+		var = lfirst(lc);
+
+		if (!strncmp(var->name, varName, varNameLen))
+			break;
+
+		var = NULL;
+		id++;
+	}
+
+	if (!var)
+		return -1;
+
+	/*
+	 * 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)
+	{
+		val->type = jbvNull;
+		return 0;
+	}
+
+	JsonItemFromDatum(var->value, var->typid, var->typmod, val);
+
+	*baseObject = *val;
+	return id;
+}
+
 /*
  * Get the value of variable passed to jsonpath executor
  */
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index a51232b84de..3aa5cddbb8e 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -247,7 +247,10 @@ typedef enum ExprEvalOp
 	EEOP_SUBTRANS,
 	EEOP_JSON_CONSTRUCTOR,
 	EEOP_IS_JSON,
-	EEOP_JSONEXPR,
+	EEOP_JSONEXPR_SKIP,
+	EEOP_JSONEXPR_PATH,
+	EEOP_JSONEXPR_IOCOERCE,
+	EEOP_JSONEXPR_POPULATE,
 
 	/* aggregation related nodes */
 	EEOP_AGG_STRICT_DESERIALIZE,
@@ -704,12 +707,50 @@ typedef struct ExprEvalStep
 			JsonIsPredicate *pred;	/* original expression node */
 		}			is_json;
 
-		/* for EEOP_JSONEXPR */
+		/* for EEOP_JSONEXPR_PATH */
 		struct
 		{
 			struct JsonExprState *jsestate;
+
+			/* See ExecEvalJsonExpr() */
+			int			jump_done;
+			int			jump_onerror;
+			int			jump_onempty;
+			int			jump_coercion;
+			int			jump_coercion_subtrans;
+			int			jump_coercion_via_io;
+			int			jump_coercion_null;
+			int			jump_coercion_null_subtrans;
 		}			jsonexpr;
 
+		/* for EEOP_JSONEXPR_SKIP */
+		struct
+		{
+			/* Same as jsonexpr.jsestate */
+			struct JsonExprState *jsestate;
+
+			/* See ExecEvalJsonExprSkip() */
+			int			jump_coercion;
+		}			jsonexpr_skip;
+
+		/* for EEOP_JSONEXPR_IOCOERCE */
+		struct
+		{
+			/* I/O info for output type */
+			FmgrInfo   *finfo;	/* typinput function for output type */
+			Oid			typioparam;
+			int32		typmod;
+		}			jsonexpr_iocoerce;
+
+		/* for EEOP_JSONEXPR_POPULATE */
+		struct
+		{
+			struct JsonExpr *jsexpr;
+
+			/* Cache for json_populate_type() */
+			void	   *cache;
+		}			jsonexpr_populate;
+
 	}			d;
 } ExprEvalStep;
 
@@ -769,68 +810,57 @@ typedef struct JsonConstructorExprState
 	int			nargs;
 } JsonConstructorExprState;
 
-typedef struct JsonCoercionState
+/*
+ * Information computed before evaluating a JsonExpr expression.
+ */
+typedef struct JsonExprPreEvalState
 {
-	ExprState  *estate; /* coercion expression state, if any */
-	JsonCoercionType type;
-} JsonCoercionState;
+	/* 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 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
+{
+	/* Number of ExprEvalStep to compute this coercion's expression */
+	int			null;
+	int			string;
+	int			numeric;
+	int			boolean;
+	int			date;
+	int			time;
+	int			timetz;
+	int			timestamp;
+	int			timestamptz;
+	int			composite;
+} JsonItemCoercionsState;
 
 /* EEOP_JSONEXPR state, too big to inline */
 typedef struct JsonExprState
 {
 	JsonExpr   *jsexpr;			/* original expression node */
 
-	struct
-	{
-		FmgrInfo	func;		/* typinput function for output type */
-		Oid			typioparam;
-	}			input;			/* I/O info for output type */
-
-	NullableDatum
-				formatted_expr,	/* formatted context item value */
-				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() */
+	JsonExprPreEvalState pre_eval;
 
 	/*
-	 * States for coercion of SQL/JSON items produced in JSON_VALUE
-	 * directly to the output type.
-	 *
-	 * `item_coercions == NULL` means output type is json[b] or its
-	 * domain and we use `result_coercion`.
+	 * Used only in JSON_VALUE().  `item_coercions == NULL` means that
+	 * output type is json[b] or its domain and we use `result_coercion`.
 	 */
-	struct JsonItemsCoercionStates
-	{
-		JsonCoercionState
-					null,
-					string,
-					numeric,
-					boolean,
-					date,
-					time,
-					timetz,
-					timestamp,
-					timestamptz,
-					composite;
-	}		   *item_coercions;
-
-	/* Default output coercion to use, can be overriden in JSON_QUERY */
-	JsonCoercionState result_coercion;
-
-	/* Special I/O coercion state for JSON_QUERY OMIT QUOTES */
-	JsonCoercionState io_coercion;
-
-	/* Can expression in DEFAULT ON EMPTY throw errors? */
-	bool		default_on_empty_can_throw;
-
-	/* Can we skip coercion of NULLs (non-domain output type)? */
-	bool		skip_null_coercion;
+	JsonItemCoercionsState *item_coercions;
 } JsonExprState;
 
 /* functions in execExpr.c */
@@ -895,15 +925,13 @@ extern bool ExecEvalSubtrans(ExprState *state, ExprEvalStep *op,
 							 ExecEvalSubroutine func);
 extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
 									ExprContext *econtext);
-extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
-							 ExprContext *econtext);
 extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr);
 extern bool ExecEvalExprCanThrowErrors(Node *expr);
-extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
-										  ExprContext *econtext, bool *isnull,
-										  Datum caseval_datum,
-										  bool caseval_isnull);
-
+extern int ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
+extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonExprPopulate(ExprState *state, ExprEvalStep *op,
+									 ExprContext *econtext);
 extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
 							 ExprContext *aggcontext);
 extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 0a40d59be26..0824a6186d1 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1576,8 +1576,11 @@ typedef struct JsonCoercion
 
 /*
  * JsonItemCoercions -
- *		expressions for coercion from SQL/JSON item types directly to the
- *		output SQL type
+ *		expressions for coercion from SQL/JSON item types directly to
+ *		the output SQL type.
+ *
+ *		"JsonCoercion == NULL" means no cast is available.
+ * 		"JsonCoercion.expr == NULL" means no coercion is needed.
  */
 typedef struct JsonItemCoercions
 {
@@ -1591,7 +1594,7 @@ typedef struct JsonItemCoercions
 	JsonCoercion *timetz;
 	JsonCoercion *timestamp;
 	JsonCoercion *timestamptz;
-	JsonCoercion *composite;	/* arrays and objects */
+	JsonCoercion *composite;
 } JsonItemCoercions;
 
 /*
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index bae466b5234..6bdd9f51219 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)
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 8e79b8dc9f0..fa1d3eae6cf 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -280,9 +280,6 @@ extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper,
 extern JsonbValue *JsonPathValue(Datum jb, JsonPath *jp, bool *empty,
 								 bool *error, List *vars);
 
-extern int	EvalJsonPathVar(void *vars, char *varName, int varNameLen,
-							JsonbValue *val, JsonbValue *baseObject);
-
 extern PGDLLIMPORT const TableFuncRoutine JsonbTableRoutine;
 
 #endif
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index ee5e597909c..c84fb03df37 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -693,6 +693,17 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +
  "2018-02-21T02:34:56+00:00"
 (1 row)
 
+-- test errors in DEFAULT ON EMPTY expressions
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int
+                  DEFAULT 1 / x ON EMPTY
+                  DEFAULT 2 ON ERROR)
+FROM (VALUES (1::int), (0)) x(x);
+ json_value 
+------------
+          1
+          2
+(2 rows)
+
 -- JSON_QUERY
 SELECT
 	JSON_QUERY(js, '$'),
@@ -794,6 +805,12 @@ SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERR
  \x616161
 (1 row)
 
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES EMPTY ARRAY ON ERROR);
+ json_query 
+------------
+ []
+(1 row)
+
 -- QUOTES behavior should not be specified when WITH WRAPPER used:
 -- Should fail
 SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES);
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
index d70104b70ef..eed163e7089 100644
--- a/src/test/regress/sql/jsonb_sqljson.sql
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -194,6 +194,12 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +
 SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json);
 SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb);
 
+-- test errors in DEFAULT ON EMPTY expressions
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int
+                  DEFAULT 1 / x ON EMPTY
+                  DEFAULT 2 ON ERROR)
+FROM (VALUES (1::int), (0)) x(x);
+
 -- JSON_QUERY
 
 SELECT
@@ -239,6 +245,7 @@ SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING
 SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR);
 SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR);
 SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES EMPTY ARRAY ON ERROR);
 
 -- QUOTES behavior should not be specified when WITH WRAPPER used:
 -- Should fail
-- 
2.25.1

