From 99cf2d1c6cea1889120da5e845b17ccf5db10332 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Fri, 12 Aug 2022 11:07:06 -0700
Subject: [PATCH v7 1/4] Simplify JsonExpr execution

json: rename ExecEvalJson() to ExecEvalJsonExpr()

It was confusing that EEOP_JSONEXPR was evaluated by ExecEvalJson() but that a
ExecEvalJsonExpr() also existed. The latter is now renamed to
ExecEvalJsonExprInternal(), but something better might be possible.

json: make ExecPrepareJsonItemCoercion static

json: Remove unused 'returning' argument to ExecPrepareJsonItemCoercion()'s

json: wip: catching errors via macro, cleanup singatures.

wip: no need for subtrans in ExecEvalJsonExpr()???

Remove JSE_OPT_SUBTRANS's parameter coercionInSubtrans

Refactor JsonExpr coercions

More correctly implement ExecEvalJsonNeedsSubTransaction()

Emit only context item for JSON_TABLE

Skip coercion of NULLs for non-domain types
---
 src/backend/executor/execExpr.c       | 109 +++--
 src/backend/executor/execExprInterp.c | 559 ++++++++++++++------------
 src/backend/jit/llvm/llvmjit_expr.c   |   2 +-
 src/backend/jit/llvm/llvmjit_types.c  |   2 +-
 src/backend/nodes/nodeFuncs.c         |  10 +-
 src/backend/optimizer/util/clauses.c  |   2 +-
 src/backend/parser/parse_expr.c       |  62 ++-
 src/include/executor/execExpr.h       |  55 ++-
 src/include/nodes/primnodes.h         |  18 +-
 9 files changed, 472 insertions(+), 347 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index d0a57c7aaee..1c9782de15f 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2561,49 +2561,72 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_JsonExpr:
 			{
 				JsonExpr   *jexpr = castNode(JsonExpr, node);
-				JsonExprState *jsestate = palloc0(sizeof(JsonExprState));
+				JsonExprState *jsestate;
+				JsonCoercion *result_coercion = jexpr->result_coercion;
 				ListCell   *argexprlc;
 				ListCell   *argnamelc;
 
-				scratch.opcode = EEOP_JSONEXPR;
-				scratch.d.jsonexpr.jsestate = jsestate;
+				/* 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;
 
-				jsestate->formatted_expr =
-					palloc(sizeof(*jsestate->formatted_expr));
+				scratch.opcode = EEOP_JSONEXPR;
+				scratch.d.jsonexpr.jsestate = jsestate;
 
 				ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
-								&jsestate->formatted_expr->value,
-								&jsestate->formatted_expr->isnull);
-
-				jsestate->pathspec =
-					palloc(sizeof(*jsestate->pathspec));
+								&jsestate->formatted_expr.value,
+								&jsestate->formatted_expr.isnull);
 
 				ExecInitExprRec((Expr *) jexpr->path_spec, state,
-								&jsestate->pathspec->value,
-								&jsestate->pathspec->isnull);
+								&jsestate->pathspec.value,
+								&jsestate->pathspec.isnull);
 
-				jsestate->res_expr =
-					palloc(sizeof(*jsestate->res_expr));
+				if (result_coercion)
+				{
+					jsestate->result_coercion.type = result_coercion->ctype;
 
-				jsestate->result_expr = jexpr->result_coercion
-					? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,
-												state->parent,
-												&jsestate->res_expr->value,
-												&jsestate->res_expr->isnull)
-					: NULL;
+					if (result_coercion->expr)
+						jsestate->result_coercion.estate =
+							ExecInitExprWithCaseValue((Expr *) result_coercion->expr,
+													  state->parent,
+													  &jsestate->coercion_expr.value,
+													  &jsestate->coercion_expr.isnull);
 
-				jsestate->default_on_empty = !jexpr->on_empty ? NULL :
-					ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
-								 state->parent);
+					/* 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 ||
-					(jexpr->result_coercion && jexpr->result_coercion->via_io))
+					jsestate->result_coercion.type == JSON_COERCION_VIA_IO)
 				{
 					Oid			typinput;
 
@@ -2641,30 +2664,46 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				if (jexpr->coercions)
 				{
 					JsonCoercion **coercion;
-					struct JsonCoercionState *cstate;
+					JsonCoercionState *cstate;
 					Datum	   *caseval;
 					bool	   *casenull;
 
-					jsestate->coercion_expr =
-						palloc(sizeof(*jsestate->coercion_expr));
+					jsestate->item_coercions = palloc0(sizeof(*jsestate->item_coercions));
 
-					caseval = &jsestate->coercion_expr->value;
-					casenull = &jsestate->coercion_expr->isnull;
+					caseval = &jsestate->coercion_expr.value;
+					casenull = &jsestate->coercion_expr.isnull;
 
-					for (cstate = &jsestate->coercions.null,
+					for (cstate = &jsestate->item_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;
+						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);
+
 				break;
 			}
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f1..bf141a66066 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1844,7 +1844,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		EEO_CASE(EEOP_JSONEXPR)
 		{
 			/* too complex for an inline implementation */
-			ExecEvalJson(state, op, econtext);
+			ExecEvalJsonExpr(state, op, econtext);
 			EEO_NEXT();
 		}
 
