From 842c002642c06d2dd371e195544236ae9ad2e76e Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 16 Aug 2022 03:17:16 +0300
Subject: [PATCH v6 3/3] Simplify JsonExpr execution

---
 src/backend/executor/execExpr.c             | 675 +++++++++++++++++---
 src/backend/executor/execExprInterp.c       | 605 ++++++------------
 src/backend/jit/llvm/llvmjit_expr.c         | 137 +++-
 src/backend/jit/llvm/llvmjit_types.c        |   3 +
 src/backend/optimizer/util/clauses.c        |   2 +-
 src/backend/parser/parse_expr.c             |  18 +-
 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 +
 13 files changed, 1110 insertions(+), 586 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 96196d50876..9370727d902 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,112 +2562,20 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_JsonExpr:
 			{
 				JsonExpr   *jexpr = castNode(JsonExpr, node);
-				JsonExprState *jsestate = palloc0(sizeof(JsonExprState));
-				ListCell   *argexprlc;
-				ListCell   *argnamelc;
-
-				scratch.opcode = EEOP_JSONEXPR;
-				scratch.d.jsonexpr.jsestate = jsestate;
-
-				jsestate->jsexpr = jexpr;
-
-				jsestate->formatted_expr =
-					palloc(sizeof(*jsestate->formatted_expr));
-
-				ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
-								&jsestate->formatted_expr->value,
-								&jsestate->formatted_expr->isnull);
-
-				jsestate->pathspec =
-					palloc(sizeof(*jsestate->pathspec));
-
-				ExecInitExprRec((Expr *) jexpr->path_spec, state,
-								&jsestate->pathspec->value,
-								&jsestate->pathspec->isnull);
-
-				jsestate->res_expr =
-					palloc(sizeof(*jsestate->res_expr));
-
-				jsestate->result_expr = jexpr->result_coercion
-					? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,
-												state->parent,
-												&jsestate->res_expr->value,
-												&jsestate->res_expr->isnull)
-					: NULL;
-
-				jsestate->default_on_empty = !jexpr->on_empty ? NULL :
-					ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
-								 state->parent);
-
-				jsestate->default_on_error =
-					ExecInitExpr((Expr *) jexpr->on_error->default_expr,
-								 state->parent);
-
-				if (jexpr->omit_quotes ||
-					(jexpr->result_coercion && jexpr->result_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;
+				ExecInitJsonExpr(&scratch, jexpr, state, resv, resnull);
+				break;
+			}
 
-				if (jexpr->coercions)
-				{
-					JsonCoercion **coercion;
-					struct JsonCoercionState *cstate;
-					Datum	   *caseval;
-					bool	   *casenull;
-
-					jsestate->coercions =
-						palloc(sizeof(*jsestate->coercions));
-					jsestate->coercion_expr =
-						palloc(sizeof(*jsestate->coercion_expr));
-
-					caseval = &jsestate->coercion_expr->value;
-					casenull = &jsestate->coercion_expr->isnull;
-
-					for (cstate = &jsestate->coercions->null,
-						 coercion = &jexpr->coercions->null;
-						 coercion <= &jexpr->coercions->composite;
-						 coercion++, cstate++)
-					{
-						cstate->coercion = *coercion;
-						cstate->estate = *coercion ?
-							ExecInitExprWithCaseValue((Expr *) (*coercion)->expr,
-													  state->parent,
-													  caseval, casenull) : NULL;
-					}
-				}
+		case T_JsonItemCoercions:
+			{
+				elog(ERROR, "JsonItemCoercions must be initialized manually");
+				break;
+			}
 
-				ExprEvalPushStep(state, &scratch);
+		case T_JsonCoercion:
+			{
+				elog(ERROR, "JsonCoercion must be initialized manually");
 				break;
 			}
 
@@ -4259,3 +4168,559 @@ 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 bool
+ExecExprNeedsSubTransaction(Node *expr)
+{
+	/* No need for sub-tranactions for Const expressions */
+	if (IsA(expr, Const))
+		return false;
+
+	/* TODO check other expression types */
+	return true;
+}
+
+bool
+ExecJsonExprNeedsSubTransaction(JsonExpr *jsexpr)
+{
+	if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+		return false;	/* no error handling */
+
+	if (jsexpr->result_coercion &&
+		(jsexpr->result_coercion->expr ||
+		 jsexpr->result_coercion->via_io ||
+		 jsexpr->result_coercion->via_populate))
+		return true;
+
+	if (jsexpr->on_empty &&
+		jsexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT &&
+		ExecExprNeedsSubTransaction(jsexpr->on_empty->default_expr))
+		return true;
+
+	if (jsexpr->op == JSON_EXISTS_OP)
+		return false;
+
+	if (jsexpr->op == JSON_VALUE_OP && jsexpr->coercions)
+		return true;	/* may call item coercions */
+
+	if (jsexpr->op == JSON_QUERY_OP && jsexpr->omit_quotes)
+		return true;	/* may call coercion via I/O */
+
+	return false;
+}
+
+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->expr &&
+			!coercion->via_io &&
+			!coercion->via_populate)
+			return -1;	/* no coercion is needed */
+	}
+
+	/* Check whether expression really needs sub-transactions */
+	if (need_subtrans)
+		need_subtrans = ExecExprNeedsSubTransaction(expr);
+
+	/* Emit EEOP_JSONEXPR_SUBTRANS step */
+	if (need_subtrans)
+	{
+		cxt->adjust_subtrans =
+			lappend_int(cxt->adjust_subtrans, state->steps_len);
+
+		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->expr)
+			/* Push step(s) to compute coercion->expr */
+			ExecInitJsonCoercionExpr(state, (Expr *) coercion->expr,
+									 cxt->resv, cxt->resnull);
+		else if (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->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 JsonItemCoercionsSteps *
+ExecInitJsonItemCoercions(ExecInitJsonContext *cxt,
+						  JsonItemCoercions *coercions,
+						  bool need_subtrans)
+{
+	JsonItemCoercionsSteps *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)
+		{
+			Assert(!(*coercion)->via_io && !(*coercion)->via_populate);
+
+			if ((*coercion)->expr)
+				*step = ExecInitJsonCoercion(cxt, (Node *) *coercion,
+											 need_subtrans);
+			else
+				*step = 0;	/* No coercion is needed */
+		}
+		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;
+		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 special output
+		 * parameter, else append it to jump_done list.
+		 */
+		if (cxt->jsestate->jsexpr->result_coercion)
+			*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 = palloc0(sizeof(JsonExprState));
+	JsonExprPreEvalState *pre_eval = &jsestate->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;
+
+	cxt.state = state;
+	cxt.scratch = scratch;
+	cxt.jsestate = jsestate;
+	cxt.resv = resv;
+	cxt.resnull = resnull;
+	cxt.adjust_jumps = NIL;
+	cxt.adjust_subtrans = NIL;
+
+	jsestate->jsexpr = jexpr;
+
+	/*
+	 * 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->via_io))
+	{
+		JsonCoercion io_coercion = {0};
+
+		io_coercion.type = T_JsonCoercion;
+		io_coercion.via_io = true;
+
+		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);
+
+	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;
+
+	/* 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 = 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 = 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 ||
+		   ExecJsonExprNeedsSubTransaction(jexpr));
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 662d47f8cbb..15278b296cd 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 */
-			ExecEvalJson(state, op, econtext);
+			EEO_JUMP(ExecEvalJson(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();
 		}
 
@@ -4799,550 +4824,300 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
 }
 
 /*
- * Evaluate a JSON error/empty behavior result.
+ * 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
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
-					 ExprState *default_estate, bool *is_null)
+ExecPrepareJsonItemCoercion(JsonbValue *item, ExprEvalStep *op,
+							JsonItemCoercionsSteps *steps, int *jump)
 {
-	*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;
-	}
-}
-
-/*
- * Evaluate a coercion of a JSON item to the target type.
- */
-static Datum
-ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
-						 Datum res, bool *isNull, void *p, bool *error)
-{
-	ExprState  *estate = p;
-	JsonExprState *jsestate;
-
-	if (estate)					/* coerce using specified expression */
-		return ExecEvalExpr(estate, econtext, isNull);
-
-	jsestate = op->d.jsonexpr.jsestate;
-
-	if (jsestate->jsexpr->op != JSON_EXISTS_OP)
-	{
-		JsonCoercion *coercion = jsestate->jsexpr->result_coercion;
-		JsonExpr   *jexpr = jsestate->jsexpr;
-		Jsonb	   *jb = *isNull ? NULL : DatumGetJsonbP(res);
-
-		if ((coercion && coercion->via_io) ||
-			(jexpr->omit_quotes && !*isNull &&
-			 JB_ROOT_IS_SCALAR(jb)))
-		{
-			/* strip quotes and call typinput function */
-			char	   *str = *isNull ? NULL : JsonbUnquote(jb);
-
-			return InputFunctionCall(&jsestate->input.func, str,
-									 jsestate->input.typioparam,
-									 jexpr->returning->typmod);
-		}
-		else if (coercion && coercion->via_populate)
-			return json_populate_type(res, JSONBOID,
-									  jexpr->returning->typid,
-									  jexpr->returning->typmod,
-									  &jsestate->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);
-	}
-
-	return res;
-}
-
-/*
- * 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);
+	JsonbValue 	buf;
 
-		if (!strncmp(var->name, varName, varNameLen))
-			break;
-
-		var = NULL;
-		id++;
-	}
-
-	if (!var)
-		return -1;
-
-	if (!var->evaluated)
+	/*
+	 * Special case for json and jsonb types for which we don't have
+	 * casts like numeric::jsonb etc.
+	 */
+	if (!steps)
 	{
-		MemoryContext oldcxt = var->mcxt ?
-		MemoryContextSwitchTo(var->mcxt) : NULL;
-
-		var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
-		var->evaluated = true;
+		Jsonb	   *jb = JsonbValueToJsonb(item);
 
-		if (oldcxt)
-			MemoryContextSwitchTo(oldcxt);
-	}
+		/*
+		 * Use result coercion from json[b] to the output
+		 * type when casting to json[b] types or their
+		 * domains.
+		 */
 
