From 04d86031ce4ce88ee600729be6a3e191ba69f971 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 30 Oct 2026 09:36:03 +0800
Subject: [PATCH v34 3/3] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Introduce SQL-standard safe type cast syntax:
  CAST(expr AS newtype DEFAULT expr ON CONVERSION ERROR)

This allow users to specify a default fallback expression if a type conversion
fails, rather than raising an error.

With this patchset, almost all of the cast functions in pg_cast.castfunc are now
error-safe.  CoerceViaIO and CoerceToDomain were already error-safe in the HEAD,
see [0], this patch extends error-safe behavior to ArrayCoerceExpr.

# Bumps catversion required

[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b

Related:
https://postgr.es/m/4c602ca2-2c1a-420c-bbf2-906d65c93e77%40postgresfriends.org

Author: jian he <jian.universality@gmail.com>
Reviewed-by: Amul Sul <sulamul@gmail.com>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Reviewed-by: Corey Huinker <corey.huinker@gmail.com>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com>
Reviewed-by: Henson Choi <assam258@gmail.com>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 contrib/citext/expected/citext.out        |    3 +
 contrib/citext/expected/citext_1.out      |    3 +
 contrib/citext/sql/citext.sql             |    2 +
 doc/src/sgml/syntax.sgml                  |   39 +
 src/backend/executor/execExpr.c           |  210 +++-
 src/backend/executor/execExprInterp.c     |  182 ++++
 src/backend/jit/llvm/llvmjit_expr.c       |   80 +-
 src/backend/jit/llvm/llvmjit_types.c      |    2 +
 src/backend/nodes/nodeFuncs.c             |   77 ++
 src/backend/optimizer/util/clauses.c      |  202 +++-
 src/backend/parser/gram.y                 |   21 +
 src/backend/parser/parse_coerce.c         |  109 +-
 src/backend/parser/parse_collate.c        |   26 +
 src/backend/parser/parse_expr.c           |  374 ++++++-
 src/backend/parser/parse_target.c         |    2 +-
 src/backend/parser/parse_type.c           |   22 +
 src/backend/parser/parse_utilcmd.c        |    3 +-
 src/backend/utils/adt/arrayfuncs.c        |    9 +
 src/backend/utils/adt/ruleutils.c         |   28 +
 src/backend/utils/fmgr/README             |   15 +-
 src/include/executor/execExpr.h           |   15 +
 src/include/executor/executor.h           |    1 +
 src/include/nodes/execnodes.h             |   23 +
 src/include/nodes/parsenodes.h            |    1 +
 src/include/nodes/primnodes.h             |   71 ++
 src/include/optimizer/optimizer.h         |    2 +-
 src/include/parser/parse_coerce.h         |    3 +-
 src/include/parser/parse_node.h           |    6 +
 src/include/parser/parse_type.h           |    2 +
 src/test/regress/expected/cast.out        | 1194 +++++++++++++++++++++
 src/test/regress/expected/create_cast.out |   10 +
 src/test/regress/expected/equivclass.out  |   19 +
 src/test/regress/expected/xml.out         |    6 +
 src/test/regress/parallel_schedule        |    2 +-
 src/test/regress/sql/cast.sql             |  461 ++++++++
 src/test/regress/sql/create_cast.sql      |    3 +
 src/test/regress/sql/equivclass.sql       |    5 +
 src/test/regress/sql/xml.sql              |    2 +
 src/tools/pgindent/typedefs.list          |    3 +
 39 files changed, 3180 insertions(+), 58 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 8c0bf54f0f3..e9189ef3a5a 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,6 +10,9 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
 --------+---------
 (0 rows)
 
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR:  cannot cast type character to citext with DEFAULT expression in CAST ... ON CONVERSION ERROR
+DETAIL:  Explicit cast is defined but definition is not error safe.
 -- Test the operators and indexing functions
 -- Test = and <>.
 SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index c5e5f180f2b..4cbaf5359d8 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,6 +10,9 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
 --------+---------
 (0 rows)
 
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR:  cannot cast type character to citext with DEFAULT expression in CAST ... ON CONVERSION ERROR
+DETAIL:  Explicit cast is defined but definition is not error safe.
 -- Test the operators and indexing functions
 -- Test = and <>.
 SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index aa1cf9abd5c..c820b0bd4d9 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,6 +9,8 @@ SELECT amname, opcname
 FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
 WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
 
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error
+
 -- Test the operators and indexing functions
 
 -- Test = and <>.
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index a697218c8b3..bb42ab30a49 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2133,6 +2133,45 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
      <xref linkend="sql-createcast"/>.
     </para>
    </note>
+
+   <sect3 id="sql-syntax-type-casts-safe">
+    <title>Safe Type Casts</title>
+
+    <para>
+     A type cast will fail if the input value cannot be resolved to a value of
+     the destination type. To guard against such failures, you can provide an
+     <literal>ON CONVERSION ERROR</literal> clause to control what happens when
+     a cast fails. The available forms are:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
+
+     When <literal>DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR</literal>
+     is specified, if the first <replaceable>expression</replaceable> cannot be
+     cast to the destination type, then the specified
+     <literal>DEFAULT</literal><replaceable>expression</replaceable>
+     is evaluated and cast to the destination type, which itself will raise an
+     error if the value cannot be cast to the destination type.
+    </para>
+
+    <para>
+     Casts created with <link linkend="sql-createcast"><command>CREATE CAST</command></link>
+     are also supported, provided that the underlying cast function is marked <literal>ERROR SAFE</literal>;
+     see <link linkend="sql-createfunction"><command>CREATE FUNCTION</command></link>.
+    </para>
+
+    <para>
+     Some examples:
+<screen>
+SELECT CAST('not-a-date' AS date DEFAULT '1970-01-01' ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1970-01-01</computeroutput>
+SELECT CAST(ARRAY['x'] AS integer[] DEFAULT NULL ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>NULL</computeroutput>
+</screen>
+    </para>
+
+   </sect3>
   </sect2>
 
   <sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 35cec43fb3d..dc3f1c87cd2 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -99,6 +99,12 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
 static void ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state,
 							 Datum *resv, bool *resnull,
 							 ExprEvalStep *scratch);
+static void ExecInitSafeCastElement(SafeCastElement *elem, ExprState *state,
+									Datum *resv, bool *resnull,
+									ExprEvalStep *scratch);
+static void ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state,
+									 Datum *resv, bool *resnull,
+									 ExprEvalStep *scratch);
 static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning,
 								 ErrorSaveContext *escontext, bool omit_quotes,
 								 bool exists_coerce,
@@ -141,6 +147,26 @@ static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning,
  */
 ExprState *
 ExecInitExpr(Expr *node, PlanState *parent)
+{
+	return ExecInitExprWithContext(node, parent, NULL);
+}
+
+/*
+ * ExecInitExprWithContext: same as ExecInitExpr, but with an optional
+ * ErrorSaveContext for soft error handling.
+ *
+ * When 'escontext' is non-NULL, expression nodes that support soft errors
+ * (currently CoerceToDomain's NOT NULL and CHECK constraint steps, and function
+ * calls, including operator will use errsave() instead of ereport(), allowing
+ * the caller to detect and handle failures without a transaction abort.
+ *
+ * The escontext must be provided at initialization time (not after), because
+ * it is copied into per-step data during expression compilation.
+ *
+ * Not all expression node types support soft errors.  If in doubt, pass NULL.
+ */
+ExprState *
+ExecInitExprWithContext(Expr *node, PlanState *parent, Node *escontext)
 {
 	ExprState  *state;
 	ExprEvalStep scratch = {0};
@@ -154,6 +180,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
 	state->expr = node;
 	state->parent = parent;
 	state->ext_params = NULL;
+	state->escontext = (ErrorSaveContext *) escontext;
 
 	/* Insert setup steps as needed */
 	ExecCreateExprSetupSteps(state, (Node *) node);
@@ -1701,6 +1728,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				elemstate->innermost_caseval = palloc_object(Datum);
 				elemstate->innermost_casenull = palloc_object(bool);
+				elemstate->escontext = state->escontext;
 
 				ExecInitExprRec(acoerce->elemexpr, elemstate,
 								&elemstate->resvalue, &elemstate->resnull);
@@ -1921,6 +1949,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				int			nelems = list_length(arrayexpr->elements);
 				ListCell   *lc;
 				int			elemoff;
+				List	   *skipjumps = NIL;
 
 				/*
 				 * Evaluate by computing each element, and then forming the
@@ -1953,11 +1982,32 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					ExecInitExprRec(e, state,
 									&scratch.d.arrayexpr.elemvalues[elemoff],
 									&scratch.d.arrayexpr.elemnulls[elemoff]);
+
+					/*
+					 * If this ArrayExpr is being evaluated softly has already
+					 * failed, the result is going to be discarded, so don't
+					 * check the remaining against it; doing so might throw a
+					 * hard error or overwrite the saved error.
+					 */
+					if (state->escontext != NULL)
+					{
+						ExprEvalStep jump = {0};
+
+						jump.opcode = EEOP_JUMP_IF_NULL;
+						jump.resnull = &state->escontext->error_occurred;
+						jump.d.jump.jumpdone = -1;	/* set below */
+						skipjumps = lappend_int(skipjumps, state->steps_len);
+						ExprEvalPushStep(state, &jump);
+					}
 					elemoff++;
 				}
 
 				/* and then collect all into an array */
 				ExprEvalPushStep(state, &scratch);
+
+				foreach_int(jumpno, skipjumps)
+					state->steps[jumpno].d.jump.jumpdone = state->steps_len;
+
 				break;
 			}
 
@@ -2176,6 +2226,26 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				ExecInitSafeTypeCastExpr(stcexpr, state, resv, resnull,
+										 &scratch);
+
+				break;
+			}
+
+		case T_SafeCastElement:
+			{
+				SafeCastElement *elem = castNode(SafeCastElement, node);
+
+				ExecInitSafeCastElement(elem, state, resv, resnull,
+										&scratch);
+
+				break;
+			}
+
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesce = (CoalesceExpr *) node;
@@ -2702,6 +2772,8 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 	FunctionCallInfo fcinfo;
 	int			argno;
 	ListCell   *lc;
+	bool		strict;
+	bool		fusage;
 
 	/* Check permission to call function */
 	aclresult = object_aclcheck(ProcedureRelationId, funcid, GetUserId(), ACL_EXECUTE);
@@ -2735,7 +2807,8 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 
 	/* Initialize function call parameter structure too */
 	InitFunctionCallInfoData(*fcinfo, flinfo,
-							 nargs, inputcollid, NULL, NULL);
+							 nargs, inputcollid,
+							 (Node *) state->escontext, NULL);
 
 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -2776,10 +2849,35 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 		argno++;
 	}
 