@@ -4738,56 +4738,159 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
 	}
 }
 
+
+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);
+	}
+}
+
 /*
- * Evaluate a coercion of a JSON item to the target type.
+ * 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(ExprEvalStep *op, ExprContext *econtext,
-						 Datum res, bool *isNull, void *p, bool *error)
+ExecEvalJsonExprCoercion(JsonExprState *jsestate, ExprContext *econtext,
+						 JsonCoercionState *coercion,
+						 Datum res, bool *resnull, bool *error)
 {
-	ExprState  *estate = p;
-	JsonExprState *jsestate;
+	switch (coercion->type)
+	{
+		case JSON_COERCION_NONE:
+			return (Datum) res;
 
-	if (estate)					/* coerce using specified expression */
-		return ExecEvalExpr(estate, econtext, isNull);
+		case JSON_COERCION_VIA_EXPR:
+			jsestate->coercion_expr.value = res;
+			jsestate->coercion_expr.isnull = *resnull;
+			return ExecEvalExpr(coercion->estate, econtext, resnull);
 
-	jsestate = op->d.jsonexpr.jsestate;
+		case JSON_COERCION_VIA_IO:
+			{
+				/* Strip quotes from jsonb strings and call typinput() */
+				char	   *str;
 
-	if (jsestate->jsexpr->op != JSON_EXISTS_OP)
-	{
-		JsonCoercion *coercion = jsestate->jsexpr->result_coercion;
-		JsonExpr   *jexpr = jsestate->jsexpr;
-		Jsonb	   *jb = *isNull ? NULL : DatumGetJsonbP(res);
+				if (*resnull)
+					str = NULL;
+				else
+					str = JsonbUnquote(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,
+										 jsestate->jsexpr->returning->typmod);
+			}
 
-			return InputFunctionCall(&jsestate->input.func, str,
-									 jsestate->input.typioparam,
-									 jexpr->returning->typmod);
-		}
-		else if (coercion && coercion->via_populate)
+		case JSON_COERCION_VIA_POPULATE:
 			return json_populate_type(res, JSONBOID,
-									  jexpr->returning->typid,
-									  jexpr->returning->typmod,
+									  jsestate->jsexpr->returning->typid,
+									  jsestate->jsexpr->returning->typmod,
 									  &jsestate->cache,
 									  econtext->ecxt_per_query_memory,
-									  isNull);
-	}
+									  resnull);
 
-	if (jsestate->result_expr)
-	{
-		jsestate->res_expr->value = res;
-		jsestate->res_expr->isnull = *isNull;
+		default:
+			Assert(coercion->type == JSON_COERCION_ERROR);
 
-		res = ExecEvalExpr(jsestate->result_expr, econtext, isNull);
-	}
+			/* 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")));
 
-	return res;
+			*error = true;
+			*resnull = true;
+			return (Datum) 0;
+	}
 }
 
 /*
@@ -4844,19 +4947,48 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
 }
 
 /*
- * Prepare SQL/JSON item coercion to the output type. Returned a datum of the
- * corresponding SQL type and a pointer to the coercion state.
+ * Check whether we need to override default coercion in
+ * JSON_QUERY(OMIT QUOTES) case.
  */