-	if (var->isnull)
-	{
-		val->type = jbvNull;
-		return 0;
+		*jump = op->d.jsonexpr.jump_coercion_subtrans;
+		return JsonbPGetDatum(jb);;
 	}
 
-	JsonItemFromDatum(var->value, var->typid, var->typmod, val);
-
-	*baseObject = *val;
-	return id;
-}
-
-/*
- * Prepare SQL/JSON item coercion to the output type. Returned a datum of the
- * corresponding SQL type and a pointer to the coercion state.
- */
-Datum
-ExecPrepareJsonItemCoercion(JsonbValue *item,
-							JsonReturning *returning,
-							struct JsonCoercionsState *coercions,
-							struct JsonCoercionState **pcoercion)
-{
-	struct JsonCoercionState *coercion;
-	Datum		res;
-	JsonbValue	buf;
-
+	/* 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:
-			coercion = &coercions->null;
-			res = (Datum) 0;
-			break;
+			*jump = steps->null;
+			return (Datum) 0;
 
 		case jbvString:
-			coercion = &coercions->string;
-			res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
-														   item->val.string.len));
-			break;
+			*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;
-			res = NumericGetDatum(item->val.numeric);
-			break;
+			*jump = steps->numeric;
+			return NumericGetDatum(item->val.numeric);
 
 		case jbvBool:
-			coercion = &coercions->boolean;
-			res = BoolGetDatum(item->val.boolean);
-			break;
+			*jump = steps->boolean;
+			return BoolGetDatum(item->val.boolean);
 
 		case jbvDatetime:
-			res = item->val.datetime.value;
 			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",
 						 item->val.datetime.typid);
 					return (Datum) 0;
 			}
-			break;
+
+			return item->val.datetime.value;
 
 		case jbvArray:
 		case jbvObject:
 		case jbvBinary:
-			coercion = &coercions->composite;
-			res = JsonbPGetDatum(JsonbValueToJsonb(item));
-			break;
+			*jump = steps->composite;
+			return *jump < 0 ? (Datum) 0 :
+				JsonbPGetDatum(JsonbValueToJsonb(item));
 
 		default:
 			elog(ERROR, "unexpected jsonb value type %d", item->type);
 			return (Datum) 0;
 	}
-
-	*pcoercion = coercion;
-
-	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,
-						 Datum res, bool *resnull,
-						 void *p, bool *error, bool subtrans)
-{
-	MemoryContext oldcontext;
-	ResourceOwner oldowner;
-
-	if (!subtrans)
-		/* No need to use subtransactions. */
-		return func(op, econtext, res, resnull, p, error);
-
-	/*
-	 * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
-	 * execute the corresponding ON ERROR behavior then.
-	 */
-	oldcontext = CurrentMemoryContext;
-	oldowner = CurrentResourceOwner;
-
-	Assert(error);
-
-	BeginInternalSubTransaction(NULL);
-	/* Want to execute expressions inside function's memory context */
-	MemoryContextSwitchTo(oldcontext);
-
-	PG_TRY();
-	{
-		res = func(op, econtext, res, resnull, p, error);
-
-		/* Commit the inner transaction, return to outer xact context */
-		ReleaseCurrentSubTransaction();
-		MemoryContextSwitchTo(oldcontext);
-		CurrentResourceOwner = oldowner;
-	}
-	PG_CATCH();
-	{
-		ErrorData  *edata;
-		int			ecategory;
-
-		/* Save error info in oldcontext */
-		MemoryContextSwitchTo(oldcontext);
-		edata = CopyErrorData();
-		FlushErrorState();
-
-		/* Abort the inner transaction */
-		RollbackAndReleaseCurrentSubTransaction();
-		MemoryContextSwitchTo(oldcontext);
-		CurrentResourceOwner = 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);
-
-		res = (Datum) 0;
-		*error = true;
-	}
-	PG_END_TRY();
-
-	return res;
 }
 