-	/* Insert appropriate opcode depending on strictness and stats level */
-	if (pgstat_track_functions <= flinfo->fn_stats)
+	strict = (flinfo->fn_strict && nargs > 0);
+	fusage = (pgstat_track_functions > flinfo->fn_stats);
+
+	/*
+	 * Insert appropriate opcode depending on strictness and stats level.
+	 * Under an ErrorSaveContext, use the _SAFE variants, which pass the
+	 * context to the function and return NULL when it reports an error
+	 * softly.
+	 */
+	if (state->escontext != NULL)
 	{
-		if (flinfo->fn_strict && nargs > 0)
+		if (!fusage)
+		{
+			if (strict)
+				scratch->opcode = EEOP_FUNCEXPR_STRICT_SAFE;
+			else
+				scratch->opcode = EEOP_FUNCEXPR_SAFE;
+		}
+		else
+		{
+			if (strict)
+				scratch->opcode = EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE;
+			else
+				scratch->opcode = EEOP_FUNCEXPR_SAFE_FUSAGE;
+		}
+	}
+	else if (!fusage)
+	{
+		if (strict)
 		{
 			/* Choose nargs optimized implementation if available. */
 			if (nargs == 1)
@@ -2794,7 +2892,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 	}
 	else
 	{
-		if (flinfo->fn_strict && nargs > 0)
+		if (strict)
 			scratch->opcode = EEOP_FUNCEXPR_STRICT_FUSAGE;
 		else
 			scratch->opcode = EEOP_FUNCEXPR_FUSAGE;
@@ -4759,6 +4857,108 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state,
+						 Datum *resv, bool *resnull,
+						 ExprEvalStep *scratch)
+{
+	SafeTypeCastState *stcstate;
+	ErrorSaveContext *saved_escontext = state->escontext;
+	Datum	   *saved_caseval = state->innermost_caseval;
+	bool	   *saved_casenull = state->innermost_casenull;
+
+	stcstate = palloc0_object(SafeTypeCastState);
+	stcstate->stcexpr = stcexpr;
+	stcstate->escontext.type = T_ErrorSaveContext;
+	stcstate->escontext.error_occurred = false;
+	stcstate->escontext.details_wanted = false;
+	stcstate->escontext.error_data = NULL;
+
+	/*
+	 * arg normally throws its own errors, so it is compiled under the
+	 * caller's context.  If arg_errorsafe, it holds SafeCastElement nodes
+	 * converting its elements; it is then compiled under our context, which
+	 * makes those conversions error-safe.  The element expressions themselves
+	 * still throw their errors.
+	 */
+	if (stcexpr->arg_errorsafe)
+		state->escontext = &stcstate->escontext;
+	ExecInitExprRec(stcexpr->arg, state,
+					&stcstate->args.value, &stcstate->args.isnull);
+
+	/*
+	 * No coercion path exists at all (castexpr is NULL), so the result is
+	 * always the DEFAULT expression.  The argument above is still evaluated,
+	 * so that its own errors are thrown, but its value is unused.
+	 */
+	if (stcexpr->castexpr == NULL)
+	{
+		state->escontext = saved_escontext;
+		ExecInitExprRec(stcexpr->defexpr, state, resv, resnull);
+		return;
+	}
+
+	state->escontext = &stcstate->escontext;
+
+	/* The conversion itself is always evaluated error-safe */
+	state->innermost_caseval = &stcstate->args.value;
+	state->innermost_casenull = &stcstate->args.isnull;
+	ExecInitExprRec(stcexpr->castexpr, state, resv, resnull);
+	scratch->d.stcexpr.stcstate = stcstate;
+	scratch->opcode = EEOP_SAFETYPE_CAST;
+	ExprEvalPushStep(state, scratch);
+
+	state->innermost_caseval = saved_caseval;
+	state->innermost_casenull = saved_casenull;
+	state->escontext = saved_escontext;
+	/* DEFAULT, under the caller's context */
+	ExecInitExprRec(stcexpr->defexpr, state, resv, resnull);
+	stcstate->jump_end = state->steps_len;
+}
+
+/*
+ * Push steps to evaluate a SafeCastElement.
+ *
+ * In CAST(ARRAY[...] AS type DEFAULT ... ON CONVERSION ERROR), each element
+ * expression must be evaluated in a non-error-safe way, while its conversion to
+ * the target element type must be error-safe.
+ */
+static void
+ExecInitSafeCastElement(SafeCastElement *elem, ExprState *state,
+						Datum *resv, bool *resnull,
+						ExprEvalStep *scratch)
+{
+	ErrorSaveContext *saved_escontext = state->escontext;
+	Datum	   *saved_caseval = state->innermost_caseval;
+	bool	   *saved_casenull = state->innermost_casenull;
+	NullableDatum *argval = palloc_object(NullableDatum);
+
+	state->escontext = NULL;
+	ExecInitExprRec(elem->arg, state, &argval->value, &argval->isnull);
+	state->escontext = saved_escontext;
+
+	if (elem->castexpr == NULL)
+	{
+		scratch->opcode = EEOP_CONST;
+		scratch->resvalue = resv;
+		scratch->resnull = resnull;
+		scratch->d.constval.value = (Datum) 0;
+		scratch->d.constval.isnull = true;
+		ExprEvalPushStep(state, scratch);
+		return;
+	}
+
+	state->innermost_caseval = &argval->value;
+	state->innermost_casenull = &argval->isnull;
+	ExecInitExprRec(elem->castexpr, state, resv, resnull);
+	state->innermost_caseval = saved_caseval;
+	state->innermost_casenull = saved_casenull;
+}
+
 /*
  * Push steps to evaluate a JsonExpr and its various subsidiary expressions.
  */
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index d0d70be13e1..e2275d5194c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -514,6 +514,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_FUNCEXPR_STRICT_2,
 		&&CASE_EEOP_FUNCEXPR_FUSAGE,
 		&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEOP_FUNCEXPR_SAFE,
+		&&CASE_EEOP_FUNCEXPR_STRICT_SAFE,
+		&&CASE_EEOP_FUNCEXPR_SAFE_FUSAGE,
+		&&CASE_EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE,
 		&&CASE_EEOP_BOOL_AND_STEP_FIRST,
 		&&CASE_EEOP_BOOL_AND_STEP,
 		&&CASE_EEOP_BOOL_AND_STEP_LAST,
@@ -578,6 +582,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_JSON_CONSTRUCTOR,
 		&&CASE_EEOP_IS_JSON,
+		&&CASE_EEOP_SAFETYPE_CAST,
 		&&CASE_EEOP_JSONEXPR_PATH,
 		&&CASE_EEOP_JSONEXPR_COERCION,
 		&&CASE_EEOP_JSONEXPR_COERCION_FINISH,
@@ -1032,6 +1037,77 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_FUNCEXPR_SAFE)
+		{
+			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+			Datum		d;
+
+			fcinfo->isnull = false;
+			d = op->d.func.fn_addr(fcinfo);
+			*op->resvalue = d;
+			*op->resnull = fcinfo->isnull;
+
+			/*
+			 * A function that reported an error softly returned a dummy
+			 * datum.
+			 */
+			if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			{
+				*op->resnull = true;
+				*op->resvalue = (Datum) 0;
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_FUNCEXPR_STRICT_SAFE)
+		{
+			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+			NullableDatum *args = fcinfo->args;
+			Datum		d;
+
+			for (int argno = 0; argno < op->d.func.nargs; argno++)
+			{
+				if (args[argno].isnull)
+				{
+					*op->resnull = true;
+					*op->resvalue = (Datum) 0;
+					goto strictsafe_fail;
+				}
+			}
+
+			fcinfo->isnull = false;
+			d = op->d.func.fn_addr(fcinfo);
+			*op->resvalue = d;
+			*op->resnull = fcinfo->isnull;
+
+			/*
+			 * A function that reported an error softly returned a dummy
+			 * datum.
+			 */
+			if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			{
+				*op->resnull = true;
+				*op->resvalue = (Datum) 0;
+			}
+	strictsafe_fail:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_FUNCEXPR_SAFE_FUSAGE)
+		{
+			ExecEvalFuncSafeFusage(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE)
+		{
+			ExecEvalFuncStrictSafeFusage(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		/*
 		 * If any of its clauses is FALSE, an AND's result is FALSE regardless
 		 * of the states of the rest of the clauses, so we can stop evaluating
@@ -1936,6 +2012,27 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SAFETYPE_CAST)
+		{
+			SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+			if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+			{
+				/*
+				 * The conversion failed softly, so fall through to evaluate
+				 * the DEFAULT expression.  Clear the error first, so that the
+				 * next evaluation starts afresh.
+				 */
+				stcstate->escontext.error_occurred = false;
+				*op->resnull = true;
+				*op->resvalue = (Datum) 0;
+
+				EEO_NEXT();
+			}
+			else
+				EEO_JUMP(stcstate->jump_end);
+		}
+
 		EEO_CASE(EEOP_JSONEXPR_PATH)
 		{
 			/* too complex for an inline implementation */
@@ -3014,6 +3111,36 @@ ExecEvalFuncExprFusage(ExprState *state, ExprEvalStep *op,
 	pgstat_end_function_usage(&fcusage, true);
 }
 
+/*
+ * Evaluate EEOP_FUNCEXPR_SAFE_FUSAGE
+ */
+void
+ExecEvalFuncSafeFusage(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+	PgStat_FunctionCallUsage fcusage;
+	Datum		d;
+
+	pgstat_init_function_usage(fcinfo, &fcusage);
+
+	fcinfo->isnull = false;
+	d = op->d.func.fn_addr(fcinfo);
+	*op->resvalue = d;
+	*op->resnull = fcinfo->isnull;
+
+	/*
+	 * A function that reported an error softly returned a dummy datum. Hand a
+	 * NULL to whatever consumes the result instead.
+	 */
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+	{
+		*op->resnull = true;
+		*op->resvalue = (Datum) 0;
+	}
+
+	pgstat_end_function_usage(&fcusage, true);
+}
+
 /*
  * Evaluate EEOP_FUNCEXPR_STRICT_FUSAGE
  */
@@ -3048,6 +3175,50 @@ ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op,
 	pgstat_end_function_usage(&fcusage, true);
 }
 
+/*
+ * Evaluate EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE
+ */
+void
+ExecEvalFuncStrictSafeFusage(ExprState *state, ExprEvalStep *op,
+							 ExprContext *econtext)
+{
+	FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+	PgStat_FunctionCallUsage fcusage;
+	NullableDatum *args = fcinfo->args;
+	int			nargs = op->d.func.nargs;
+	Datum		d;
+
+	/* strict function, so check for NULL args */
+	for (int argno = 0; argno < nargs; argno++)
+	{
+		if (args[argno].isnull)
+		{
+			*op->resnull = true;
+			*op->resvalue = (Datum) 0;
+			return;
+		}
+	}
+
+	pgstat_init_function_usage(fcinfo, &fcusage);
+
+	fcinfo->isnull = false;
+	d = op->d.func.fn_addr(fcinfo);
+	*op->resvalue = d;
+	*op->resnull = fcinfo->isnull;
+
+	/*
+	 * A function that reported an error softly returned a dummy datum. Hand a
+	 * NULL to whatever consumes the result instead.
+	 */
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+	{
+		*op->resnull = true;
+		*op->resvalue = (Datum) 0;
+	}
+
+	pgstat_end_function_usage(&fcusage, true);
+}
+
 /*
  * Evaluate a PARAM_EXEC parameter.
  *
@@ -3654,6 +3825,17 @@ ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 							  econtext,
 							  op->d.arraycoerce.resultelemtype,
 							  op->d.arraycoerce.amstate);
+
+	if (SOFT_ERROR_OCCURRED(op->d.arraycoerce.elemexprstate->escontext))
+	{
+		*op->resvalue = (Datum) 0;
+		*op->resnull = true;
+
+		/*
+		 * A soft error occurred. The caller may need to reset
+		 * ExprState.ErrorSaveContext.error_occurred for the next evaluation.
+		 */
+	}
 }
 
 /*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index fc80d3e55fe..fd607b878dd 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -665,6 +665,8 @@ llvm_compile_expr(ExprState *state)
 			case EEOP_FUNCEXPR_STRICT:
 			case EEOP_FUNCEXPR_STRICT_1:
 			case EEOP_FUNCEXPR_STRICT_2:
+			case EEOP_FUNCEXPR_SAFE:
+			case EEOP_FUNCEXPR_STRICT_SAFE:
 				{
 					FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
 					LLVMValueRef v_fcinfo_isnull;
@@ -672,7 +674,8 @@ llvm_compile_expr(ExprState *state)
 
 					if (opcode == EEOP_FUNCEXPR_STRICT ||
 						opcode == EEOP_FUNCEXPR_STRICT_1 ||
-						opcode == EEOP_FUNCEXPR_STRICT_2)
+						opcode == EEOP_FUNCEXPR_STRICT_2 ||
+						opcode == EEOP_FUNCEXPR_STRICT_SAFE)
 					{
 						LLVMBasicBlockRef b_nonull;
 						LLVMBasicBlockRef *b_checkargnulls;
@@ -741,6 +744,34 @@ llvm_compile_expr(ExprState *state)
 
 					v_retval = BuildV1Call(context, b, mod, fcinfo,
 										   &v_fcinfo_isnull);
+
+					/*
+					 * A function that reported an error softly returned a
+					 * dummy datum; return NULL instead.
+					 */
+					if (opcode == EEOP_FUNCEXPR_SAFE ||
+						opcode == EEOP_FUNCEXPR_STRICT_SAFE)
+					{
+						ErrorSaveContext *escontext = (ErrorSaveContext *) fcinfo->context;
+						LLVMValueRef v_error_occurred;
+						LLVMValueRef v_error;
+
+						Assert(escontext != NULL && IsA(escontext, ErrorSaveContext));
+
+						v_error_occurred =
+							l_load(b, TypeStorageBool,
+								   l_ptr_const(&escontext->error_occurred,
+											   l_ptr(TypeStorageBool)),
+								   "");
+						v_error = LLVMBuildICmp(b, LLVMIntNE, v_error_occurred,
+												l_sbool_const(0), "");
+						v_retval = LLVMBuildSelect(b, v_error, l_datum_const(0),
+												   v_retval, "");
+						v_fcinfo_isnull = LLVMBuildSelect(b, v_error,
+														  l_sbool_const(1),
+														  v_fcinfo_isnull, "");
+					}
+
 					LLVMBuildStore(b, v_retval, v_resvaluep);
 					LLVMBuildStore(b, v_fcinfo_isnull, v_resnullp);
 
@@ -761,6 +792,18 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
+			case EEOP_FUNCEXPR_SAFE_FUSAGE:
+				build_EvalXFunc(b, mod, "ExecEvalFuncSafeFusage",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
+			case EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE:
+				build_EvalXFunc(b, mod, "ExecEvalFuncStrictSafeFusage",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
 				/*
 				 * Treat them the same for now, optimizer can remove
 				 * redundancy. Could be worthwhile to optimize during emission
@@ -2257,6 +2300,41 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
+			case EEOP_SAFETYPE_CAST:
+				{
+					SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+					LLVMBasicBlockRef b_error;
+					LLVMValueRef v_error_occurred_p;
+					LLVMValueRef v_error_occurred;
+
+					b_error = l_bb_before_v(opblocks[opno + 1],
+											"op.%d.error", opno);
+
+					v_error_occurred_p = l_ptr_const(&stcstate->escontext.error_occurred,
+													 l_ptr(TypeStorageBool));
+					v_error_occurred = l_load(b, TypeStorageBool,
+											  v_error_occurred_p, "");
+
+					/* no error: skip the DEFAULT expression */
+					LLVMBuildCondBr(b,
+									LLVMBuildICmp(b, LLVMIntEQ, v_error_occurred,
+												  l_sbool_const(0), ""),
+									opblocks[stcstate->jump_end],
+									b_error);
+
+					/*
+					 * The conversion failed softly: clear the error so that
+					 * the next evaluation starts afresh, and fall through to
+					 * the DEFAULT expression.
+					 */
+					LLVMPositionBuilderAtEnd(b, b_error);
+					LLVMBuildStore(b, l_sbool_const(0), v_error_occurred_p);
+					LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+					LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+					LLVMBuildBr(b, opblocks[opno + 1]);
+					break;
+				}
+
 			case EEOP_JSONEXPR_PATH:
 				{
 					JsonExprState *jsestate = op->d.jsonexpr.jsestate;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index c8a1f841293..aa850ad89f3 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -153,6 +153,8 @@ void	   *referenced_functions[] =
 	ExecEvalFieldStoreForm,
 	ExecEvalFuncExprFusage,
 	ExecEvalFuncExprStrictFusage,
+	ExecEvalFuncSafeFusage,
+	ExecEvalFuncStrictSafeFusage,
 	ExecEvalGroupingFunc,
 	ExecEvalMergeSupportFunc,
 	ExecEvalMinMax,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 678b9a046ce..49ff4319b7d 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -206,6 +206,12 @@ exprType(const Node *expr)
 		case T_RowCompareExpr:
 			type = BOOLOID;
 			break;
+		case T_SafeTypeCastExpr:
+			type = ((const SafeTypeCastExpr *) expr)->resulttype;
+			break;
+		case T_SafeCastElement:
+			type = ((const SafeCastElement *) expr)->resulttype;
+			break;
 		case T_CoalesceExpr:
 			type = ((const CoalesceExpr *) expr)->coalescetype;
 			break;
@@ -450,6 +456,10 @@ exprTypmod(const Node *expr)
 				return typmod;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			return ((const SafeTypeCastExpr *) expr)->resulttypmod;
+		case T_SafeCastElement:
+			return ((const SafeCastElement *) expr)->resulttypmod;
 		case T_CoalesceExpr:
 			{
 				/*
@@ -965,6 +975,12 @@ exprCollation(const Node *expr)
 			/* RowCompareExpr's result is boolean ... */
 			coll = InvalidOid;	/* ... so it has no collation */
 			break;
+		case T_SafeTypeCastExpr:
+			coll = ((const SafeTypeCastExpr *) expr)->resultcollid;
+			break;
+		case T_SafeCastElement:
+			coll = ((const SafeCastElement *) expr)->resultcollid;
+			break;
 		case T_CoalesceExpr:
 			coll = ((const CoalesceExpr *) expr)->coalescecollid;
 			break;
@@ -1240,6 +1256,12 @@ exprSetCollation(Node *expr, Oid collation)
 			/* RowCompareExpr's result is boolean ... */
 			Assert(!OidIsValid(collation)); /* ... so never set a collation */
 			break;
+		case T_SafeTypeCastExpr:
+			((SafeTypeCastExpr *) expr)->resultcollid = collation;
+			break;
+		case T_SafeCastElement:
+			((SafeCastElement *) expr)->resultcollid = collation;
+			break;
 		case T_CoalesceExpr:
 			((CoalesceExpr *) expr)->coalescecollid = collation;
 			break;
@@ -1561,6 +1583,12 @@ exprLocation(const Node *expr)
 			/* just use leftmost argument's location */
 			loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs);
 			break;
+		case T_SafeTypeCastExpr:
+			loc = ((const SafeTypeCastExpr *) expr)->location;
+			break;
+		case T_SafeCastElement:
+			loc = ((const SafeCastElement *) expr)->location;
+			break;
 		case T_CoalesceExpr:
 			/* COALESCE keyword should always be the first thing */
 			loc = ((const CoalesceExpr *) expr)->location;
@@ -2336,6 +2364,28 @@ expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				if (WALK(stcexpr->arg))
+					return true;
+				if (WALK(stcexpr->castexpr))
+					return true;
+				if (WALK(stcexpr->defexpr))
+					return true;
+			}
+			break;
+		case T_SafeCastElement:
+			{
+				SafeCastElement *elem = castNode(SafeCastElement, node);
+
+				if (WALK(elem->arg))
+					return true;
+				if (WALK(elem->castexpr))
+					return true;
+			}
+			break;
 		case T_CoalesceExpr:
 			return WALK(((CoalesceExpr *) node)->args);
 		case T_MinMaxExpr:
@@ -3348,6 +3398,31 @@ expression_tree_mutator_impl(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+				SafeTypeCastExpr *newnode;
+
+				FLATCOPY(newnode, stcexpr, SafeTypeCastExpr);
+				MUTATE(newnode->arg, stcexpr->arg, Expr *);
+				MUTATE(newnode->castexpr, stcexpr->castexpr, Expr *);
+				MUTATE(newnode->defexpr, stcexpr->defexpr, Expr *);
+
+				return (Node *) newnode;
+			}
+			break;
+		case T_SafeCastElement:
+			{
+				SafeCastElement *elem = castNode(SafeCastElement, node);
+				SafeCastElement *newnode;
+
+				FLATCOPY(newnode, elem, SafeCastElement);
+				MUTATE(newnode->arg, elem->arg, Expr *);
+				MUTATE(newnode->castexpr, elem->castexpr, Expr *);
+
+				return (Node *) newnode;
+			}
+			break;
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
@@ -4485,6 +4560,8 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 				if (WALK(tc->typeName))
 					return true;
+				if (WALK(tc->defexpr))
+					return true;
 			}
 			break;
 		case T_CollateClause:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 95134bfeda3..9f365af40d6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -71,6 +71,7 @@ typedef struct
 	List	   *active_fns;
 	Node	   *case_val;
 	bool		estimate;