-Datum
-ExecPrepareJsonItemCoercion(JsonbValue *item,
-							JsonReturning *returning,
-							struct JsonCoercionsState *coercions,
-							struct JsonCoercionState **pcoercion)
+static bool
+ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res, bool isnull)
 {
-	struct JsonCoercionState *coercion;
-	Datum		res;
+	if (jsexpr->omit_quotes && !isnull)
+	{
+		Jsonb	   *jb = DatumGetJsonbP(res);
+
+		/* Coerce non-null scalar items via I/O in OMIT QUOTES case */
+		if (JB_ROOT_IS_SCALAR(jb))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * 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.
+ */
+static Datum
+ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item,
+							 JsonCoercionState **coercion, bool *resnull)
+{
+	struct JsonItemsCoercionStates *coercions = jsestate->item_coercions;
 	JsonbValue	buf;
 
+	*resnull = false;
+
+	if (!coercions)
+	{
+		/*
+		 * Special case for json[b] output types.  Simply use
+		 * default coercion from jsonb to the output type.
+		 */
+		return JsonbPGetDatum(JsonbValueToJsonb(item));
+	}
+
+	/* Use coercion from SQL/JSON item type to the output type */
 	if (item->type == jbvBinary &&
 		JsonContainerIsScalar(item->val.binary.data))
 	{
@@ -4871,174 +5003,96 @@ ExecPrepareJsonItemCoercion(JsonbValue *item,
 	switch (item->type)
 	{
 		case jbvNull:
-			coercion = &coercions->null;
-			res = (Datum) 0;
-			break;
+			Assert(0); /* must be handled by the caller */
+			*coercion = &coercions->null;
+			*resnull = true;
+			return (Datum) 0;
 
 		case jbvString:
-			coercion = &coercions->string;
-			res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
-														   item->val.string.len));
-			break;
+			*coercion = &coercions->string;
+			return 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;
+			*coercion = &coercions->numeric;
+			return NumericGetDatum(item->val.numeric);
 
 		case jbvBool:
-			coercion = &coercions->boolean;
-			res = BoolGetDatum(item->val.boolean);
-			break;
+			*coercion = &coercions->boolean;
+			return BoolGetDatum(item->val.boolean);
 
 		case jbvDatetime:
-			res = item->val.datetime.value;
 			switch (item->val.datetime.typid)
 			{
 				case DATEOID:
-					coercion = &coercions->date;
+					*coercion = &coercions->date;
 					break;
 				case TIMEOID:
-					coercion = &coercions->time;
+					*coercion = &coercions->time;
 					break;
 				case TIMETZOID:
-					coercion = &coercions->timetz;
+					*coercion = &coercions->timetz;
 					break;
 				case TIMESTAMPOID:
-					coercion = &coercions->timestamp;
+					*coercion = &coercions->timestamp;
 					break;
 				case TIMESTAMPTZOID:
-					coercion = &coercions->timestamptz;
+					*coercion = &coercions->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;
+			Assert(0);	/* non-scalars must be rejected by JsonPathValue() */
+			*coercion = &coercions->composite;
+			return 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)
+ExecEvalJsonExprInternal(JsonExprState *jsestate, ExprContext *econtext,
+						 JsonPath *path, Datum item, bool *resnull,
+						 bool *error)
 {
-	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)
-{
-	ExecEvalJsonExprContext *cxt = pcxt;
-	JsonPath   *path = cxt->path;
-	JsonExprState *jsestate = op->d.jsonexpr.jsestate;
 	JsonExpr   *jexpr = jsestate->jsexpr;
-	ExprState  *estate = NULL;
+	JsonCoercionState *coercion = &jsestate->result_coercion;
 	bool		empty = false;
 	Datum		res = (Datum) 0;
 
+	*resnull = true;
+
 	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;
-			}
+
+			if (empty)
+				break;
+
 			*resnull = !DatumGetPointer(res);
+
+			/* Override default coercion in OMIT QUOTES case */
+			if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull))
+				coercion = &jsestate->io_coercion;
+
 			break;
 
 		case JSON_VALUE_OP:
 			{
-				struct JsonCoercionState *jcstate;
 				JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
 												jsestate->args);
 
@@ -5049,49 +5103,10 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 					break;
 
 				Assert(!empty);
+				Assert(jbv->type != jbvNull);
 
-				*resnull = false;
-
-				/* coerce scalar item to the output type */
-				if (jexpr->returning->typid == JSONOID ||
-					jexpr->returning->typid == JSONBOID)
-				{
-					/* Use result coercion from json[b] to the output type */
-					res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
-					break;
-				}
-
-				/* Use coercion from SQL/JSON item type to the output type */
-				res = ExecPrepareJsonItemCoercion(jbv,
-												  jsestate->jsexpr->returning,
-												  &jsestate->coercions,
-												  &jcstate);
-
-				if (jcstate->coercion &&
-					(jcstate->coercion->via_io ||
-					 jcstate->coercion->via_populate))
-				{
-					if (error)
-					{
-						*error = true;
-						return (Datum) 0;
-					}
-
-					/*
-					 * 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;
+				res = ExecPrepareJsonValueCoercion(jsestate, jbv,
+												   &coercion, resnull);
 				break;
 			}
 
@@ -5103,21 +5118,9 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 
 				*resnull = error && *error;
 				res = BoolGetDatum(exists);
-
-				if (!jsestate->result_expr)
-					return res;
-
-				/* coerce using result expression */
-				estate = jsestate->result_expr;
-				jsestate->res_expr->value = res;
-				jsestate->res_expr->isnull = *resnull;
-				break;
+				break;	/* always use result coercion */
 			}
 
-		case JSON_TABLE_OP:
-			*resnull = false;
-			return item;
-
 		default:
 			elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
 			return (Datum) 0;
@@ -5141,73 +5144,115 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
 		}
 
 		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.