-
-typedef struct
-{
-	JsonPath   *path;
-	bool	   *error;
-	bool		coercionInSubtrans;
-} ExecEvalJsonExprContext;
-
-static Datum
-ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
-				 Datum item, bool *resnull, void *pcxt,
-				 bool *error)
+/*
+ * Evaluate a SQL/JSON function.
+ */
+int
+ExecEvalJson(ExprState *state, ExprEvalStep *op)
 {
-	ExecEvalJsonExprContext *cxt = pcxt;
-	JsonPath   *path = cxt->path;
 	JsonExprState *jsestate = op->d.jsonexpr.jsestate;
+	JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;
 	JsonExpr   *jexpr = jsestate->jsexpr;
-	ExprState  *estate = NULL;
-	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;
+
+	*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)
-			{
-				*resnull = true;
-				return (Datum) 0;
-			}
-			*resnull = !DatumGetPointer(res);
+			res = JsonPathQuery(item, path, jexpr->wrapper, &empty,
+								p_error, pre_eval->args);
+
+			if (error)
+				return op->d.jsonexpr.jump_onerror;
+
+			*op->resnull = !DatumGetPointer(res);
+			*op->resvalue = res;
+
+			/* Force coercion via I/O if OMIT QUOTES is used */
+			if (jexpr->omit_quotes && !empty && !*op->resnull &&
+				JB_ROOT_IS_SCALAR(DatumGetJsonbP(res)))
+				return op->d.jsonexpr.jump_coercion_via_io;
+
 			break;
 
 		case JSON_VALUE_OP:
 			{
-				struct JsonCoercionState *jcstate;
-				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 */
 					break;
 
 				Assert(!empty);
+				Assert(jbv->type != jbvNull);
 
-				*resnull = false;
+				/*
+				 * Prepare coercion from SQL/JSON item type to the
+				 * output SQL type.
+				 */
+				res = ExecPrepareJsonItemCoercion(jbv, op,
+												  jsestate->item_coercions,
+												  &jump_item_coercion);
 
-				/* coerce scalar item to the output type */
-				if (!jsestate->coercions)
-				{
-					/*
-					 * Use result coercion from json[b] to the output
-					 * type when casting to json[b] types or their
-					 * domains.
-					 */
-					res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
-					break;
-				}
+				*op->resvalue = res;
+				*op->resnull = false;
 
-				/* Use coercion from SQL/JSON item type to the output type */
-				res = ExecPrepareJsonItemCoercion(jbv,
-												  jsestate->jsexpr->returning,
-												  jsestate->coercions,
-												  &jcstate);
+				/* Jump to zero here means jump to the end. */
+				if (jump_item_coercion == 0)
+					return op->d.jsonexpr.jump_done;
 
-				if (jcstate->coercion &&
-					(jcstate->coercion->via_io ||
-					 jcstate->coercion->via_populate))
+				/*
+				 * Error out if no cast exists to coerce SQL/JSON item
+				 * to the the output type.
+				 */
+				if (jump_item_coercion < 0)
 				{
-					if (error)
-					{
-						*error = true;
-						return (Datum) 0;
-					}
+					*op->resnull = true;
+
+					if (catch_errors)
+						return op->d.jsonexpr.jump_onerror;
 
-					/*
-					 * Coercion via I/O means here that the cast to the target
-					 * type simply does not exist.
-					 */
 					ereport(ERROR,
 							(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;
-				break;
+				/* 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);
+				*op->resnull = error;
+				*op->resvalue = BoolGetDatum(exists);
 
-				if (!jsestate->result_expr)
-					return res;
+				if (error)
+					return op->d.jsonexpr.jump_onerror;
 
-				/* coerce using result expression */
-				estate = jsestate->result_expr;
-				jsestate->res_expr->value = res;
-				jsestate->res_expr->isnull = *resnull;
 				break;
 			}
 
 		case JSON_TABLE_OP:
-			*resnull = false;
-			return item;
+			*op->resvalue = item;
+			*op->resnull = false;
+			return op->d.jsonexpr.jump_done;
 
 		default:
 			elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
-			return (Datum) 0;
+			return op->d.jsonexpr.jump_done;
 	}
 
+	/* Execute ON EMPTY behavior if no SQL/JSON items were found */
 	if (empty)
 	{
 		Assert(jexpr->on_empty);	/* it is not JSON_EXISTS */
 
 		if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
 		{
-			if (error)
-			{
-				*error = true;
-				return (Datum) 0;
-			}
+			/* Throw an error or jump to ON ERROR handler */
+			if (catch_errors)
+				return op->d.jsonexpr.jump_onerror;
 
 			ereport(ERROR,
 					(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);
+		/* Evaluate ON EMPTY behavior */
+		return op->d.jsonexpr.jump_onempty;
 	}
 
-	return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
-									res, resnull, estate, error,
-									cxt->coercionInSubtrans);
+	/* Evaluate coercion expression inside subtransaction */
+	return op->d.jsonexpr.jump_coercion_subtrans;
 }
 
+/* Skip calling ExecEvalJson() on a JsonExpr? */
 bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
-								bool consider_conditional_coercions)
+ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
 {
-	if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
-		return false;
-
-	if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
-		return false;
+	JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate;
 
-	if (consider_conditional_coercions)
+	/*
+	 * Skip if either of the input expressions has turned out to be
+	 * NULL, though do execute domain checks for NULLs, which are
+	 * handled by the coercion step.
+	 */
+	if (jsestate->pre_eval.formatted_expr.isnull ||
+		jsestate->pre_eval.pathspec.isnull)
+	{
+		*op->resvalue = (Datum) 0;
+		*op->resnull = true;
 		return true;
+	}
 
+	/*
+	 * Go evaluate the PASSING args if any and subsequently JSON path
+	 * itself.
+	 */
 	return false;
 }
 
-/* ----------------------------------------------------------------
- *		ExecEvalJson
- * ----------------------------------------------------------------
- */
+/* Apply I/O coercion to a JSON item */
 void
-ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op)
 {
-	ExecEvalJsonExprContext cxt;
-	JsonExprState *jsestate = op->d.jsonexpr.jsestate;
-	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);
+	/* Strip quotes and call typinput function */
+	char	   *str = NULL;
 