+	ErrorSaveContext *escontext;
 } eval_const_expressions_context;
 
 typedef struct
@@ -1197,6 +1198,8 @@ contain_nonstrict_functions_walker(Node *node, void *context)
 		return true;
 	else if (IsA(node, JsonConstructorExpr))
 		return true;
+	else if (IsA(node, SafeTypeCastExpr))
+		return true;
 	else
 	{
 		/* Check other function-containing nodes */
@@ -1349,6 +1352,41 @@ contain_context_dependent_node_walker(Node *node, int *flags)
 		*flags = save_flags;
 		return res;
 	}
+	else if (IsA(node, SafeTypeCastExpr))
+	{
+		SafeTypeCastExpr *stc = (SafeTypeCastExpr *) node;
+		int			save_flags;
+		bool		res;
+
+		if (contain_context_dependent_node_walker((Node *) stc->arg, flags) ||
+			contain_context_dependent_node_walker((Node *) stc->defexpr, flags))
+			return true;
+
+		/* castexpr is built over its own CaseTestExpr placeholder  */
+		save_flags = *flags;
+		*flags |= CCDN_CASETESTEXPR_OK;
+		res = contain_context_dependent_node_walker((Node *) stc->castexpr,
+													flags);
+		*flags = save_flags;
+		return res;
+	}
+	else if (IsA(node, SafeCastElement))
+	{
+		SafeCastElement *elem = (SafeCastElement *) node;
+		int			save_flags;
+		bool		res;
+
+		if (contain_context_dependent_node_walker((Node *) elem->arg, flags))
+			return true;
+
+		/* castexpr is built over its own CaseTestExpr placeholder  */
+		save_flags = *flags;
+		*flags |= CCDN_CASETESTEXPR_OK;
+		res = contain_context_dependent_node_walker((Node *) elem->castexpr,
+													flags);
+		*flags = save_flags;
+		return res;
+	}
 	return expression_tree_walker(node, contain_context_dependent_node_walker,
 								  flags);
 }
@@ -2632,6 +2670,12 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum,
  * NOTE: another critical effect is that any function calls that require
  * default arguments will be expanded, and named-argument calls will be
  * converted to positional notation.  The executor won't handle either.
+ *
+ * NOTE: eval_const_expressions_context->escontext, when set, makes the
+ * evaluation of constant subexpressions report errors softly instead of
+ * throwing them.  Currently only the SafeTypeCastExpr case sets it, but
+ * any other node type wanting the same behavior can do so.
+ *
  *--------------------
  */
 Node *
@@ -2647,6 +2691,7 @@ eval_const_expressions(PlannerInfo *root, Node *node)
 	context.active_fns = NIL;	/* nothing being recursively simplified */
 	context.case_val = NULL;	/* no CASE being examined */
 	context.estimate = false;	/* safe transformations only */
+	context.escontext = NULL;	/* for error-safe expression evaluation */
 	return eval_const_expressions_mutator(node, &context);
 }
 
@@ -2789,6 +2834,7 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 	context.active_fns = NIL;	/* nothing being recursively simplified */
 	context.case_val = NULL;	/* no CASE being examined */
 	context.estimate = true;	/* unsafe transformations OK */
+	context.escontext = NULL;	/* for error-safe expression evaluation */
 	return eval_const_expressions_mutator(node, &context);
 }
 
@@ -2814,11 +2860,12 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 	(!expression_tree_walker((Node *) (node), contain_non_const_walker, NULL))
 
 /* Generic macro for applying evaluate_expr */
-#define ece_evaluate_expr(node) \
+#define ece_evaluate_expr(node, escontext) \
 	((Node *) evaluate_expr((Expr *) (node), \
 							exprType((Node *) (node)), \
 							exprTypmod((Node *) (node)), \
-							exprCollation((Node *) (node))))
+							exprCollation((Node *) (node)), \
+							(Node *) escontext))
 
 /*
  * Recursive guts of eval_const_expressions/estimate_expression_value
@@ -3289,7 +3336,7 @@ eval_const_expressions_mutator(Node *node,
 
 				if (!has_nonconst_input &&
 					ece_function_is_safe(expr->opfuncid, context))
-					return ece_evaluate_expr(expr);
+					return ece_evaluate_expr(expr, context->escontext);
 
 				return (Node *) expr;
 			}
@@ -3309,7 +3356,7 @@ eval_const_expressions_mutator(Node *node,
 				 */
 				if (ece_all_arguments_const(saop) &&
 					ece_function_is_safe(saop->opfuncid, context))
-					return ece_evaluate_expr(saop);
+					return ece_evaluate_expr(saop, context->escontext);
 				return (Node *) saop;
 			}
 		case T_BoolExpr:
@@ -3463,6 +3510,113 @@ eval_const_expressions_mutator(Node *node,
 
 				return (Node *) newjce;
 			}
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stc = castNode(SafeTypeCastExpr, node);
+				SafeTypeCastExpr *newexpr;
+				Node	   *arg;
+				Node	   *castexpr = NULL;
+				Node	   *defexpr;
+				ErrorSaveContext *save_escontext = context->escontext;
+				Node	   *save_case_val = context->case_val;
+				ErrorSaveContext *escontext = makeNode(ErrorSaveContext);
+
+				if (stc->castexpr == NULL)
+					return eval_const_expressions_mutator((Node *) stc->defexpr,
+														  context);
+
+				defexpr = eval_const_expressions_mutator((Node *) stc->defexpr,
+														 context);
+
+				/*
+				 * arg is normally evaluated under the caller's context.  If
+				 * arg_errorsafe, it is evaluated under ours, since its
+				 * SafeCastElement nodes report conversion errors into it.
+				 */
+				if (stc->arg_errorsafe)
+					context->escontext = escontext;
+				arg = eval_const_expressions_mutator((Node *) stc->arg, context);
+				context->escontext = save_escontext;
+
+				/*
+				 * A SafeCastElement failed to convert its constant element,
+				 * so the conversion is known to fail: the result is defexpr.
+				 */
+				if (escontext->error_occurred)
+					return defexpr;
+
+				/* only the conversion itself is evaluated error-safe */
+				context->escontext = escontext;
+				context->case_val = IsA(arg, Const) ? arg : NULL;
+				castexpr = eval_const_expressions_mutator((Node *) stc->castexpr,
+														  context);
+				context->case_val = save_case_val;
+				context->escontext = save_escontext;
+
+				/*
+				 * A constant conversion that failed softly would fail the
+				 * same way at runtime.
+				 */
+				if (escontext->error_occurred)
+					return defexpr;
+				if (IsA(castexpr, Const))
+					return castexpr;
+
+				newexpr = makeNode(SafeTypeCastExpr);
+				newexpr->arg = (Expr *) arg;
+				newexpr->arg_errorsafe = stc->arg_errorsafe;
+				newexpr->castexpr = (Expr *) castexpr;
+				newexpr->defexpr = (Expr *) defexpr;
+				newexpr->resulttype = stc->resulttype;
+				newexpr->resulttypmod = stc->resulttypmod;
+				newexpr->resultcollid = stc->resultcollid;
+				newexpr->location = stc->location;
+
+				return (Node *) newexpr;
+			}
+		case T_SafeCastElement:
+			{
+				SafeCastElement *elem = castNode(SafeCastElement, node);
+				SafeCastElement *newelem;
+				ErrorSaveContext *save_escontext = context->escontext;
+				Node	   *save_case_val = context->case_val;
+
+				/*
+				 * Once an element has failed, the enclosing SafeTypeCastExpr
+				 * returns its DEFAULT and discards the array, so the
+				 * remaining elements need not be processed.
+				 */
+				if (SOFT_ERROR_OCCURRED(save_escontext))
+					return node;
+
+				/*
+				 * An element without a conversion makes the whole cast fail
+				 * at parse time, and the SafeTypeCastExpr case then never
+				 * gets here.
+				 */
+				Assert(elem->castexpr != NULL);
+
+				newelem = makeNode(SafeCastElement);
+				memcpy(newelem, elem, sizeof(SafeCastElement));
+
+				/* the element expression itself raises its own errors */
+				context->escontext = NULL;
+				newelem->arg = (Expr *)
+					eval_const_expressions_mutator((Node *) elem->arg, context);
+				context->escontext = save_escontext;
+
+				/* its conversion reports into the enclosing cast's context */
+				context->case_val = IsA(newelem->arg, Const) ?
+					(Node *) newelem->arg : NULL;
+				newelem->castexpr = (Expr *)
+					eval_const_expressions_mutator((Node *) elem->castexpr, context);
+				context->case_val = save_case_val;
+
+				if (IsA(newelem->castexpr, Const))
+					return (Node *) newelem->castexpr;
+
+				return (Node *) newelem;
+			}
 		case T_SubPlan:
 		case T_AlternativeSubPlan:
 
@@ -3614,7 +3768,7 @@ eval_const_expressions_mutator(Node *node,
 				if (ac->arg && IsA(ac->arg, Const) &&
 					ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
 					!contain_mutable_functions((Node *) ac->elemexpr))
-					return ece_evaluate_expr(ac);
+					return ece_evaluate_expr(ac, context->escontext);
 
 				return (Node *) ac;
 			}
@@ -3817,7 +3971,7 @@ eval_const_expressions_mutator(Node *node,
 				node = ece_generic_processing(node);
 				/* If all arguments are Consts, we can fold to a constant */
 				if (ece_all_arguments_const(node))
-					return ece_evaluate_expr(node);
+					return ece_evaluate_expr(node, context->escontext);
 				return node;
 			}
 		case T_CoalesceExpr:
@@ -3901,7 +4055,8 @@ eval_const_expressions_mutator(Node *node,
 					return (Node *) evaluate_expr((Expr *) svf,
 												  svf->type,
 												  svf->typmod,
-												  InvalidOid);
+												  InvalidOid,
+												  NULL);
 				else
 					return copyObject((Node *) svf);
 			}
@@ -3916,7 +4071,7 @@ eval_const_expressions_mutator(Node *node,
 				if ((context->estimate ||
 					 xmlexpr_is_immutable((XmlExpr *) node)) &&
 					ece_all_arguments_const(node))
-					return ece_evaluate_expr(node);
+					return ece_evaluate_expr(node, context->escontext);
 				return node;
 			}
 		case T_FieldSelect:
@@ -4012,7 +4167,7 @@ eval_const_expressions_mutator(Node *node,
 											  newfselect->resulttype,
 											  newfselect->resulttypmod,
 											  newfselect->resultcollid))
-						return ece_evaluate_expr(newfselect);
+						return ece_evaluate_expr(newfselect, context->escontext);
 				}
 				return (Node *) newfselect;
 			}
@@ -4350,7 +4505,7 @@ eval_const_expressions_mutator(Node *node,
 				newcre->arg = (Expr *) arg;
 
 				if (arg != NULL && IsA(arg, Const))
-					return ece_evaluate_expr((Node *) newcre);
+					return ece_evaluate_expr((Node *) newcre, context->escontext);
 				return (Node *) newcre;
 			}
 		default:
@@ -5399,6 +5554,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	bool		has_null_input = false;
 	ListCell   *arg;
 	FuncExpr   *newexpr;
+	Node	   *result;
 
 	/*
 	 * Can't simplify if it returns a set.
@@ -5478,8 +5634,13 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	newexpr->args = args;
 	newexpr->location = -1;
 
-	return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
-						 result_collid);
+	result = (Node *) evaluate_expr((Expr *) newexpr, result_type, result_typmod,
+									result_collid, (Node *) context->escontext);
+
+	if (result == (Node *) newexpr)
+		return NULL;
+
+	return (Expr *) result;
 }
 
 /*
@@ -5933,10 +6094,14 @@ sql_inline_error_callback(void *arg)
  *
  * We use the executor's routine ExecEvalExpr() to avoid duplication of
  * code and ensure we get the same result as the executor would get.
+ *
+ * If escontext is non-NULL, evaluation errors are reported softly.  When one
+ * has occurred, now or in an earlier call under the same context, the
+ * expression is returned unchanged instead of a Const.
  */
 Expr *
 evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