+			 * 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.
 			 */
-			estate = jsestate->default_on_empty;
-		else
-			/* Execute ON EMPTY behavior */
-			res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,
-									   jsestate->default_on_empty,
-									   resnull);
+			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);
 	}
 
-	return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
-									res, resnull, estate, error,
-									cxt->coercionInSubtrans);
+	/*
+	 * 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)
+	{
+		/* Skip coercion of NULLs for non-domain types */
+		if (*resnull && jsestate->skip_null_coercion)
+			return (Datum) 0;
+
+		JSE_OPT_SUBTRANS(coercion->type != JSON_COERCION_ERROR ? error : NULL,
+						 jexpr, res,
+						 ExecEvalJsonExprCoercion(jsestate, econtext,
+												  coercion, res,
+												  resnull, error));
+	}
+
+	return res;
 }
 
 bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
-								struct JsonCoercionsState *coercions)
+ExecEvalExprCanThrowErrors(Node *expr)
 {
-	if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+	if (!expr)
 		return false;
 
-	if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
+	if (IsA(expr, Const))
 		return false;
 
-	if (!coercions)
-		return true;
+	/* TODO consider more cases */
+	return true;
+}
 
-	return false;
+/* Can SQL/JSON expression start subtransactions for error handling? */
+bool
+ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr)
+{
+	if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+		return false;		/* no error handling needed */
+
+	if (jsexpr->result_coercion &&
+		jsexpr->result_coercion->ctype != JSON_COERCION_NONE)
+		return true;		/* may execute non-empty result coercion */
+
+	if (jsexpr->on_empty &&
+		jsexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT &&
+		ExecEvalExprCanThrowErrors(jsexpr->on_empty->default_expr))
+		return true;		/* may execute arbitrary unsafe expression */
+
+	if (jsexpr->coercions)
+		return true;		/* JSON_VALUE may execute item coercions */
+
+	if (jsexpr->omit_quotes)
+		return true;		/* JSON_QUERY may execute coercion via I/O */
+
+	return false;			/* subtransactios should not be used */
 }
 
 /* ----------------------------------------------------------------
- *		ExecEvalJson
+ *		ExecEvalJsonExpr
  * ----------------------------------------------------------------
  */
 void
-ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
-	ExecEvalJsonExprContext cxt;
 	JsonExprState *jsestate = op->d.jsonexpr.jsestate;
 	JsonExpr   *jexpr = jsestate->jsexpr;
 	Datum		item;
-	Datum		res = (Datum) 0;
+	Datum		res;
 	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)
+	if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
 	{
+		*op->resnull = true;
+		*op->resvalue = (Datum) 0;
+
 		/* execute domain checks for NULLs */
-		(void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
-										NULL, NULL);
+		(void) ExecEvalJsonExprCoercion(jsestate, econtext,
+										&jsestate->result_coercion,
+										*op->resvalue, op->resnull, NULL);
 
 		Assert(*op->resnull);
 		return;
 	}
 
-	item = jsestate->formatted_expr->value;
-	path = DatumGetJsonPathP(jsestate->pathspec->value);
+	item = jsestate->formatted_expr.value;
+	path = DatumGetJsonPathP(jsestate->pathspec.value);
 
 	/* reset JSON path variable contexts */
 	foreach(lc, jsestate->args)
@@ -5218,19 +5263,13 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 		var->evaluated = false;
 	}
 
-	needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions);
-
-	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);
-
+	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,
@@ -5238,9 +5277,9 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 
 		/* 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);
+			res = ExecEvalJsonExprCoercion(jsestate, econtext,
+										   &jsestate->result_coercion,
+										   res, op->resnull, NULL);
 	}
 
 	*op->resvalue = res;
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index bd3965143da..fd72630f5e6 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2408,7 +2408,7 @@ llvm_compile_expr(ExprState *state)
 				break;
 
 			case EEOP_JSONEXPR:
-				build_EvalXFunc(b, mod, "ExecEvalJson",
+				build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
 								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 373471ad27f..37fe64654b6 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -135,7 +135,7 @@ void	   *referenced_functions[] =
 	ExecEvalXmlExpr,
 	ExecEvalJsonConstructor,
 	ExecEvalJsonIsPredicate,
-	ExecEvalJson,
+	ExecEvalJsonExpr,
 	MakeExpandedObjectReadOnlyInternal,
 	slot_getmissingattrs,
 	slot_getsomeattrs_int,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c334daae392..b7fdd0af9d8 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1014,9 +1014,10 @@ exprCollation(const Node *expr)
 
 				if (!coercion)
 					coll = InvalidOid;
-				else if (coercion->expr)
+				else if (coercion->ctype == JSON_COERCION_VIA_EXPR)
 					coll = exprCollation(coercion->expr);
-				else if (coercion->via_io || coercion->via_populate)
+				else if (coercion->ctype == JSON_COERCION_VIA_IO ||
+						 coercion->ctype == JSON_COERCION_VIA_POPULATE)
 					coll = coercion->collation;
 				else
 					coll = InvalidOid;
@@ -1259,9 +1260,10 @@ exprSetCollation(Node *expr, Oid collation)
 
 				if (!coercion)
 					Assert(!OidIsValid(collation));
-				else if (coercion->expr)
+				else if (coercion->ctype == JSON_COERCION_VIA_EXPR)
 					exprSetCollation(coercion->expr, collation);
-				else if (coercion->via_io || coercion->via_populate)
+				else if (coercion->ctype == JSON_COERCION_VIA_IO ||
+						 coercion->ctype == JSON_COERCION_VIA_POPULATE)
 					coercion->collation = collation;
 				else
 					Assert(!OidIsValid(collation));
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 533df86ff77..980c34cf49d 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, NULL))
+		if (ExecEvalJsonNeedsSubTransaction(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 fabb5f72076..3b82c0f3328 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4172,10 +4172,11 @@ assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format,
 /*
  * Try to coerce expression to the output type or
  * use json_populate_type() for composite, array and domain types or
- * use coercion via I/O.
+ * use coercion via I/O, if allowed.
  */
 static JsonCoercion *
-coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
+coerceJsonExpr(ParseState *pstate, Node *expr,
+			   const JsonReturning *returning, bool allow_io_coercion)
 {
 	char		typtype;
 	JsonCoercion *coercion = makeNode(JsonCoercion);
@@ -4185,20 +4186,28 @@ coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
 	if (coercion->expr)
 	{
 		if (coercion->expr == expr)
+		{
 			coercion->expr = NULL;
+			coercion->ctype = JSON_COERCION_NONE;
+		}
+		else
+			coercion->ctype = JSON_COERCION_VIA_EXPR;
 
 		return coercion;
 	}
 
+	if (!allow_io_coercion)
+		return NULL;
+
 	typtype = get_typtype(returning->typid);
 
 	if (returning->typid == RECORDOID ||
 		typtype == TYPTYPE_COMPOSITE ||
 		typtype == TYPTYPE_DOMAIN ||
 		type_is_array(returning->typid))
-		coercion->via_populate = true;
+		coercion->ctype = JSON_COERCION_VIA_POPULATE;
 	else
-		coercion->via_io = true;
+		coercion->ctype = JSON_COERCION_VIA_IO;
 
 	return coercion;
 }
@@ -4231,8 +4240,7 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
 		{
 			/* Forced coercion via I/O for JSON_VALUE for non-JSON types */
 			jsexpr->result_coercion = makeNode(JsonCoercion);
-			jsexpr->result_coercion->expr = NULL;
-			jsexpr->result_coercion->via_io = true;
+			jsexpr->result_coercion->ctype = JSON_COERCION_VIA_IO;
 			return;
 		}
 
@@ -4247,7 +4255,8 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
 			Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod);
 
 			jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder,
-													 jsexpr->returning);
+													 jsexpr->returning,
+													 true);
 		}
 	}
 	else
@@ -4318,13 +4327,15 @@ initJsonItemCoercion(ParseState *pstate, Oid typid,
 		expr = (Node *) placeholder;
 	}
 
-	return coerceJsonExpr(pstate, expr, returning);
+	/* Allow only ordinary casts here */
+	return coerceJsonExpr(pstate, expr, returning, false);
 }
 