-		Assert(*op->resnull);
-		return;
-	}
-
-	item = jsestate->formatted_expr->value;
-	path = DatumGetJsonPathP(jsestate->pathspec->value);
-
-	/* 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);
 	}
 
-	needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, false);
-
-	cxt.path = path;
-	cxt.error = throwErrors ? NULL : &error;
-	cxt.coercionInSubtrans = !needSubtrans && !throwErrors;
-	Assert(!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);
-	}
+	*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 614ed58f1cb..5245fe765c5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2465,8 +2465,141 @@ llvm_compile_expr_step(ExprState *state, int start_opno)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
-			case EEOP_JSONEXPR:
-				build_EvalXFunc(b, mod, "ExecEvalJson",
+			case EEOP_JSONEXPR_PATH:
+				{
+					LLVMValueRef v_ret;
+					LLVMBasicBlockRef *b_coercions;
+					JsonItemCoercionsSteps *jcstate =
+						op->d.jsonexpr.jsestate->item_coercions;
+					int			n_jumps = 6;
+					int			n_coercions = !jcstate ? 0 :
+						(int) (&jcstate->composite - &jcstate->null) + 1;
+					int		   *jumps;
+
+					/*
+					 * ExecEvalJson() returns number of the step we
+					 * need to jump.
+					 */
+					v_ret = build_EvalXFunc(b, mod, "ExecEvalJson",
+											v_state, op);
+
+					/*
+					 * Will create a block for each possible jump to
+					 * check whether to evaluate the coercion's
+					 * expression if there's one or to skip to the end
+					 * if not.
+					 */
+					b_coercions = palloc((n_jumps + n_coercions) * sizeof(*b_coercions));
+					jumps = palloc((n_jumps + n_coercions) * sizeof(*jumps));
+
+					jumps[0] = op->d.jsonexpr.jump_done;
+					jumps[1] = op->d.jsonexpr.jump_coercion;
+					jumps[2] = op->d.jsonexpr.jump_coercion_subtrans;
+					jumps[3] = op->d.jsonexpr.jump_coercion_via_io;
+					jumps[4] = op->d.jsonexpr.jump_onempty;
+					jumps[5] = op->d.jsonexpr.jump_onerror;
+
+					b_coercions[0] =
+						l_bb_before_v(opblocks[opno + 1],
+									  "op.%d.jsonexpr_jump_coercion", opno);
+					b_coercions[1] =
+						l_bb_before_v(opblocks[opno + 1],
+									  "op.%d.jsonexpr_jump_coercion_subtrans", opno);
+					b_coercions[2] =
+						l_bb_before_v(opblocks[opno + 1],
+									  "op.%d.jsonexpr_jump_coercion_io", opno);
+					b_coercions[3] =
+						l_bb_before_v(opblocks[opno + 1],
+									  "op.%d.jsonexpr_jump_onempty", opno);
+					b_coercions[4] =
+						l_bb_before_v(opblocks[opno + 1],
+									  "op.%d.jsonexpr_jump_onerror", opno);
+
+					for (int i = 0; i < n_coercions; i++)
+						jumps[i + n_jumps] = (&jcstate->null)[i];
+
+					for (int i = 0; i < n_coercions + 1; i++)
+						b_coercions[i + n_jumps - 1] =
+							l_bb_before_v(opblocks[opno + 1],
+										  "op.%d.jsonexpr_jump_item.%d",
+										  opno, i);
+
+					/*
+					 * Add conditional branches for individual
+					 * coercion's expressions.
+					 *
+					 * Jump to coercion step if the returned address is
+					 * the same as jumps[i], else jump to the next
+					 * block, that checks next jump address.
+					 */
+					for (int i = 0; i < n_jumps + n_coercions; i++)
+					{
+						if (i > 0)
+							LLVMPositionBuilderAtEnd(b, b_coercions[i - 1]);
+
+						LLVMBuildCondBr(b,
+										LLVMBuildICmp(b,
+													  LLVMIntEQ,
+													  v_ret,
+													  l_int32_const(jumps[i]),
+													  ""),
+										opblocks[jumps[i] >= 0 ? jumps[i] : jumps[0]],
+										b_coercions[i]);
+					}
+
+					/*
+					 * A placeholder block that the last coercion's
+					 * block might jump to, which unconditionally jumps
+					 * to end of coercions.
+					 */
+					LLVMPositionBuilderAtEnd(b, b_coercions[n_jumps + n_coercions - 1]);
+					LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);
+
+					pfree(b_coercions);
+					pfree(jumps);
+					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 a9ddba6e4cd..d47d065dc59 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -136,6 +136,9 @@ void	   *referenced_functions[] =
 	ExecEvalSubtrans,
 	ExecEvalJsonConstructor,
 	ExecEvalJsonIsPredicate,