-			  Oid result_collation)
+			  Oid result_collation, Node *escontext)
 {
 	EState	   *estate;
 	ExprState  *exprstate;
@@ -5946,6 +6111,9 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	int16		resultTypLen;
 	bool		resultTypByVal;
 
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return expr;
+
 	/*
 	 * To use the executor, we need an EState.
 	 */
@@ -5961,7 +6129,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	 * Prepare expr for execution.  (Note: we can't use ExecPrepareExpr
 	 * because it'd result in recursively invoking eval_const_expressions.)
 	 */
-	exprstate = ExecInitExpr(expr, NULL);
+	exprstate = ExecInitExprWithContext(expr, NULL, escontext);
 
 	/*
 	 * And evaluate it.
@@ -5981,6 +6149,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);
 
+	if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+	{
+		FreeExecutorState(estate);
+		return expr;
+	}
+
 	/*
 	 * Must copy result out of sub-context used by expression eval.
 	 *
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 24a13ebbafe..37e30d703e2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -157,6 +157,8 @@ static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
 static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+static Node *makeTypeCastOpt(Node *arg, TypeName *typename,
+							 Node *raw_default, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
 static Node *makeFloatConst(char *str, int location);
@@ -16250,6 +16252,10 @@ func_expr_common_subexpr:
 				}
 			| CAST '(' a_expr AS Typename ')'
 				{ $$ = makeTypeCast($3, $5, @1); }
+			| CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+				{ $$ = makeTypeCast($3, $5, @1); }
+			| CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+				{ $$ = makeTypeCastOpt($3, $5, $7, @1); }
 			| EXTRACT '(' extract_list ')'
 				{
 					$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -19179,10 +19185,25 @@ makeTypeCast(Node *arg, TypeName *typename, int location)
 
 	n->arg = arg;
 	n->typeName = typename;
+	n->defexpr = NULL;
 	n->location = location;
 	return (Node *) n;
 }
 
+static Node *
+makeTypeCastOpt(Node *arg, TypeName *typename,
+				Node *defexpr, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->defexpr = defexpr;
+	n->location = location;
+
+	return (Node *) n;
+}
+
 static Node *
 makeStringConstCast(char *str, int location, TypeName *typename)
 {
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d3240f4b265..a75a70add28 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -17,6 +17,7 @@
 #include "access/htup_details.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
@@ -33,13 +34,15 @@
 #include "utils/typcache.h"
 
 
-static Node *coerce_type_typmod(Node *node,
+static Node *coerce_type_typmod(ParseState *pstate,
+								Node *node,
 								Oid targetTypeId, int32 targetTypMod,
 								CoercionContext ccontext, CoercionForm cformat,
 								int location,
 								bool hideInputCoercion);
 static void hide_coercion_node(Node *node);
-static Node *build_coercion_expression(Node *node,
+static Node *build_coercion_expression(ParseState *pstate,
+									   Node *node,
 									   CoercionPathType pathtype,
 									   Oid funcId,
 									   Oid targetTypeId, int32 targetTypMod,
@@ -53,6 +56,12 @@ static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
 static bool is_complex_array(Oid typid);
 static bool typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId);
 
+/* pstate may be NULL in parse_coerce.c entry points */
+static inline Node *
+parse_escontext(ParseState *pstate)
+{
+	return pstate ? pstate->p_escontext : NULL;
+}
 
 /*
  * coerce_to_target_type()
@@ -111,10 +120,12 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
 	 * well as a type coercion.  If we find ourselves adding both, force the
 	 * inner coercion node to implicit display form.
 	 */
-	result = coerce_type_typmod(result,
+	result = coerce_type_typmod(pstate, result,
 								targettype, targettypmod,
 								ccontext, cformat, location,
 								(result != expr && !IsA(result, Const)));
+	if (result == NULL)			/* shouldn't happen */
+		elog(ERROR, "failed to coerce type modifier for type %u", targettype);
 
 	if (expr != origexpr && type_is_collatable(targettype))
 	{
@@ -256,6 +267,8 @@ coerce_type(ParseState *pstate, Node *node,
 		int32		inputTypeMod;
 		Type		baseType;
 		ParseCallbackState pcbstate;
+		char	   *string = NULL;
+		ErrorSaveContext escontext = {.type = T_ErrorSaveContext};
 
 		/*
 		 * If the target type is a domain, we want to call its base type's
@@ -309,13 +322,35 @@ coerce_type(ParseState *pstate, Node *node,
 		 * as CSTRING.
 		 */
 		if (!con->constisnull)
-			newcon->constvalue = stringTypeDatum(baseType,
-												 DatumGetCString(con->constvalue),
-												 inputTypeMod);
-		else
-			newcon->constvalue = stringTypeDatum(baseType,
-												 NULL,
-												 inputTypeMod);
+			string = DatumGetCString(con->constvalue);
+
+		/*
+		 * Under an ErrorSaveContext (CAST ... DEFAULT ... ON CONVERSION
+		 * ERROR), an invalid literal is not thrown: we flag the error in
+		 * pstate->p_escontext and return the literal as a text constant.
+		 */
+		if (!stringTypeDatumSafe(baseType, string, inputTypeMod,
+								 parse_escontext(pstate) ? (Node *) &escontext : NULL,
+								 &newcon->constvalue))
+		{
+			Const	   *textcon;
+
+			((ErrorSaveContext *) pstate->p_escontext)->error_occurred = true;
+
+			cancel_parser_errposition_callback(&pcbstate);
+			ReleaseSysCache(baseType);
+
+			textcon = makeConst(TEXTOID,
+								-1,
+								DEFAULT_COLLATION_OID,
+								-1,
+								con->constisnull ? (Datum) 0 : CStringGetTextDatum(string),
+								con->constisnull,
+								false);
+			textcon->location = con->location;
+
+			return (Node *) textcon;
+		}
 
 		/*
 		 * If it's a varlena value, force it to be in non-expanded
@@ -360,7 +395,7 @@ coerce_type(ParseState *pstate, Node *node,
 
 		/* If target is a domain, apply constraints. */
 		if (baseTypeId != targetTypeId)
-			result = coerce_to_domain(result,
+			result = coerce_to_domain(pstate, result,
 									  baseTypeId, baseTypeMod,
 									  targetTypeId,
 									  ccontext, cformat, location,
@@ -430,7 +465,7 @@ coerce_type(ParseState *pstate, Node *node,
 			 * and we need to extract the correct typmod to use from the
 			 * domain's typtypmod.
 			 */
-			result = build_coercion_expression(node, pathtype, funcId,
+			result = build_coercion_expression(pstate, node, pathtype, funcId,
 											   baseTypeId, baseTypeMod,
 											   ccontext, cformat, location);
 
@@ -439,7 +474,7 @@ coerce_type(ParseState *pstate, Node *node,
 			 * type ID, hiding the previous coercion node.
 			 */
 			if (targetTypeId != baseTypeId)
-				result = coerce_to_domain(result, baseTypeId, baseTypeMod,
+				result = coerce_to_domain(pstate, result, baseTypeId, baseTypeMod,
 										  targetTypeId,
 										  ccontext, cformat, location,
 										  true);
@@ -455,7 +490,7 @@ coerce_type(ParseState *pstate, Node *node,
 			 * that must be accounted for.  If the destination is a domain
 			 * then we won't need a RelabelType node.
 			 */
-			result = coerce_to_domain(node, baseTypeId, baseTypeMod,
+			result = coerce_to_domain(pstate, node, baseTypeId, baseTypeMod,
 									  targetTypeId,
 									  ccontext, cformat, location,
 									  false);
@@ -478,6 +513,20 @@ coerce_type(ParseState *pstate, Node *node,
 		}
 		return result;
 	}
+
+	/*
+	 * What remains are row-type casts.  These are not supported under CAST
+	 * ... DEFAULT ... ON CONVERSION ERROR for now; that could be added later.
+	 */
+	if (parse_escontext(pstate) != NULL)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot cast type %s to %s with DEFAULT expression in CAST ... ON CONVERSION ERROR",
+					   format_type_be(inputTypeId),
+					   format_type_be(targetTypeId)),
+				errdetail("Casts between row types are not supported here."),
+				parser_errposition(pstate, exprLocation(node)));
+
 	if (inputTypeId == RECORDOID &&
 		ISCOMPLEX(targetTypeId))
 	{
@@ -665,6 +714,7 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
 /*
  * Create an expression tree to represent coercion to a domain type.
  *
+ *  'pstate': parse state
  * 'arg': input expression
  * 'baseTypeId': base type of domain
  * 'baseTypeMod': base type typmod of domain
@@ -677,7 +727,8 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
  * If the target type isn't a domain, the given 'arg' is returned as-is.
  */
 Node *
-coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
+coerce_to_domain(ParseState *pstate,
+				 Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
 				 CoercionContext ccontext, CoercionForm cformat, int location,
 				 bool hideInputCoercion)
 {
@@ -707,9 +758,11 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
 	 * would be safe to do anyway, without lots of knowledge about what the
 	 * base type thinks the typmod means.
 	 */
-	arg = coerce_type_typmod(arg, baseTypeId, baseTypeMod,
+	arg = coerce_type_typmod(pstate, arg, baseTypeId, baseTypeMod,
 							 ccontext, COERCE_IMPLICIT_CAST, location,
 							 false);
+	if (arg == NULL)			/* shouldn't happen */
+		elog(ERROR, "failed to coerce type modifier as expected");
 
 	/*
 	 * Now build the domain coercion node.  This represents run-time checking
@@ -753,7 +806,8 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
  * needed to produce the domain value in the first place.  So, no getBaseType.
  */
 static Node *
-coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
+coerce_type_typmod(ParseState *pstate,
+				   Node *node, Oid targetTypeId, int32 targetTypMod,
 				   CoercionContext ccontext, CoercionForm cformat,
 				   int location,
 				   bool hideInputCoercion)
@@ -781,7 +835,7 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
 
 	if (pathtype != COERCION_PATH_NONE)
 	{
-		node = build_coercion_expression(node, pathtype, funcId,
+		node = build_coercion_expression(pstate, node, pathtype, funcId,
 										 targetTypeId, targetTypMod,
 										 ccontext, cformat, location);
 	}
@@ -839,7 +893,8 @@ hide_coercion_node(Node *node)
  * since there is no difference in terms of the calling convention.
  */
 static Node *
-build_coercion_expression(Node *node,
+build_coercion_expression(ParseState *pstate,
+						  Node *node,
 						  CoercionPathType pathtype,
 						  Oid funcId,
 						  Oid targetTypeId, int32 targetTypMod,
@@ -873,6 +928,15 @@ build_coercion_expression(Node *node,
 		Assert(nargs < 2 || procstruct->proargtypes.values[1] == INT4OID);
 		Assert(nargs < 3 || procstruct->proargtypes.values[2] == BOOLOID);
 
+		if (parse_escontext(pstate) != NULL && !procstruct->proerrorsafe)
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("cannot cast type %s to %s with DEFAULT expression in CAST ... ON CONVERSION ERROR",
+						   format_type_be(exprType(node)),
+						   format_type_be(targetTypeId)),
+					errdetail("Explicit cast is defined but definition is not error safe."),
+					parser_errposition(pstate, exprLocation(node)));
+
 		ReleaseSysCache(tp);
 	}
 
@@ -954,7 +1018,7 @@ build_coercion_expression(Node *node,
 		targetElementType = get_element_type(targetTypeId);
 		Assert(OidIsValid(targetElementType));
 
-		elemexpr = coerce_to_target_type(NULL,
+		elemexpr = coerce_to_target_type(pstate,
 										 (Node *) ctest,
 										 ctest->typeId,
 										 targetElementType,
@@ -1139,7 +1203,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 	if (baseTypeId != targetTypeId)
 	{
 		rowexpr->row_format = COERCE_IMPLICIT_CAST;
-		return coerce_to_domain((Node *) rowexpr,
+		return coerce_to_domain(NULL, (Node *) rowexpr,
 								baseTypeId, baseTypeMod,
 								targetTypeId,
 								ccontext, cformat, location,
@@ -1292,7 +1356,8 @@ coerce_null_to_domain(Oid typid, int32 typmod, Oid collation,
 								true,	/* isnull */
 								typbyval);
 	if (typid != baseTypeId)
-		result = coerce_to_domain(result,
+		result = coerce_to_domain(NULL,
+								  result,
 								  baseTypeId, baseTypeMod,
 								  typid,
 								  COERCION_IMPLICIT,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index ba7df2a7789..043cebd23de 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -441,6 +441,32 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 					exprSetCollation(node, collation);
 			}
 			break;
+
+		case T_SafeTypeCastExpr:
+			{
+				/*
+				 * Like CaseExpr, we do not recurse into the input expression.
+				 * It was already marked with collations during
+				 * transformSafeTypeCast, and its collation reaches the result
+				 * only through castexpr's placeholder.
+				 */
+				SafeTypeCastExpr *stc = (SafeTypeCastExpr *) node;
+
+				(void) assign_collations_walker((Node *) stc->castexpr,
+												&loccontext);
+				(void) assign_collations_walker((Node *) stc->defexpr,
+												&loccontext);
+			}
+			break;
+		case T_SafeCastElement:
+			{
+				/* Likewise, see T_SafeTypeCastExpr above */
+				SafeCastElement *elem = (SafeCastElement *) node;
+
+				(void) assign_collations_walker((Node *) elem->castexpr,
+												&loccontext);
+			}
+			break;
 		case T_TargetEntry:
 			(void) expression_tree_walker(node,
 										  assign_collations_walker,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 728392f436f..89e3bd94af2 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -61,7 +61,9 @@ static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
 static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c);
 static Node *transformSubLink(ParseState *pstate, SubLink *sublink);
 static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
-								Oid array_type, Oid element_type, int32 typmod);
+								Oid array_type, Oid element_type, int32 typmod,
+								bool *coercion_failed);
+
 static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault);
 static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
 static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
@@ -109,6 +111,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
 static Node *make_nulltest_from_distinct(ParseState *pstate,
 										 A_Expr *distincta, Node *arg);
 
+static Node *transformSafeTypeCast(ParseState *pstate, TypeCast *tc);
+static Node *transformSafeCastSource(ParseState *pstate, Node *expr,
+									 Oid targetType, int32 targetTypmod);
 
 /*
  * transformExpr -
@@ -165,7 +170,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 
 		case T_A_ArrayExpr:
 			result = transformArrayExpr(pstate, (A_ArrayExpr *) expr,
-										InvalidOid, InvalidOid, -1);
+										InvalidOid, InvalidOid, -1, NULL);
 			break;
 
 		case T_TypeCast:
@@ -2024,12 +2029,14 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
  */
 static Node *
 transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
-				   Oid array_type, Oid element_type, int32 typmod)
+				   Oid array_type, Oid element_type, int32 typmod,
+				   bool *coercion_failed)
 {
 	ArrayExpr  *newa = makeNode(ArrayExpr);
 	List	   *newelems = NIL;
 	List	   *newcoercedelems = NIL;
 	ListCell   *element;
+	ListCell   *rawelement;
 	Oid			coerce_type;
 	bool		coerce_hard;
 
@@ -2055,7 +2062,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  coercion_failed);
 			/* we certainly have an array here */
 			Assert(array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
@@ -2135,6 +2143,111 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		coerce_hard = false;
 	}
 
+	if (coercion_failed != NULL)
+	{
+		Assert(coerce_hard);
+
+		/*
+		 * Build a SafeCastElement converting each element of an ARRAY[]
+		 * constructor argument of (CAST ... DEFAULT ... ON CONVERSION ERROR)
+		 * to the target element type.
+		 */
+		forboth(element, newelems, rawelement, a->elements)
+		{
+			Node	   *e = (Node *) lfirst(element);
+			Node	   *newe;
+
+			if (IsA(lfirst(rawelement), A_ArrayExpr))
+				newe = e;
+			else
+			{
+				Node	   *castexpr;
+				SafeCastElement *elem;
+				int			location = exprLocation(e);
+				Node	   *saved_escontext = pstate->p_escontext;
+				ErrorSaveContext *escontext = makeNode(ErrorSaveContext);
+
+				/*
+				 * each element gets its own context, so one failure doesn't
+				 * hide others
+				 */
+				pstate->p_escontext = (Node *) escontext;
+
+				e = transformSafeCastSource(pstate, e, coerce_type, typmod);
+
+				/*
+				 * Run collation assignment on the argument so that we know
+				 * what collation to mark the placeholder with, as
+				 * transformCaseExpr does.
+				 */
+				assign_expr_collations(pstate, e);
+
+				if (escontext->error_occurred)
+					castexpr = NULL;
+				else
+				{
+					CaseTestExpr *placeholder;
+
+					placeholder = makeNode(CaseTestExpr);
+					placeholder->typeId = exprType(e);
+					placeholder->typeMod = exprTypmod(e);
+					placeholder->collation = exprCollation(e);
+
+					castexpr = coerce_to_target_type(pstate, (Node *) placeholder,
+													 placeholder->typeId,
+													 coerce_type, typmod,
+													 COERCION_EXPLICIT,
+													 COERCE_EXPLICIT_CAST,
+													 location);
+					if (escontext->error_occurred)
+						castexpr = NULL;
+
+					/*
+					 * The placeholder carries the argument's collation only
+					 * at implicit strength.  Keep an explicit COLLATE on the
+					 * argument explicit
+					 */
+					if (castexpr != NULL && IsA(e, CollateExpr) &&
+						type_is_collatable(coerce_type))
+					{
+						CollateExpr *coll = makeNode(CollateExpr);
+
+						coll->arg = (Expr *) castexpr;
+						coll->collOid = ((CollateExpr *) e)->collOid;
+						coll->location = ((CollateExpr *) e)->location;
+						castexpr = (Node *) coll;
+					}
+				}
+
+				if (castexpr == NULL)
+					*coercion_failed = true;
+
+				elem = makeNode(SafeCastElement);
+				elem->arg = (Expr *) e;
+				elem->castexpr = (Expr *) castexpr;
+				elem->resulttype = coerce_type;
+				elem->resulttypmod = typmod;
+				elem->resultcollid = get_typcollation(coerce_type);
+				elem->location = location;
+				newe = (Node *) elem;
+
+				pstate->p_escontext = saved_escontext;
+			}
+
+			newcoercedelems = lappend(newcoercedelems, newe);
+		}
+
+		newa->array_typeid = array_type;
+		/* array_collid will be set by transformSafeTypeCast() */
+		newa->element_typeid = element_type;
+		newa->elements = newcoercedelems;
+		newa->list_start = a->list_start;
+		newa->list_end = a->list_end;
+		newa->location = a->location;
+
+		return (Node *) newa;
+	}
+
 	/*
 	 * Coerce elements to target type
 	 *
@@ -2722,6 +2835,9 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	int32		targetTypmod;
 	int			location;
 
+	if (tc->defexpr)
+		return transformSafeTypeCast(pstate, tc);
+
 	/* Look up the type name first */
 	typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod);
 
@@ -2753,7 +2869,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 									  (A_ArrayExpr *) arg,
 									  targetBaseType,
 									  elementType,
-									  targetBaseTypmod);
+									  targetBaseTypmod,
+									  NULL);
 		}
 		else
 			expr = transformExprRecurse(pstate, arg);
@@ -2823,6 +2940,253 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 	return (Node *) newc;
 }
 
+/*
+ * Transform CAST(expr AS type DEFAULT defexpr ON CONVERSION ERROR).
+ *
+ * The argument is stored once, in "arg", and is not coerced itself; instead we
+ * coerce a CaseTestExpr placeholder of its type, so castexpr holds only the
+ * coercion and no tree walker sees the argument twice. At runtime the executor
+ * feeds the value of "arg" to the placeholder, as ArrayCoerceExpr does for
+ * elemexpr.
+ */
+static Node *
+transformSafeTypeCast(ParseState *pstate, TypeCast *tc)
+{
+	Node	   *defexpr;
+	Node	   *castexpr;
+	Node	   *arg = tc->arg;
+	Node	   *expr = NULL;
+	SafeTypeCastExpr *stc;
+	Oid			targetType;
+	int32		targetTypmod;
+	Oid			targetTypecoll;
+	bool		arg_errorsafe = false;
+	bool		array_failed = false;
+	int			defloc;
+	Node	   *last_srf;
+	Node	   *saved_escontext;
+
+	last_srf = pstate->p_last_srf;
+	defloc = exprLocation(tc->defexpr);
+	saved_escontext = pstate->p_escontext;
+
+	/* Look up the type name first */
+	typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod);
+
+	targetTypecoll = get_typcollation(targetType);
+
+	defexpr = transformExprRecurse(pstate, tc->defexpr);
+
+	/* if the DEFAULT contained a SRF, complain */
+	if (pstate->p_last_srf != last_srf)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("set-returning functions are not allowed in CAST DEFAULT expression"),
+				parser_errposition(pstate, defloc));
+
+	defexpr = coerce_to_target_type(pstate, defexpr, exprType(defexpr),
+									targetType, targetTypmod,
+									COERCION_EXPLICIT,
+									COERCE_EXPLICIT_CAST,
+									exprLocation(defexpr));
+	if (defexpr == NULL)
+		ereport(ERROR,
+				errcode(ERRCODE_CANNOT_COERCE),
+				errmsg("cannot coerce %s expression to type %s",
+					   "CAST DEFAULT",
+					   format_type_be(targetType)),
+				parser_coercion_errposition(pstate, exprLocation(tc->defexpr), defexpr));
+
+	/*
+	 * If the subject of the typecast is an ARRAY[] construct and the target
+	 * type is an array type, we invoke transformArrayExpr() directly so that
+	 * we can pass down the type information.  This avoids some cases where
+	 * transformArrayExpr() might not infer the correct type.  It wraps each
+	 * element in a SafeCastElement converting it to the target element type,
+	 * and sets array_failed if an element has no conversion.
+	 */
+	if (IsA(arg, A_ArrayExpr))
+	{
+		Oid			targetBaseType;
+		int32		targetBaseTypmod;
+		Oid			elementType;
+
+		/*
+		 * If target is a domain over array, work with the base array type
+		 * here.  Below, we'll cast the array type to the domain.  In the
+		 * usual case that the target is not a domain, the remaining steps
+		 * will be a no-op.
+		 */
+		targetBaseTypmod = targetTypmod;
+		targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+		elementType = get_element_type(targetBaseType);
+		if (OidIsValid(elementType))
+		{
+			expr = transformArrayExpr(pstate,
+									  (A_ArrayExpr *) arg,
+									  targetBaseType,
+									  elementType,
+									  targetBaseTypmod,
+									  &array_failed);
+			arg_errorsafe = true;
+		}
+		else
+			expr = transformExprRecurse(pstate, arg);
+	}
+	else
+		expr = transformExprRecurse(pstate, arg);
+
+	if (array_failed)
+	{
+		assign_expr_collations(pstate, expr);
+		castexpr = NULL;
+	}
+	else
+	{
+		ErrorSaveContext *escontext = NULL;
+
+		/*
+		 * Build the coercion expression under our ErrorSaveContext, so that
+		 * cast functions not marked ERROR SAFE are rejected, and an invalid
+		 * literal is recorded rather than thrown.
+		 */
+		escontext = makeNode(ErrorSaveContext);
+		pstate->p_escontext = (Node *) escontext;
+
+		expr = transformSafeCastSource(pstate, expr, targetType, targetTypmod);
+
+		/*
+		 * Run collation assignment on the argument so that we know what
+		 * collation to mark the placeholder with, as transformCaseExpr does.
+		 */
+		assign_expr_collations(pstate, expr);
+
+		if (SOFT_ERROR_OCCURRED(pstate->p_escontext))
+			castexpr = NULL;
+		else
+		{
+			CaseTestExpr *placeholder;
+
+			placeholder = makeNode(CaseTestExpr);
+			placeholder->typeId = exprType(expr);
+			placeholder->typeMod = exprTypmod(expr);
+			placeholder->collation = exprCollation(expr);
+
+			castexpr = coerce_to_target_type(pstate, (Node *) placeholder, exprType(expr),
+											 targetType, targetTypmod,
+											 COERCION_EXPLICIT,
+											 COERCE_EXPLICIT_CAST,
+											 tc->location);
+
+			if (SOFT_ERROR_OCCURRED(pstate->p_escontext))
+				castexpr = NULL;
+
+			/*
+			 * The placeholder carries the argument's collation only at
+			 * implicit strength.  Keep an explicit COLLATE on the argument
+			 * explicit
+			 */
+			if (castexpr != NULL && IsA(expr, CollateExpr) &&
+				type_is_collatable(targetType))
+			{
+				CollateExpr *coll = makeNode(CollateExpr);
+
+				coll->arg = (Expr *) castexpr;
+				coll->collOid = ((CollateExpr *) expr)->collOid;
+				coll->location = ((CollateExpr *) expr)->location;
+				castexpr = (Node *) coll;
+			}
+		}
+	}
+
+	pstate->p_escontext = saved_escontext;
+
+	stc = makeNode(SafeTypeCastExpr);
+	stc->arg = (Expr *) expr;
+	stc->arg_errorsafe = arg_errorsafe;
+	stc->castexpr = (Expr *) castexpr;
+	stc->defexpr = (Expr *) defexpr;
+	stc->resulttype = targetType;
+	stc->resulttypmod = targetTypmod;
+	stc->resultcollid = targetTypecoll;
+	stc->location = tc->location;
+
+	return (Node *) stc;
+}
+
+/*
+ * Prepare the source expression of CAST ... DEFAULT ... ON CONVERSION ERROR, or
+ * of one of its ARRAY[] elements, for conversion over a placeholder.
+ *
+ * Only the placeholder is coerced to the target type, but an unknown literal
+ * still needs a real type.  It is first converted to the target's base type, as
+ * in a plain CAST; for INTERVAL that passes the typmod (see coerce_type()). So
+ *		'1'::interval hour			gives 01:00:00
+ *		'1'::text::interval hour	gives 00:00:00
+ */
+static Node *
+transformSafeCastSource(ParseState *pstate, Node *expr,
+						Oid targetType, int32 targetTypmod)
+{
+	Node	   *origexpr = expr;
+	Node	   *result = NULL;
+
+	Assert(pstate->p_escontext != NULL &&
+		   !SOFT_ERROR_OCCURRED(pstate->p_escontext));
+
+	while (expr && IsA(expr, CollateExpr))
+		expr = (Node *) ((CollateExpr *) expr)->arg;
+
+	if (exprType(expr) != UNKNOWNOID)
+		result = expr;
+	else
+	{
+		if (IsA(expr, Const))
+		{
+			Oid			baseType;
+			int32		baseTypmod = targetTypmod;
+
+			baseType = getBaseTypeAndTypmod(targetType, &baseTypmod);
+
+			result = coerce_type(pstate, expr, UNKNOWNOID,
+								 baseType, baseTypmod,
+								 COERCION_EXPLICIT,
+								 COERCE_EXPLICIT_CAST,
+								 exprLocation(expr));
+
+			if (SOFT_ERROR_OCCURRED(pstate->p_escontext) ||
+				exprType(result) == UNKNOWNOID)
+				result = NULL;
+		}
+
+		if (result == NULL)
+			result = coerce_type(pstate, expr, UNKNOWNOID,
+								 TEXTOID, -1,
+								 COERCION_IMPLICIT,
+								 COERCE_IMPLICIT_CAST,
+								 exprLocation(expr));
+	}
+
+	/*
+	 * Reinstall the top CollateExpr, unless the type is not collatable, as
+	 * coerce_type() does for a plain CAST.
+	 */
+	if (IsA(origexpr, CollateExpr) && type_is_collatable(exprType(result)))
+	{
+		CollateExpr *coll = (CollateExpr *) origexpr;
+		CollateExpr *newcoll = makeNode(CollateExpr);
+
+		newcoll->arg = (Expr *) result;
+		newcoll->collOid = coll->collOid;
+		newcoll->location = coll->location;
+		result = (Node *) newcoll;
+	}
+
+	Assert(result != NULL);
+
+	return result;
+}
+
 /*
  * Transform a "row compare-op row" construct
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 728936b502e..445323d23fa 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -835,7 +835,7 @@ transformAssignmentIndirection(ParseState *pstate,
 			 * the fields that the INSERT or UPDATE means to.
 			 */
 			if (baseTypeId != targetTypeId)
-				return coerce_to_domain((Node *) fstore,
+				return coerce_to_domain(NULL, (Node *) fstore,
 										baseTypeId, baseTypeMod,
 										targetTypeId,
 										COERCION_IMPLICIT,
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 262f2742b02..fb984d4fb96 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -660,6 +660,28 @@ stringTypeDatum(Type tp, const char *string, int32 atttypmod)
 	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
 }
 
+/*
+ * Error-safe version of stringTypeDatum.
+ *
+ * Returns true if "string" is a valid representation of type "tp".
+ * On success, the internal Datum representation is written to "result".
+ */
+bool
+stringTypeDatumSafe(Type tp, const char *string, int32 atttypmod,
+					Node *escontext, Datum *result)
+{
+	FmgrInfo	flinfo;
+
+	Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+	Oid			typinput = typform->typinput;
+	Oid			typioparam = getTypeIOParam(tp);
+
+	fmgr_info(typinput, &flinfo);
+
+	return InputFunctionCallSafe(&flinfo, string, typioparam, atttypmod,
+								 escontext, result);
+}
+
 /*
  * Given a typeid, return the type's typrelid (associated relation), if any.
  * Returns InvalidOid if type is not a composite type.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 583346bf531..9c204274ade 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -688,6 +688,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 		castnode = makeNode(TypeCast);
 		castnode->typeName = SystemTypeName("regclass");
 		castnode->arg = (Node *) snamenode;
+		castnode->defexpr = NULL;
 		castnode->location = -1;
 		funccallnode = makeFuncCall(SystemFuncName("nextval"),
 									list_make1(castnode),
@@ -4866,7 +4867,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
 		assign_expr_collations(pstate, value);
 		value = (Node *) expression_planner((Expr *) value);
 		value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod,
-									   partCollation);
+									   partCollation, NULL);
 		if (!IsA(value, Const))
 			elog(ERROR, "could not evaluate partition bound expression");
 	}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f1f6336e5c8..041da42877f 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3287,6 +3287,15 @@ array_map(Datum arrayd,
 		/* Apply the given expression to source element */
 		values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
 
+		/* Exit early if evaluation failed */
+		if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+		{
+			pfree(values);
+			pfree(nulls);
+
+			return (Datum) 0;
+		}
+
 		if (nulls[i])
 			hasnulls = true;
 		else
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 294228bad82..95db325ba1d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10618,6 +10618,34 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				appendStringInfoString(buf, "CAST(");
+				get_rule_expr((Node *) stcexpr->arg, context, showimplicit);
+
+				appendStringInfo(buf, " AS %s ",
+								 format_type_with_typemod(stcexpr->resulttype,
+														  stcexpr->resulttypmod));
+
+				appendStringInfoString(buf, "DEFAULT ");
+
+				get_rule_expr((Node *) stcexpr->defexpr, context, showimplicit);
+
+				appendStringInfoString(buf, " ON CONVERSION ERROR)");
+			}
+			break;
+
+		case T_SafeCastElement:
+			{
+				SafeCastElement *elem = castNode(SafeCastElement, node);
+
+				/* the conversion is implied by the enclosing CAST */
+				get_rule_expr((Node *) elem->arg, context, showimplicit);
+			}
+			break;
+
 		case T_JsonExpr:
 			{
 				JsonExpr   *jexpr = (JsonExpr *) node;
diff --git a/src/backend/utils/fmgr/README b/src/backend/utils/fmgr/README
index 9958d38992b..56d899d35a2 100644
--- a/src/backend/utils/fmgr/README
+++ b/src/backend/utils/fmgr/README
@@ -267,12 +267,15 @@ See windowapi.h for more information.
 information about the context of the CALL statement, particularly
 whether it is within an "atomic" execution context.
 
-* Some callers of datatype input functions (and in future perhaps
-other classes of functions) pass an instance of ErrorSaveContext.
-This indicates that the caller wishes to handle "soft" errors without
-a transaction-terminating exception being thrown: instead, the callee
-should store information about the error cause in the ErrorSaveContext
-struct and return a dummy result value.  Further details appear in
+* Some callers pass an instance of ErrorSaveContext, indicating that they
+wish to handle "soft" errors themselves rather than have a
+transaction-terminating exception thrown.  In that case the callee should
+record the error cause in the ErrorSaveContext struct and return a dummy
+result value.  Datatype input functions are called this way.  So is every
+function call within an expression that the executor compiles with an
+ErrorSaveContext, for example the conversion in CAST ... DEFAULT ... ON
+CONVERSION ERROR.  Reporting errors softly is up to the callee: any error
+it does not report that way is thrown as usual.  Further details appear in
 "Handling Soft Errors" below.
 
 
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c61b3d624d5..64eac197758 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -125,6 +125,10 @@ typedef enum ExprEvalOp
 	EEOP_FUNCEXPR_STRICT_2,
 	EEOP_FUNCEXPR_FUSAGE,
 	EEOP_FUNCEXPR_STRICT_FUSAGE,
+	EEOP_FUNCEXPR_SAFE,			/* called with an ErrorSaveContext */
+	EEOP_FUNCEXPR_STRICT_SAFE,
+	EEOP_FUNCEXPR_SAFE_FUSAGE,
+	EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE,
 
 	/*
 	 * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
@@ -265,6 +269,7 @@ typedef enum ExprEvalOp
 	EEOP_XMLEXPR,
 	EEOP_JSON_CONSTRUCTOR,
 	EEOP_IS_JSON,
+	EEOP_SAFETYPE_CAST,
 	EEOP_JSONEXPR_PATH,
 	EEOP_JSONEXPR_COERCION,
 	EEOP_JSONEXPR_COERCION_FINISH,
@@ -754,6 +759,12 @@ typedef struct ExprEvalStep
 			JsonIsPredicate *pred;	/* original expression node */
 		}			is_json;
 
+		/* for EEOP_SAFETYPE_CAST */
+		struct
+		{
+			struct SafeTypeCastState *stcstate;
+		}			stcexpr;
+
 		/* for EEOP_JSONEXPR_PATH */
 		struct
 		{
@@ -854,8 +865,12 @@ extern void CheckExprStillValid(ExprState *state, ExprContext *econtext);
  */
 extern void ExecEvalFuncExprFusage(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
+extern void ExecEvalFuncSafeFusage(ExprState *state, ExprEvalStep *op,
+								   ExprContext *econtext);
 extern void ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op,
 										 ExprContext *econtext);
+extern void ExecEvalFuncStrictSafeFusage(ExprState *state, ExprEvalStep *op,
+										 ExprContext *econtext);
 extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 							  ExprContext *econtext);
 extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 152a1dfa568..657af8269ef 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -332,6 +332,7 @@ ExecProcNode(PlanState *node)
  * prototypes from functions in execExpr.c
  */
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprWithContext(Expr *node, PlanState *parent, Node *escontext);
 extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
 extern ExprState *ExecInitQual(List *qual, PlanState *parent);
 extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fe8681ba642..cc4c13b19f5 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1073,6 +1073,29 @@ typedef struct DomainConstraintState
 	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;
 
+typedef struct SafeTypeCastState
+{
+	SafeTypeCastExpr *stcexpr;
+
+	NullableDatum	args;
+
+	/*
+	 * The jump target used to skip all remaining steps when the default
+	 * expression evaluation is bypassed.
+	 */
+	int			jump_end;
+
+	/*
+	 * Error-safe context for type coercion evaluation. A pointer to this is
+	 * passed to ExecInitExprRec() during type coercion expression
+	 * initialization (see ExecInitSafeTypeCastExpr).
+	 *
+	 * At runtime, this is reset before each evaluation of EEOP_SAFETYPE_CAST.
+	 */
+	ErrorSaveContext escontext;
+
+} SafeTypeCastState;
+
 /*
  * State for JsonExpr evaluation, too big to inline.
  *
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0debcd193ab..e385f3afa96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -398,6 +398,7 @@ typedef struct TypeCast
 	NodeTag		type;
 	Node	   *arg;			/* the expression being casted */
 	TypeName   *typeName;		/* the target type */
+	Node	   *defexpr;		/* DEFAULT expression */
 	ParseLoc	location;		/* token location, or -1 if unknown */
 } TypeCast;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 726c186c7d2..cda32f14301 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -764,6 +764,77 @@ typedef enum CoercionForm
 	COERCE_SQL_SYNTAX,			/* display with SQL-mandated special syntax */
 } CoercionForm;
 
+/*
+ * SafeTypeCastExpr -
+ *		Transformed representation of
+ * 		CAST(expr AS typename DEFAULT expr ON CONVERSION ERROR)
+ */
+typedef struct SafeTypeCastExpr
+{
+	Expr		xpr;
+
+	/*
+	 * The transformed input expression, evaluated once and fed to castexpr.
+	 * Normally arg is not evaluated in an error-safe manner.  For an ARRAY[]
+	 * input cast to an array type, however, its elements are SafeCastElement
+	 * nodes converting them to the target element type.  arg_errorsafe is
+	 * then true, and arg is evaluated under this cast's error context, so
+	 * that those conversions fail softly.
+	 */
+	Expr	   *arg;
+	bool		arg_errorsafe;
+
+	/*
+	 * coercion of arg to the target type, built over a CaseTestExpr that
+	 * stands for its value; NULL means no cast path exists, in which case the
+	 * result is always defexpr
+	 */
+	Expr	   *castexpr;
+	/* Fall-back to the default expression if cast evaluation fails */
+	Expr	   *defexpr;
+
+	/* target type Oid */
+	Oid			resulttype;
+	/* target type modifier */
+	int32		resulttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* token location, or -1 if unknown */
+	ParseLoc	location;
+} SafeTypeCastExpr;
+
+/*
+ * SafeCastElement -
+ *		One element of an ARRAY[] SafeTypeCastExpr.arg (see arg_errorsafe)
+ *
+ * arg, the element itself, is evaluated with hard errors.  castexpr converts
+ * its value to the target element type and reports errors into the enclosing
+ * SafeTypeCastExpr's context, which then returns its DEFAULT.
+ */
+typedef struct SafeCastElement
+{
+	Expr		xpr;
+
+	/* the element expression */
+	Expr	   *arg;
+
+	/*
+	 * coercion of arg to the target element type, built over a CaseTestExpr
+	 * that stands for arg's value; NULL means no cast path exists, in which
+	 * case the element yields NULL and the enclosing cast has no castexpr
+	 */
+	Expr	   *castexpr;
+
+	/* target element type Oid */
+	Oid			resulttype;
+	/* target element type modifier */
+	int32		resulttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* token location, or -1 if unknown */
+	ParseLoc	location;
+} SafeCastElement;
+
 /*
  * FuncExpr - expression node for a function call
  */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index cb6241e2bdd..fb81ed6e551 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -151,7 +151,7 @@ extern void convert_saop_to_hashed_saop(Node *node);
 extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
 
 extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
-						   Oid result_collation);
+						   Oid result_collation, Node *escontext);
 
 extern bool var_is_nonnullable(PlannerInfo *root, Var *var,
 							   NotNullSource source);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index aabacd49b65..98caa18d73e 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -48,7 +48,8 @@ extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *targ
 extern Node *coerce_type(ParseState *pstate, Node *node,
 						 Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
 						 CoercionContext ccontext, CoercionForm cformat, int location);
-extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
+extern Node *coerce_to_domain(ParseState *pstate, Node *arg,
+							  Oid baseTypeId, int32 baseTypeMod,
 							  Oid typeId,
 							  CoercionContext ccontext, CoercionForm cformat, int location,
 							  bool hideInputCoercion);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f23e21f318b..36412e5e26c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -180,6 +180,10 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * p_last_srf: the set-returning FuncExpr or OpExpr most recently found in
  * the query, or NULL if none.
  *
+ * p_escontext: if not NULL, an ErrorSaveContext under which expressions are
+ * evaluated during parse analysis, so that conversion errors are reported
+ * softly instead of being thrown.
+ *
  * p_pre_columnref_hook, etc: optional parser hook functions for modifying the
  * interpretation of ColumnRefs and ParamRefs.
  *
@@ -226,6 +230,8 @@ struct ParseState
 
 	Node	   *p_last_srf;		/* most recent set-returning func/op found */
 
+	Node	   *p_escontext;	/* ErrorSaveContext pointer */
+
 	/*
 	 * Optional hook functions for parser callbacks.  These are null unless
 	 * set up by the caller of make_parsestate.
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 79f10b5b5b2..cad8d019e84 100644
--- a/src/include/parser/parse_type.h
+++ b/src/include/parser/parse_type.h
@@ -47,6 +47,8 @@ extern char *typeTypeName(Type t);
 extern Oid	typeTypeRelid(Type typ);
 extern Oid	typeTypeCollation(Type typ);
 extern Datum stringTypeDatum(Type tp, const char *string, int32 atttypmod);
+extern bool stringTypeDatumSafe(Type tp, const char *string, int32 atttypmod,
+								Node *escontext, Datum *result);
 
 extern Oid	typeidTypeRelid(Oid type_id);
 extern Oid	typeOrDomainTypeRelid(Oid type_id);
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..c46aab71546
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,1194 @@
+SET extra_float_digits = 0;
+SET lc_monetary TO "C";
+-- CAST DEFAULT ON CONVERSION ERROR
+VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); -- error
+ERROR:  invalid input syntax for type integer: "error"
+LINE 1: VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR));
+                     ^
+-- The DEFAULT expression can contain aggreagte/window function or subquery
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR);
+ int4 
+------
+    1
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR);
+ int4 
+------
+    1
+(1 row)
+
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR);
+ int4 
+------
+     
+(1 row)
+
+SELECT CAST('a' as int DEFAULT (SELECT 1 where false) ON CONVERSION ERROR);
+ int4 
+------
+     
+(1 row)
+
+-- The DEFAULT expression cannot contain set expression
+SELECT CAST('a' as int DEFAULT generate_series(1, 1) ON CONVERSION ERROR);
+ERROR:  set-returning functions are not allowed in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT generate_series(1, 1) ON CONV...
+                                       ^
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ column1 
+---------
+ error
+(1 row)
+
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR);
+ERROR:  invalid input syntax for type integer: "b"
+LINE 1: SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR);
+                                       ^
+SELECT CAST(1 AS int DEFAULT 70000::int2::int4 ON CONVERSION ERROR);
+ERROR:  smallint out of range
+SELECT CAST('70000' AS int2 DEFAULT -1 ON CONVERSION ERROR);
+ int2 
+------
+   -1
+(1 row)
+
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+ char 
+------
+ A
+(1 row)
+
+SELECT CAST('def'::text AS integer DEFAULT NULL ON CONVERSION ERROR);
+ int4 
+------
+     
+(1 row)
+
+SELECT CAST('2'::jsonb AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+ text 
+------
+ 2
+(1 row)
+
+SELECT CAST('1' COLLATE "C" || 'h' AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+ text 
+------
+ 1h
+(1 row)
+
+SELECT CAST(concat('1' collate "C", 'h') AS int DEFAULT NULL ON CONVERSION ERROR);
+ concat 
+--------
+       
+(1 row)
+
+SELECT CAST(65536 AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2 
+------
+     
+(1 row)
+
+SELECT CAST(int8(65536) AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int8 
+------
+     
+(1 row)
+
+SELECT CAST(12.111 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale;
+ to_numeric_scale 
+------------------
+             12.1
+(1 row)
+
+SELECT CAST('\x01'::bytea as uuid DEFAULT NULL ON CONVERSION ERROR);
+ uuid 
+------
+ 
+(1 row)
+
+SELECT CAST(CAST('{2020-01-01}'::text[] AS date[]) AS int[] DEFAULT '{7}' ON CONVERSION ERROR);
+ int4 
+------
+ {7}
+(1 row)
+
+SELECT (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ int2vector 
+------------
+ 1 3
+(1 row)
+
+SELECT (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ int2vector 
+------------
+ {"1 3"}
+(1 row)
+
+SELECT CAST ('b' AS integer
+            DEFAULT CAST('c' AS INTEGER DEFAULT 0 ON CONVERSION ERROR)
+            ON CONVERSION ERROR);
+ int4 
+------
+    0
+(1 row)
+
+-- source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+            AS int2vector[] DEFAULT '{1 3}'  ON CONVERSION ERROR);
+    b    
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+            AS INT[]
+            DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS $$ BEGIN RETURN 2147483648; END; $$ LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); -- error
+ERROR:  integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); -- error
+ERROR:  cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+                                        ^
+-- Test JIT: run the same cast with JIT forced on, then interpreted
+SET jit_above_cost = 0;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (65536)) t(a);
+ a 
+---
+ 1
+  
+(2 rows)
+
+RESET jit_above_cost;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (65536)) t(a);
+ a 
+---
+ 1
+  
+(2 rows)
+
+CREATE TABLE tcast1(a int DEFAULT CAST('x' AS int DEFAULT a ON CONVERSION ERROR)); -- error
+ERROR:  cannot use column reference in DEFAULT expression
+LINE 1: ...ABLE tcast1(a int DEFAULT CAST('x' AS int DEFAULT a ON CONVE...
+                                                             ^
+CREATE DOMAIN d_nota AS text CHECK (VALUE <> 'a');
+CREATE TABLE tcast1(a text, b text[], c int, d int8, e int8[]);
+ALTER TABLE tcast1 ADD COLUMN f int
+  DEFAULT CAST('x' AS int DEFAULT a ON CONVERSION ERROR); -- error
+ERROR:  cannot use column reference in DEFAULT expression
+INSERT INTO tcast1(a, b, d, e) values('a', '{a}', 2147483648, '{2147483648}');
+-- domain constraints must not be checked once the conversion to the base type
+-- has failed softly
+CREATE DOMAIN dint AS int CHECK ((1 / coalesce(VALUE, 0)) IS NOT NULL);
+CREATE DOMAIN dintnn AS int NOT NULL CHECK ((1 / coalesce(VALUE, 0)) IS NOT NULL);
+SELECT CAST(concat('a') AS dint DEFAULT 2 ON CONVERSION ERROR);
+ concat 
+--------
+      2
+(1 row)
+
+SELECT CAST(nullif(concat('a'), 'a') AS dintnn DEFAULT 2 ON CONVERSION ERROR);
+ nullif 
+--------
+      2
+(1 row)
+
+SELECT CAST(ARRAY[concat('a')] AS dint[] DEFAULT '{2}' ON CONVERSION ERROR);
+ array 
+-------
+ {2}
+(1 row)
+
+SELECT CAST(concat('{a}')::text[] AS dint[] DEFAULT '{2}' ON CONVERSION ERROR);
+ concat 
+--------
+ {2}
+(1 row)
+
+SELECT CAST(concat('0') AS dint DEFAULT 2 ON CONVERSION ERROR);
+ERROR:  division by zero
+-- Only the conversion itself is evaluated in an error-safe manner.  The source
+-- expression is evaluated normally, so errors raised inside it are not caught.
+-- none of the following should be accepted
+SELECT CAST (CAST(a AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ERROR:  invalid input syntax for type integer: "a"
+SELECT CAST (CAST(a AS d_nota) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ERROR:  value for domain d_nota violates check constraint "d_nota_check"
+SELECT CAST (CAST(b AS d_nota[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ERROR:  value for domain d_nota violates check constraint "d_nota_check"
+SELECT CAST (CAST(b AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ERROR:  invalid input syntax for type integer: "a"
+SELECT CAST (CAST(d AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ERROR:  integer out of range
+SELECT CAST(ARRAY[a::int, concat('x')] AS int[] DEFAULT '{0}' ON CONVERSION ERROR) FROM tcast1;
+ERROR:  invalid input syntax for type integer: "a"
+SELECT CAST(ARRAY['x', a::int] AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ERROR:  invalid input syntax for type integer: "a"
+SELECT CAST(ARRAY[a::int] AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ERROR:  invalid input syntax for type integer: "a"
+SELECT CAST(ARRAY['x', a::int] AS text DEFAULT '{0}' ON CONVERSION ERROR) FROM tcast1;
+ERROR:  invalid input syntax for type integer: "x"
+LINE 1: SELECT CAST(ARRAY['x', a::int] AS text DEFAULT '{0}' ON CONV...
+                          ^
+SELECT CAST(ARRAY['x', a::int] AS date DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ERROR:  invalid input syntax for type integer: "x"
+LINE 1: SELECT CAST(ARRAY['x', a::int] AS date DEFAULT NULL ON CONVE...
+                          ^
+SELECT CAST (CAST('{a}'::text[] AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ERROR:  invalid input syntax for type integer: "a"
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+ERROR:  invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+                                       ^
+SELECT CAST('70000'::int2 AS int2 DEFAULT -1 ON CONVERSION ERROR);
+ERROR:  value "70000" is out of range for type smallint
+LINE 1: SELECT CAST('70000'::int2 AS int2 DEFAULT -1 ON CONVERSION E...
+                    ^
+SELECT CAST(1 + '70000'::int2::int4 AS int DEFAULT -1 ON CONVERSION ERROR);
+ERROR:  value "70000" is out of range for type smallint
+LINE 1: SELECT CAST(1 + '70000'::int2::int4 AS int DEFAULT -1 ON CON...
+                        ^
+SELECT CAST('\x01'::bytea::uuid AS text DEFAULT 'x1' ON CONVERSION ERROR);
+ERROR:  invalid input length for type uuid
+DETAIL:  Expected 16 bytes, got 1.
+SELECT CAST(ARRAY[int4pl(1, 'x')] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  invalid input syntax for type integer: "x"
+LINE 1: SELECT CAST(ARRAY[int4pl(1, 'x')] AS int[] DEFAULT NULL ON C...
+                                    ^
+SELECT CAST(ARRAY[1 + 'x'] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  invalid input syntax for type integer: "x"
+LINE 1: SELECT CAST(ARRAY[1 + 'x'] AS int[] DEFAULT NULL ON CONVERSI...
+                              ^
+-- none of the above should be accepted
+-- Test ARRAY constructor and target type is ARRAY type
+SELECT CAST(ARRAY[e] as int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ array 
+-------
+ 
+(1 row)
+
+-- all of the following should be accepted. For an ARRAY[] argument, each
+-- element is converted to the target element type
+SELECT CAST(ARRAY['x', a::int] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ array 
+-------
+ 
+(1 row)
+
+SELECT CAST(ARRAY[1,2] || 3 AS boolean DEFAULT false ON CONVERSION ERROR);
+ bool 
+------
+ f
+(1 row)
+
+SELECT CAST(ARRAY[] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ {}
+(1 row)
+
+SELECT CAST(ARRAY[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
+SELECT CAST(ARRAY[1, 'x'] AS point[] DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
+SELECT CAST(ARRAY[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
+SELECT CAST(ARRAY[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
+SELECT CAST(ARRAY['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array 
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+  array  
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(e as int4[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ e 
+---
+ 
+(1 row)
+
+SELECT CAST('{123,abc,def}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+  int4  
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+  int4   
+---------
+ {-1011}
+(1 row)
+
+-- all of the above should be accepted
+-- test with user-defined type, domain, ARRAY over domain, domain over ARRAY
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_int42arr as d_int42[];
+CREATE DOMAIN d_int42arr1 as d_int42arr;
+CREATE DOMAIN d_int8 as int8 check (value > 0);
+CREATE DOMAIN d_int8arr as d_int8[];
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+CREATE DOMAIN d_numeric as numeric(4,4);
+SELECT CAST(1.0::float4 AS d_numeric DEFAULT NULL ON CONVERSION ERROR);
+ d_numeric 
+-----------
+          
+(1 row)
+
+SELECT CAST('{1.0}' AS numeric(4,1)[] DEFAULT NULL ON CONVERSION ERROR);
+ numeric 
+---------
+ {1.0}
+(1 row)
+
+SELECT CAST('{0.1, 1.0}' AS d_numeric[] DEFAULT NULL ON CONVERSION ERROR);
+ d_numeric 
+-----------
+ 
+(1 row)
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); -- error
+ERROR:  value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR:  domain d_int42 does not allow null values
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+ d_int42 
+---------
+      42
+(1 row)
+
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+ d_int42 
+---------
+      42
+(1 row)
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8 DEFAULT '1' ON CONVERSION ERROR);
+ array 
+-------
+     1
+(1 row)
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8[] DEFAULT '{1}' ON CONVERSION ERROR);
+ array 
+-------
+ {42}
+(1 row)
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr DEFAULT '{1}' ON CONVERSION ERROR);
+ array 
+-------
+ {42}
+(1 row)
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr[] DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+  array  
+---------
+ {42,42}
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+  array  
+---------
+ {41,43}
+(1 row)
+
+-- error. ARRAY[42, 41]::d_int42arr1 is the source expression, so it is not
+-- evaluated in an error-safe manner
+SELECT CAST(ARRAY[42, 41]::d_int42arr1 AS d_int8arr
+        DEFAULT '{1,2,3}' ON CONVERSION ERROR);
+ERROR:  value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(ARRAY[42, 41] AS d_int8arr DEFAULT '{1,2,3}' ON CONVERSION ERROR);
+  array  
+---------
+ {42,41}
+(1 row)
+
+SELECT CAST(ARRAY[42, 41] AS d_int42arr1 DEFAULT '{42, 42}' ON CONVERSION ERROR);
+  array  
+---------
+ {42,42}
+(1 row)
+
+SELECT CAST(CAST(ARRAY[CAST(ARRAY[42, 42] AS d_int42arr)] AS d_int42arr1[])
+            AS d_int8arr[]
+            DEFAULT NULL ON CONVERSION ERROR);
+    array    
+-------------
+ {"{42,42}"}
+(1 row)
+
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); -- error
+ERROR:  value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+                                              ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+ comp2 
+-------
+ (123)
+(1 row)
+
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+ comp_domain_with_typmod 
+-------------------------
+ 
+(1 row)
+
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+ comp_domain_with_typmod 
+-------------------------
+ ("1  ",2)
+(1 row)
+
+SELECT CAST(A AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR)
+FROM (VALUES('(NULL,42)'::text), ('(,42)')) sub(a);
+     a     
+-----------
+ ("1  ",2)
+ ("1  ",2)
+(2 rows)
+
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); -- error
+ERROR:  value too long for type character(3)
+LINE 1: ...ST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)'...
+                                                             ^
+SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR:  cannot cast type record to comp_domain_with_typmod with DEFAULT expression in CAST ... ON CONVERSION ERROR
+DETAIL:  Casts between row types are not supported here.
+-- safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+     box     
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+     point      
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+       point       
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon 
+---------
+ 
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+     point      
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+     lseg      
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+          polygon          
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path 
+------
+ 
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+        circle        
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+     point      
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+        path         
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+     box     
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+        circle        
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point 
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+     box     
+-------------
+ (3,5),(3,5)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon 
+---------
+ 
+(1 row)
+
+-- safe cast with money data type
+-- cast from money to other type is not error safe
+SELECT CAST('123456789012345678'::numeric AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money 
+-------
+      
+(1 row)
+
+SELECT CAST('123456789012345678'::int8 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money 
+-------
+      
+(1 row)
+
+SELECT CAST('2147483647'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+       money       
+-------------------
+ $2,147,483,647.00
+(1 row)
+
+SELECT CAST('-2147483648'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+       money        
+--------------------
+ -$2,147,483,648.00
+(1 row)
+
+SELECT CAST('-0'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money 
+-------
+ $0.00
+(1 row)
+
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  cannot cast type money to numeric with DEFAULT expression in CAST ... ON CONVERSION ERROR
+DETAIL:  Explicit cast is defined but definition is not error safe.
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  cannot cast type money to numeric with DEFAULT expression in CAST ... ON CONVERSION ERROR
+DETAIL:  Explicit cast is defined but definition is not error safe.
+-- safe cast from boolean type to other data types
+SELECT CAST(true AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ numeric 
+---------
+        
+(1 row)
+
+SELECT CAST(false AS int DEFAULT NULL ON CONVERSION ERROR);
+ int4 
+------
+    0
+(1 row)
+
+SELECT CAST(false AS text DEFAULT NULL ON CONVERSION ERROR);
+ text  
+-------
+ false
+(1 row)
+
+-- safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+ int8 
+------
+   19
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+ int4 
+------
+   20
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+ int2 
+------
+   21
+(1 row)
+
+SELECT CAST('\x1234567890abcdef'::bytea AS uuid DEFAULT NULL ON CONVERSION ERROR);
+ uuid 
+------
+ 
+(1 row)
+
+-- safe cast from uuid type to other data types
+SELECT CAST('5b35380a-7143-4912-9b55-f322699c6770'::uuid AS bytea DEFAULT NULL ON CONVERSION ERROR);
+               bytea                
+------------------------------------
+ \x5b35380a714349129b55f322699c6770
+(1 row)
+
+-- safe cast related to network address data type
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet 
+------
+ 
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr 
+------
+ 
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr 
+---------
+ 
+(1 row)
+
+-- safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+ int4 
+------
+   22
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+ int8 
+------
+   23
+(1 row)
+
+-- safe cast related to oid data type
+SELECT CAST(12345678901::bigint AS oid DEFAULT NULL ON CONVERSION ERROR);
+ oid 
+-----
+    
+(1 row)
+
+SELECT CAST(12345678901::bigint AS regproc DEFAULT NULL ON CONVERSION ERROR);
+ regproc 
+---------
+ 
+(1 row)
+
+SELECT CAST(12345678901::bigint AS regprocedure DEFAULT NULL ON CONVERSION ERROR);
+ regprocedure 
+--------------
+ 
+(1 row)
+
+SELECT CAST(12345678901::bigint AS regclass DEFAULT NULL ON CONVERSION ERROR);
+ regclass 
+----------
+ 
+(1 row)
+
+SELECT CAST(1234567890::bigint AS oid DEFAULT NULL ON CONVERSION ERROR);
+    oid     
+------------
+ 1234567890
+(1 row)
+
+SELECT CAST(1234567890::bigint AS regrole DEFAULT NULL ON CONVERSION ERROR);
+  regrole   
+------------
+ 1234567890
+(1 row)
+
+SELECT CAST(1234567890::bigint AS regnamespace DEFAULT NULL ON CONVERSION ERROR);
+ regnamespace 
+--------------
+ 1234567890
+(1 row)
+
+SELECT CAST(1234567890::bigint AS regdatabase DEFAULT NULL ON CONVERSION ERROR);
+ regdatabase 
+-------------
+ 1234567890
+(1 row)
+
+-- safe cast from text type to other data types
+SELECT CAST('pg_temp_1.foo'::text AS regclass DEFAULT NULL ON CONVERSION ERROR);
+ regclass 
+----------
+ 
+(1 row)
+
+SELECT CAST('a.b.c.d'::text as regclass DEFAULT NULL ON CONVERSION ERROR);
+ regclass 
+----------
+ 
+(1 row)
+
+CREATE TABLE test_safecast0(col0 text, col1 jsonb);
+INSERT INTO test_safecast0 VALUES ('<value>one</value', '"test"');
+SELECT col0 as text,
+       CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+       CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+       CAST(col0 AS "char"  DEFAULT NULL ON CONVERSION ERROR) as to_char,
+       CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name
+FROM test_safecast0;
+       text        | to_regclass | expect_true | to_char |      to_name      
+-------------------+-------------+-------------+---------+-------------------
+ <value>one</value |             | t           | <       | <value>one</value
+(1 row)
+
+-- safe type cast for jsonb data type
+SELECT col1 as jsonb,
+       CAST(col1 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+       CAST(col1 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col1 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col1 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col1 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col1 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+       CAST(col1 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast0;
+ jsonb  | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint 
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" |            |            |         |           |           |         |            
+(1 row)
+
+-- test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as ...
+                                                      ^
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as xid DEFAULT NULL ON CONVERSION ERROR))); -- error
+ERROR:  data type xid has no default operator class for access method "btree"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT NULL ON CONVERSION ERROR))); -- ok
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT (SELECT NULL) ON CONVERSION ERROR))); -- error
+ERROR:  cannot use subquery in index expression
+LINE 1: ...r_idx ON test_safecast0((CAST(col1 as int DEFAULT (SELECT NU...
+                                                             ^
+SELECT pg_get_indexdef('cast_error_idx'::regclass);
+                                                           pg_get_indexdef                                                            
+--------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX cast_error_idx ON public.test_safecast0 USING btree ((CAST(col1 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+-- safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange 
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange 
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange  AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange 
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+     datemultirange     
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+      tsmultirange      
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+     tstzmultirange     
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-- safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+  col1 float4, col2 float8, col3 numeric, col4 numeric[],
+  col5 int2 default 32767,
+  col6 int4 default 32768,
+  col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+       CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2  | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale 
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 |   32767 |   32767 |  32767 |   32767 |     32767 |     32767 |      32767 |          32767.0
+ 32767 |   32767 |   32767 |  32767 |   32767 |     32767 |     32767 |      32767 |          32767.0
+ 32767 |   32767 |   32767 |  32767 |   32767 |     32767 |     32767 |      32767 |          32767.0
+ 32767 |   32767 |   32767 |  32767 |   32767 |     32767 |     32767 |      32767 |          32767.0
+(4 rows)
+
+SELECT col6 as int4,
+       CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4  | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale 
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 |         |   32768 |  32768 |   32768 |     32768 |     32768 |      32768 |          32768.0
+ 32768 |         |   32768 |  32768 |   32768 |     32768 |     32768 |      32768 |          32768.0
+ 32768 |         |   32768 |  32768 |   32768 |     32768 |     32768 |      32768 |          32768.0
+ 32768 |         |   32768 |  32768 |   32768 |     32768 |     32768 |      32768 |          32768.0
+(4 rows)
+
+SELECT col7 as int8,
+       CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+    int8    | to_int2 | to_int4 | to_oid |  to_int8   |  to_float4  | to_float8  | to_numeric | to_numeric_scale 
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 |         |         |        | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |                 
+ 4294967296 |         |         |        | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |                 
+ 4294967296 |         |         |        | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |                 
+ 4294967296 |         |         |        | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |                 
+(4 rows)
+
+SELECT col3 as numeric,
+       CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+       numeric       | to_int2 | to_int4 | to_oid | to_int8 |  to_float4  |      to_float8       |     to_numeric      | to_numeric_scale 
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 |         |         |        |         | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |                 
+            Infinity |         |         |        |         |    Infinity |             Infinity |            Infinity |                 
+           -Infinity |         |         |        |         |   -Infinity |            -Infinity |           -Infinity |                 
+                 NaN |         |         |        |         |         NaN |                  NaN |                 NaN |              NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+       CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+       CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+       CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+       CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+       CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+       CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+          num_arr           | int2arr | int4arr | int8arr |           f4arr            |           f8arr            | numarr 
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234}                  | {11}    | {11}    | {11}    | {11.1234}                  | {11.1234}                  | {11.1}
+ {11.1234,12,Infinity,NaN}  |         |         |         | {11.1234,12,Infinity,NaN}  | {11.1234,12,Infinity,NaN}  | 
+ {11.1234,12,-Infinity,NaN} |         |         |         | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} | 
+ {11.1234,12,-Infinity,NaN} |         |         |         | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} | 
+(4 rows)
+
+SELECT col1 as float4,
+       CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+  float4   | to_int2 | to_int4 | to_oid | to_int8 | to_float4 |    to_float8     | to_numeric | to_numeric_scale 
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+   11.1234 |      11 |      11 |        |      11 |   11.1234 | 11.1233997344971 |    11.1234 |             11.1
+  Infinity |         |         |        |         |  Infinity |         Infinity |   Infinity |                 
+ -Infinity |         |         |        |         | -Infinity |        -Infinity |  -Infinity |                 
+       NaN |         |         |        |         |       NaN |              NaN |        NaN |              NaN
+(4 rows)
+
+SELECT col2 as float8,
+       CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+  float8   | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale 
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+   11.1234 |      11 |      11 |        |      11 |   11.1234 |   11.1234 |    11.1234 |             11.1
+  Infinity |         |         |        |         |  Infinity |  Infinity |   Infinity |                 
+ -Infinity |         |         |        |         | -Infinity | -Infinity |  -Infinity |                 
+       NaN |         |         |        |         |       NaN |       NaN |        NaN |              NaN
+(4 rows)
+
+-- safe type cast for type date/timestamp/timestamptz/interval
+CREATE TABLE test_safecast2(
+  col0 date, col1 timestamp, col2 timestamptz,
+  col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+  '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity', '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+       CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+   date    | to_timestamptz |  to_date  | to_times | to_timetz | to_timestamp_scale 
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity      | -infinity |          |           | -infinity
+ -infinity | -infinity      | -infinity |          |           | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+       CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz |  to_date  | to_times | to_timetz | to_timestamp_scale 
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity      | -infinity |          |           | -infinity
+ infinity  | infinity       | infinity  |          |           | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+       CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz |  to_date  | to_times | to_timetz | to_timestamp_scale 
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity   | -infinity      | -infinity |          |           | -infinity
+ infinity    | infinity       | infinity  |          |           | infinity
+(2 rows)
+
+SELECT col3 as interval,
+       CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+       CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval  | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale 
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity |                |         |          |           |                    | -infinity
+ infinity  |                |         |          |           |                    | infinity
+(2 rows)
+
+SELECT col4 as time,
+       CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+       CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+       CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+    time     |  to_times   | to_timetz |          to_interval          |       to_interval_scale       
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39    | 15:36:39    | t         | @ 15 hours 36 mins 39 secs    | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t         | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+       CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+       CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+       CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+       CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+       CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+     timetz     |   to_time   | to_time_scale |   to_timetz    | to_timetz_scale | to_interval | to_interval_scale 
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04    | 15:36:39    | 15:36:39      | 15:36:39-04    | 15:36:39-04     |             | 
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99   | 23:59:59.99-07 | 23:59:59.99-07  |             | 
+(2 rows)
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT
+      CAST('2022-01-01' COLLATE "C" COLLATE "POSIX" as DATE DEFAULT NULL ON CONVERSION ERROR) as cast,
+      CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR) as cast0,
+      CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast1,
+      CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast2,
+      CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast3,
+      CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast4,
+      CAST(CAST(1 AS int DEFAULT 0 ON CONVERSION ERROR) + 70000::int4 AS int DEFAULT -1 ON CONVERSION ERROR) as cast5;
+\sv safecastview
+CREATE OR REPLACE VIEW public.safecastview AS
+ SELECT CAST('2022-01-01'::date AS date DEFAULT NULL::date ON CONVERSION ERROR) AS "cast",
+    CAST('1234'::bpchar AS character(3) DEFAULT '-1111'::integer::character(3) ON CONVERSION ERROR) AS cast0,
+    CAST(1 AS date DEFAULT '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast1,
+    CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast2,
+    CAST(ARRAY[ARRAY['1'::text, '2'::text], ARRAY['three'::text, 'a'::text]] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast3,
+    CAST(ARRAY['three'::text] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast4,
+    CAST((CAST(1 AS integer DEFAULT 0 ON CONVERSION ERROR)) + 70000 AS integer DEFAULT '-1'::integer ON CONVERSION ERROR) AS cast5
+SELECT * FROM safecastview;
+    cast    | cast0 |   cast1    | cast2 | cast3 | cast4 | cast5 
+------------+-------+------------+-------+-------+-------+-------
+ 2022-01-01 | 123   | 2025-12-07 | {1,2} |       | {1,2} | 70001
+(1 row)
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+            DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+            DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2,
+       CAST('a'::text AS int DEFAULT 3 ON CONVERSION ERROR) AS cast3;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+    CAST(ARRAY[ARRAY[1, 2, 1.1], ARRAY['three'::text, true, '01'::"bit"]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2,
+    CAST('a'::text AS integer DEFAULT 3 ON CONVERSION ERROR) AS cast3
+SELECT * FROM safecastview1;
+  cast1  |  cast2  | cast3 
+---------+---------+-------
+ {41,43} | {41,43} |     3
+(1 row)
+
+CREATE VIEW safecastview2 AS
+SELECT CAST(ARRAY[b] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM (values ('1'), ('a'::text)) t(b);
+\sv safecastview2
+CREATE OR REPLACE VIEW public.safecastview2 AS
+ SELECT CAST(ARRAY[b] AS integer[] DEFAULT NULL::integer[] ON CONVERSION ERROR) AS "array"
+   FROM ( VALUES ('1'::text), ('a'::text)) t(b)
+SELECT * FROM safecastview2;
+ array 
+-------
+ {1}
+ 
+(2 rows)
+
+RESET datestyle;
+DROP VIEW safecastview;
+DROP VIEW safecastview1;
+DROP TABLE test_safecast0;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE tcast1;
+DROP FUNCTION ret_int8;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_nota;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int8arr;
+DROP DOMAIN d_int8;
+DROP DOMAIN d_int42arr1;
+DROP DOMAIN d_int42arr;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+DROP DOMAIN d_numeric;
+DROP DOMAIN dint;
+DROP DOMAIN dintnn;
+RESET extra_float_digits;
+RESET lc_monetary;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..c9e26912caf 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,16 @@ SELECT 1234::int4::casttesttype; -- Should work now
  bar1234
 (1 row)
 
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR:  cannot cast type integer to casttesttype with DEFAULT expression in CAST ... ON CONVERSION ERROR
+DETAIL:  Explicit cast is defined but definition is not error safe.
+ALTER FUNCTION bar_int4_text(int4) ERROR SAFE;
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- ok
+ casttesttype 
+--------------
+ bar1234
+(1 row)
+
 -- check dependencies generated for that
 SELECT pg_describe_object(classid, objid, objsubid) as obj,
        pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index ad8ab294ff6..3af737c220a 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -95,6 +95,25 @@ create function int8alias1cmp(int8, int8alias1) returns int
   strict immutable language internal as 'btint8cmp';
 alter operator family integer_ops using btree add
   function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8, this should not fail
+select cast('1'::int8 as int8alias2 default null on conversion error);
+ int8alias2 
+------------
+ 1
+(1 row)
+
+select cast('1'::int8alias2 as int8 default null on conversion error);
+ int8 
+------
+    1
+(1 row)
+
+select cast('a' as int8alias2 default null on conversion error);
+ int8alias2 
+------------
+ 
+(1 row)
+
 create table ec0 (ff int8 primary key, f1 int8, f2 int8);
 create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
 create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 6998745ee03..2855f88317f 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -24,6 +24,12 @@ SELECT * FROM xmltest;
   2 | <value>two</value>
 (2 rows)
 
+SELECT CAST('<wrong'::text AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml;
+ to_xml 
+--------
+ 
+(1 row)
+
 -- test non-throwing API, too
 SELECT pg_input_is_valid('<value>one</value>', 'xml');
  pg_input_is_valid 
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 75063f87a4a..f37516c1af7 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -115,7 +115,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
 # NB: temp.sql does reconnects which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml cast
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/sql/cast.sql b/src/test/regress/sql/cast.sql
new file mode 100644
index 00000000000..35c3bddd325
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,461 @@
+SET extra_float_digits = 0;
+SET lc_monetary TO "C";
+
+-- CAST DEFAULT ON CONVERSION ERROR
+VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); -- error
+
+-- The DEFAULT expression can contain aggreagte/window function or subquery
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR);
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR);
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR);
+SELECT CAST('a' as int DEFAULT (SELECT 1 where false) ON CONVERSION ERROR);
+
+-- The DEFAULT expression cannot contain set expression
+SELECT CAST('a' as int DEFAULT generate_series(1, 1) ON CONVERSION ERROR);
+
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR);
+SELECT CAST(1 AS int DEFAULT 70000::int2::int4 ON CONVERSION ERROR);
+SELECT CAST('70000' AS int2 DEFAULT -1 ON CONVERSION ERROR);
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+SELECT CAST('def'::text AS integer DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('2'::jsonb AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+SELECT CAST('1' COLLATE "C" || 'h' AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+SELECT CAST(concat('1' collate "C", 'h') AS int DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(65536 AS int2 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(int8(65536) AS int2 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(12.111 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale;
+SELECT CAST('\x01'::bytea as uuid DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(CAST('{2020-01-01}'::text[] AS date[]) AS int[] DEFAULT '{7}' ON CONVERSION ERROR);
+SELECT (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+SELECT (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+SELECT CAST ('b' AS integer
+            DEFAULT CAST('c' AS INTEGER DEFAULT 0 ON CONVERSION ERROR)
+            ON CONVERSION ERROR);
+
+-- source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+            AS int2vector[] DEFAULT '{1 3}'  ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+            AS INT[]
+            DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS $$ BEGIN RETURN 2147483648; END; $$ LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); -- error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); -- error
+
+-- Test JIT: run the same cast with JIT forced on, then interpreted
+SET jit_above_cost = 0;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (65536)) t(a);
+RESET jit_above_cost;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (65536)) t(a);
+
+CREATE TABLE tcast1(a int DEFAULT CAST('x' AS int DEFAULT a ON CONVERSION ERROR)); -- error
+CREATE DOMAIN d_nota AS text CHECK (VALUE <> 'a');
+CREATE TABLE tcast1(a text, b text[], c int, d int8, e int8[]);
+ALTER TABLE tcast1 ADD COLUMN f int
+  DEFAULT CAST('x' AS int DEFAULT a ON CONVERSION ERROR); -- error
+INSERT INTO tcast1(a, b, d, e) values('a', '{a}', 2147483648, '{2147483648}');
+
+-- domain constraints must not be checked once the conversion to the base type
+-- has failed softly
+CREATE DOMAIN dint AS int CHECK ((1 / coalesce(VALUE, 0)) IS NOT NULL);
+CREATE DOMAIN dintnn AS int NOT NULL CHECK ((1 / coalesce(VALUE, 0)) IS NOT NULL);
+SELECT CAST(concat('a') AS dint DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST(nullif(concat('a'), 'a') AS dintnn DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST(ARRAY[concat('a')] AS dint[] DEFAULT '{2}' ON CONVERSION ERROR);
+SELECT CAST(concat('{a}')::text[] AS dint[] DEFAULT '{2}' ON CONVERSION ERROR);
+SELECT CAST(concat('0') AS dint DEFAULT 2 ON CONVERSION ERROR);
+
+-- Only the conversion itself is evaluated in an error-safe manner.  The source
+-- expression is evaluated normally, so errors raised inside it are not caught.
+
+-- none of the following should be accepted
+SELECT CAST (CAST(a AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST(a AS d_nota) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST(b AS d_nota[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST(b AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST(d AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(ARRAY[a::int, concat('x')] AS int[] DEFAULT '{0}' ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(ARRAY['x', a::int] AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(ARRAY[a::int] AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(ARRAY['x', a::int] AS text DEFAULT '{0}' ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(ARRAY['x', a::int] AS date DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST('{a}'::text[] AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+SELECT CAST('70000'::int2 AS int2 DEFAULT -1 ON CONVERSION ERROR);
+SELECT CAST(1 + '70000'::int2::int4 AS int DEFAULT -1 ON CONVERSION ERROR);
+SELECT CAST('\x01'::bytea::uuid AS text DEFAULT 'x1' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[int4pl(1, 'x')] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(ARRAY[1 + 'x'] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+-- none of the above should be accepted
+
+-- Test ARRAY constructor and target type is ARRAY type
+SELECT CAST(ARRAY[e] as int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+
+-- all of the following should be accepted. For an ARRAY[] argument, each
+-- element is converted to the target element type
+SELECT CAST(ARRAY['x', a::int] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(ARRAY[1,2] || 3 AS boolean DEFAULT false ON CONVERSION ERROR);
+SELECT CAST(ARRAY[] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(ARRAY[1, 'x'] AS point[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(ARRAY[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(ARRAY[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(ARRAY['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+SELECT CAST(e as int4[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST('{123,abc,def}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+-- all of the above should be accepted
+
+-- test with user-defined type, domain, ARRAY over domain, domain over ARRAY
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_int42arr as d_int42[];
+CREATE DOMAIN d_int42arr1 as d_int42arr;
+CREATE DOMAIN d_int8 as int8 check (value > 0);
+CREATE DOMAIN d_int8arr as d_int8[];
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+CREATE DOMAIN d_numeric as numeric(4,4);
+
+SELECT CAST(1.0::float4 AS d_numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{1.0}' AS numeric(4,1)[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{0.1, 1.0}' AS d_numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); -- error
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); -- error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8 DEFAULT '1' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8[] DEFAULT '{1}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr DEFAULT '{1}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr[] DEFAULT NULL ON CONVERSION ERROR);
+
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+-- error. ARRAY[42, 41]::d_int42arr1 is the source expression, so it is not
+-- evaluated in an error-safe manner
+SELECT CAST(ARRAY[42, 41]::d_int42arr1 AS d_int8arr
+        DEFAULT '{1,2,3}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42, 41] AS d_int8arr DEFAULT '{1,2,3}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42, 41] AS d_int42arr1 DEFAULT '{42, 42}' ON CONVERSION ERROR);
+SELECT CAST(CAST(ARRAY[CAST(ARRAY[42, 42] AS d_int42arr)] AS d_int42arr1[])
+            AS d_int8arr[]
+            DEFAULT NULL ON CONVERSION ERROR);
+
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); -- error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+SELECT CAST(A AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR)
+FROM (VALUES('(NULL,42)'::text), ('(,42)')) sub(a);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); -- error
+SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); -- error
+
+-- safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast with money data type
+-- cast from money to other type is not error safe
+SELECT CAST('123456789012345678'::numeric AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('123456789012345678'::int8 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('2147483647'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('-2147483648'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('-0'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast from boolean type to other data types
+SELECT CAST(true AS numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(false AS int DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(false AS text DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+SELECT CAST('\x1234567890abcdef'::bytea AS uuid DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast from uuid type to other data types
+SELECT CAST('5b35380a-7143-4912-9b55-f322699c6770'::uuid AS bytea DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast related to network address data type
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+
+-- safe cast related to oid data type
+SELECT CAST(12345678901::bigint AS oid DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(12345678901::bigint AS regproc DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(12345678901::bigint AS regprocedure DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(12345678901::bigint AS regclass DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1234567890::bigint AS oid DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1234567890::bigint AS regrole DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1234567890::bigint AS regnamespace DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1234567890::bigint AS regdatabase DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast from text type to other data types
+SELECT CAST('pg_temp_1.foo'::text AS regclass DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('a.b.c.d'::text as regclass DEFAULT NULL ON CONVERSION ERROR);
+CREATE TABLE test_safecast0(col0 text, col1 jsonb);
+INSERT INTO test_safecast0 VALUES ('<value>one</value', '"test"');
+
+SELECT col0 as text,
+       CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+       CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+       CAST(col0 AS "char"  DEFAULT NULL ON CONVERSION ERROR) as to_char,
+       CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name
+FROM test_safecast0;
+
+-- safe type cast for jsonb data type
+SELECT col1 as jsonb,
+       CAST(col1 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+       CAST(col1 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col1 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col1 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col1 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col1 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+       CAST(col1 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast0;
+
+-- test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); -- error
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as xid DEFAULT NULL ON CONVERSION ERROR))); -- error
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT NULL ON CONVERSION ERROR))); -- ok
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT (SELECT NULL) ON CONVERSION ERROR))); -- error
+SELECT pg_get_indexdef('cast_error_idx'::regclass);
+
+-- safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange  AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+  col1 float4, col2 float8, col3 numeric, col4 numeric[],
+  col5 int2 default 32767,
+  col6 int4 default 32768,
+  col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+       CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+       CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+       CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+       CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+       CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+       CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+       CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+       CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+       CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+       CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+       CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+       CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- safe type cast for type date/timestamp/timestamptz/interval
+CREATE TABLE test_safecast2(
+  col0 date, col1 timestamp, col2 timestamptz,
+  col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+  '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity', '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+       CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+       CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+       CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+       CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+       CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+       CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+       CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+       CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+       CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+       CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+       CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+       CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+       CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT
+      CAST('2022-01-01' COLLATE "C" COLLATE "POSIX" as DATE DEFAULT NULL ON CONVERSION ERROR) as cast,
+      CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR) as cast0,
+      CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast1,
+      CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast2,
+      CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast3,
+      CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast4,
+      CAST(CAST(1 AS int DEFAULT 0 ON CONVERSION ERROR) + 70000::int4 AS int DEFAULT -1 ON CONVERSION ERROR) as cast5;
+\sv safecastview
+SELECT * FROM safecastview;
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+            DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+            DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2,
+       CAST('a'::text AS int DEFAULT 3 ON CONVERSION ERROR) AS cast3;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+CREATE VIEW safecastview2 AS
+SELECT CAST(ARRAY[b] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM (values ('1'), ('a'::text)) t(b);
+\sv safecastview2
+SELECT * FROM safecastview2;
+
+RESET datestyle;
+
+DROP VIEW safecastview;
+DROP VIEW safecastview1;
+DROP TABLE test_safecast0;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE tcast1;
+DROP FUNCTION ret_int8;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_nota;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int8arr;
+DROP DOMAIN d_int8;
+DROP DOMAIN d_int42arr1;
+DROP DOMAIN d_int42arr;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+DROP DOMAIN d_numeric;
+DROP DOMAIN dint;
+DROP DOMAIN dintnn;
+RESET extra_float_digits;
+RESET lc_monetary;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..88377a7b467 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,9 @@ $$ SELECT ('bar'::text || $1::text); $$;
 
 CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
 SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ALTER FUNCTION bar_int4_text(int4) ERROR SAFE;
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- ok
 
 -- check dependencies generated for that
 SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 7fc2159349b..96e6f73c7b6 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -98,6 +98,11 @@ create function int8alias1cmp(int8, int8alias1) returns int
 alter operator family integer_ops using btree add
   function 1 int8alias1cmp (int8, int8alias1);
 
+-- int8alias2 binary-coercible to int8, this should not fail
+select cast('1'::int8 as int8alias2 default null on conversion error);
+select cast('1'::int8alias2 as int8 default null on conversion error);
+select cast('a' as int8alias2 default null on conversion error);
+
 create table ec0 (ff int8 primary key, f1 int8, f2 int8);
 create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
 create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 0581d21b03f..8c19c03488a 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -16,6 +16,8 @@ SELECT count(*) = 0 AS skip_test FROM xmltest \gset
 
 SELECT * FROM xmltest;
 
+SELECT CAST('<wrong'::text AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml;
+
 -- test non-throwing API, too
 SELECT pg_input_is_valid('<value>one</value>', 'xml');
 SELECT pg_input_is_valid('<value>one</', 'xml');
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b964e5ee907..f858c41df9e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2765,6 +2765,9 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeCastElement
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