-static void
-initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,
-					  const JsonReturning *returning, Oid contextItemTypeId)
+static JsonItemCoercions *
+initJsonItemCoercions(ParseState *pstate, const JsonReturning *returning,
+					  Oid contextItemTypeId)
 {
+	JsonItemCoercions *coercions = makeNode(JsonItemCoercions);
 	struct
 	{
 		JsonCoercion **coercion;
@@ -4347,6 +4358,8 @@ initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,
 
 	for (p = coercionTypids; p->coercion; p++)
 		*p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
+
+	return coercions;
 }
 
 /*
@@ -4377,9 +4390,12 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
 				coerceDefaultJsonExpr(pstate, jsexpr,
 									  jsexpr->on_error->default_expr);
 
-			jsexpr->coercions = makeNode(JsonItemCoercions);
-			initJsonItemCoercions(pstate, jsexpr->coercions, jsexpr->returning,
-								  exprType(contextItemExpr));
+			if (jsexpr->returning->typid != JSONOID &&
+				jsexpr->returning->typid != JSONBOID)
+				jsexpr->coercions =
+					initJsonItemCoercions(pstate, jsexpr->returning,
+										  exprType(contextItemExpr));
+
 
 			break;
 
@@ -4409,6 +4425,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
 			jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT;
 			jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
 
+			/* Coerce intermediate boolean result to the output type if needed */
 			if (!OidIsValid(jsexpr->returning->typid))
 			{
 				jsexpr->returning->typid = BOOLOID;
@@ -4418,21 +4435,22 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
 			{
 				CaseTestExpr *placeholder = makeNode(CaseTestExpr);
 				int			location = exprLocation((Node *) jsexpr);
+				Node	   *coercion_expr;
 
 				placeholder->typeId = BOOLOID;
 				placeholder->typeMod = -1;
 				placeholder->collation = InvalidOid;
 
-				jsexpr->result_coercion = makeNode(JsonCoercion);
-				jsexpr->result_coercion->expr =
-					coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID,
+				coercion_expr =
+					coerce_to_target_type(pstate, (Node *) placeholder,
+										  BOOLOID,
 										  jsexpr->returning->typid,
 										  jsexpr->returning->typmod,
 										  COERCION_EXPLICIT,
 										  COERCE_IMPLICIT_CAST,
 										  location);
 
-				if (!jsexpr->result_coercion->expr)
+				if (!coercion_expr)
 					ereport(ERROR,
 							(errcode(ERRCODE_CANNOT_COERCE),
 							 errmsg("cannot cast type %s to %s",
@@ -4440,8 +4458,12 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
 									format_type_be(jsexpr->returning->typid)),
 							 parser_coercion_errposition(pstate, location, (Node *) jsexpr)));
 
-				if (jsexpr->result_coercion->expr == (Node *) placeholder)
-					jsexpr->result_coercion->expr = NULL;
+				if (coercion_expr != (Node *) placeholder)
+				{
+					jsexpr->result_coercion = makeNode(JsonCoercion);
+					jsexpr->result_coercion->expr = coercion_expr;
+					jsexpr->result_coercion->ctype = JSON_COERCION_VIA_EXPR;
+				}
 			}
 			break;
 
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c8ef917ffe0..7dd96b8d5ba 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -757,6 +757,12 @@ typedef struct JsonConstructorExprState
 	int			nargs;
 } JsonConstructorExprState;
 
+typedef struct JsonCoercionState
+{
+	ExprState  *estate; /* coercion expression state, if any */
+	JsonCoercionType type;
+} JsonCoercionState;
+
 /* EEOP_JSONEXPR state, too big to inline */
 typedef struct JsonExprState
 {
@@ -769,10 +775,9 @@ typedef struct JsonExprState
 	}			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 */
+				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 */
@@ -781,15 +786,16 @@ typedef struct JsonExprState
 
 	void	   *cache;			/* cache for json_populate_type() */
 
-	struct JsonCoercionsState
+	/*
+	 * States for coercion of SQL/JSON items produced in JSON_VALUE
+	 * directly to the output type.
+	 */
+	struct JsonItemsCoercionStates
 	{
-		struct JsonCoercionState
-		{
-			JsonCoercion *coercion; /* coercion expression */
-			ExprState  *estate; /* coercion expression state */
-		}			null,
+		JsonCoercionState
+					null,
 					string,
-		numeric    ,
+					numeric,
 					boolean,
 					date,
 					time,
@@ -797,8 +803,19 @@ typedef struct JsonExprState
 					timestamp,
 					timestamptz,
 					composite;
-	}			coercions;		/* states for coercion from SQL/JSON item
-								 * types directly to the output type */
+	}		   *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;
 } JsonExprState;
 
 /* functions in execExpr.c */
@@ -860,14 +877,10 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
 						   ExprContext *econtext, TupleTableSlot *slot);
 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,
-											struct JsonCoercionsState *);
+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,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb6855..0a40d59be26 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1551,6 +1551,15 @@ typedef struct JsonBehavior
 	Node	   *default_expr;	/* default expression, if any */
 } JsonBehavior;
 
+typedef enum JsonCoercionType
+{
+	JSON_COERCION_NONE = 0,			/* coercion is not needed */
+	JSON_COERCION_VIA_EXPR = 1,		/* coerce using expression */
+	JSON_COERCION_VIA_IO = 2,		/* coerce using type input function */
+	JSON_COERCION_VIA_POPULATE = 3,	/* coerce using json_populate_type() */
+	JSON_COERCION_ERROR = 4			/* no noercion is available */
+} JsonCoercionType;
+
 /*
  * JsonCoercion -
  *		coercion from SQL/JSON item types to SQL types
@@ -1558,10 +1567,11 @@ typedef struct JsonBehavior
 typedef struct JsonCoercion
 {
 	NodeTag		type;
-	Node	   *expr;			/* resulting expression coerced to target type */
-	bool		via_populate;	/* coerce result using json_populate_type()? */
-	bool		via_io;			/* coerce result using type input function? */
-	Oid			collation;		/* collation for coercion via I/O or populate */
+	JsonCoercionType ctype; /* coercion type */
+	Node	   *expr;		/* resulting expression coerced to the
+							 * target if type == JSON_COERCION_VIA_EXPR,
+							 * NULL otherwise */
+	Oid			collation;	/* collation for coercion via I/O or populate */
 } JsonCoercion;
 
 /*
-- 
2.25.1