+	ExecEvalJsonExprSkip,
+	ExecEvalJsonExprIOCoerce,
+	ExecEvalJsonExprPopulate,
 	ExecEvalJson,
 	MakeExpandedObjectReadOnlyInternal,
 	slot_getmissingattrs,
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a1d868bb87b..e9eee8c1a0c 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -901,7 +901,7 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 	{
 		JsonExpr   *jsexpr = (JsonExpr *) node;
 
-		if (ExecEvalJsonNeedsSubTransaction(jsexpr, true))
+		if (ExecJsonExprNeedsSubTransaction(jsexpr))
 		{
 			context->max_hazard = PROPARALLEL_UNSAFE;
 			return true;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 31fb5555659..41f4acbc262 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4329,6 +4329,7 @@ initJsonItemCoercion(ParseState *pstate, Oid typid,
 					 const JsonReturning *returning)
 {
 	Node	   *expr;
+	JsonCoercion *coercion;
 
 	if (typid == UNKNOWNOID)
 	{
@@ -4345,7 +4346,16 @@ initJsonItemCoercion(ParseState *pstate, Oid typid,
 		expr = (Node *) placeholder;
 	}
 
-	return coerceJsonExpr(pstate, expr, returning);
+	coercion = coerceJsonExpr(pstate, expr, returning);
+
+	/*
+	 * Coercion via I/O means here that the cast to the target type
+	 * simply does not exist.
+	 */
+	if (coercion->via_io || coercion->via_populate)
+		return NULL;
+
+	return coercion;
 }
 
 static JsonItemCoercions *
@@ -4373,6 +4383,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);
 
@@ -4453,6 +4468,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);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 10c7e64aab3..c3fc77cf515 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 14064dd9521..432876fb01f 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -245,7 +245,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,
@@ -702,12 +705,48 @@ 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;
 		}			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;
 
@@ -767,60 +806,67 @@ typedef struct JsonConstructorExprState
 	int			nargs;
 } JsonConstructorExprState;
 
+/*
+ * Information computed before evaluating a JsonExpr expression.
+ */
+typedef struct JsonExprPreEvalState
+{
+	/* value/isnull for JsonExpr.formatted_expr */
+	NullableDatum formatted_expr;
+
+	/* value/isnull for JsonExpr.pathspec */
+	NullableDatum pathspec;
+
+	/* JsonPathVariableEvalContext entries for JsonExpr.passing_values */
+	List	   *args;
+}	JsonExprPreEvalState;
+
+/*
+ * State for 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 JsonItemCoercionsSteps
+{
+	/* 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;
+} JsonItemCoercionsSteps;
+
 /* 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 */
-			   *res_expr,		/* result item */
-			   *coercion_expr,	/* input for JSON item coercion */
-			   *pathspec;		/* path specification value */
+	JsonExprPreEvalState pre_eval;
 
-	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() */
+	/*
+	 * Either of the following two is used by ExecEvalJsonCoercion()
+	 * to apply coercion to the final result if needed.
+	 */
 
 	/*
-	 * This is used only in JSON_VALUE.
-	 *
-	 * States for coercion from SQL/JSON item types directly to the
-	 * output type.
-	 *
-	 * "coercions == NULL" means output type is json[b] or its domain
-	 * and we use "result_expr" for coercion.
+	 * In JSON_VALUE() "item_coercions == NULL" means that output type
+	 * is json[b] or its domain and we use "result_coercion".
 	 */
-	struct JsonCoercionsState
-	{
-		struct JsonCoercionState
-		{
-			JsonCoercion *coercion; /* coercion expression */
-			ExprState  *estate; /* coercion expression state */
-		}			null,
-					string,
-		numeric    ,
-					boolean,
-					date,
-					time,
-					timetz,
-					timestamp,
-					timestamptz,
-					composite;
-	}		   *coercions;
+	JsonItemCoercionsSteps *item_coercions;
 } JsonExprState;
 
 /* functions in execExpr.c */
 extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
+extern bool ExecJsonExprNeedsSubTransaction(JsonExpr *jsexpr);
 
 /* functions in execExprInterp.c */
 extern void ExecReadyInterpretedExpr(ExprState *state);
@@ -881,19 +927,11 @@ extern bool ExecEvalSubtrans(ExprState *state, ExprEvalStep *op,
 							 ExecEvalSubroutine func);
 extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
 									ExprContext *econtext);
-extern void ExecEvalJson(ExprState *state, ExprEvalStep *op,
-						 ExprContext *econtext);
-extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,
-										 JsonReturning *returning,
-										 struct JsonCoercionsState *coercions,
-										 struct JsonCoercionState **pjcstate);
-extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
-											bool consider_conditional_coercions);
-extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
-										  ExprContext *econtext, bool *isnull,
-										  Datum caseval_datum,
-										  bool caseval_isnull);
-
+extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonExprPopulate(ExprState *state, ExprEvalStep *op,
+									 ExprContext *econtext);
+extern int ExecEvalJson(ExprState *state, ExprEvalStep *op);
 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 3aa96bb6855..6afb4228730 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1566,8 +1566,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
 {
@@ -1581,7 +1584,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

