Subject: CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions

From: Corey Huinker <[email-masked]>
Date: Mon, 19 Dec 2022 17:56:37 -0500

Attached is my work in progress to implement the changes to the CAST()
function as proposed by Vik Fearing.

This work builds upon the Error-safe User Functions work currently ongoing.

The proposed changes are as follows:

CAST(expr AS typename)
    continues to behave as before.

CAST(expr AS typename ERROR ON ERROR)
    has the identical behavior as the unadorned CAST() above.

CAST(expr AS typename NULL ON ERROR)
    will use error-safe functions to do the cast of expr, and will return
NULL if the cast fails.

CAST(expr AS typename DEFAULT expr2 ON ERROR)
    will use error-safe functions to do the cast of expr, and will return
expr2 if the cast fails.

There is an additional FORMAT parameter that I have not yet implemented, my
understanding is that it is largely intended for DATE/TIME field
conversions, but others are certainly possible.
CAST(expr AS typename FORMAT fmt DEFAULT expr2 ON ERROR)

What is currently working:
- Any scalar expression that can be evaluated at parse time. These tests
from cast.sql all currently work:

VALUES (CAST('error' AS integer));
VALUES (CAST('error' AS integer ERROR ON ERROR));
VALUES (CAST('error' AS integer NULL ON ERROR));
VALUES (CAST('error' AS integer DEFAULT 42 ON ERROR));

SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON ERROR) as
array_test1;

- Scalar values evaluated at runtime.

CREATE TEMPORARY TABLE t(t text);
INSERT INTO t VALUES ('a'), ('1'), ('b'), (2);
SELECT CAST(t.t AS integer DEFAULT -1 ON ERROR) AS foo FROM t;
 foo
-----
  -1
   1
  -1
   2
(4 rows)

Along the way, I made a few design decisions, each of which is up for
debate:

First, I created OidInputFunctionCallSafe, which is to OidInputFunctionCall
what InputFunctionCallSafe is to InputFunctionCall. Given that the only
place I ended up using it was stringTypeDatumSafe(), it may be possible to
just move that code inside stringTypeDatumSafe.

Next, I had a need for FuncExpr, CoerceViaIO, and ArrayCoerce to all report
if their expr argument failed, and if not, just past the evaluation of
expr2. Rather than duplicate this logic in several places, I chose instead
to modify CoalesceExpr to allow for an error-test mode in addition to its
default null-test mode, and then to provide this altered node with two
expressions, the first being the error-safe typecast of expr and the second
being the non-error-safe typecast of expr2.

I still don't have array-to-array casts working, as the changed I would
likely need to make to ArrayCoerce get somewhat invasive, so this seemed
like a good time to post my work so far and solicit some feedback beyond
what I've already been getting from Jeff Davis and Michael Paquier.

I've sidestepped domains as well for the time being as well as avoiding JIT
issues entirely.

No documentation is currently prepared. All but one of the regression test
queries work, the one that is currently failing is:

SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON
ERROR) as array_test2;

Other quirks:
- an unaliased CAST ON DEFAULT will return the column name of "coalesce",
which internally is true, but obviously would be quite confusing to a user.

As a side observation, I noticed that the optimizer already tries to
resolve expressions based on constants and to collapse expression trees
where possible, which makes me wonder if the work done to do the same in
transformTypeCast/ and coerce_to_target_type and coerce_type isn't also
wasted.
From 897c9c68a29ad0fa57f28734df0c77553e026d80 Mon Sep 17 00:00:00 2001
From: coreyhuinker <[email-masked]>
Date: Sun, 18 Dec 2022 16:20:01 -0500
Subject: [PATCH 1/3] add OidInputFunctionCall

---
 src/backend/utils/fmgr/fmgr.c | 13 +++++++++++++
 src/include/fmgr.h            |  4 ++++
 2 files changed, 17 insertions(+)

diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0d37f69298..e9a19ce653 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1701,6 +1701,19 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
 	return InputFunctionCall(&flinfo, str, typioparam, typmod);
 }
 
+bool
+OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+						 int32 typmod, fmNodePtr escontext,
+						 Datum *result)
+{
+	FmgrInfo			flinfo;
+
+	fmgr_info(functionId, &flinfo);
+
+	return InputFunctionCallSafe(&flinfo, str, typioparam, typmod,
+								 escontext, result);
+}
+
 char *
 OidOutputFunctionCall(Oid functionId, Datum val)
 {
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index b7832d0aa2..b835ef72b5 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -706,6 +706,10 @@ extern bool InputFunctionCallSafe(FmgrInfo *flinfo, char *str,
 								  Datum *result);
 extern Datum OidInputFunctionCall(Oid functionId, char *str,
 								  Oid typioparam, int32 typmod);
+extern bool
+OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+						 int32 typmod, fmNodePtr escontext,
+						 Datum *result);
 extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
 extern char *OidOutputFunctionCall(Oid functionId, Datum val);
 extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, fmStringInfo buf,
-- 
2.38.1

From 1153c977ba153f116170ca2922e0785f9d2604c8 Mon Sep 17 00:00:00 2001
From: coreyhuinker <[email-masked]>
Date: Mon, 19 Dec 2022 13:39:16 -0500
Subject: [PATCH 2/3] add stringTypeDatumSafe

---
 src/backend/parser/parse_type.c | 13 +++++++++++++
 src/include/parser/parse_type.h |  2 ++
 2 files changed, 15 insertions(+)

diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index f7ad689459..0e6dfe3b49 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "utils/array.h"
@@ -660,6 +661,18 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
 	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
 }
 
+bool
+stringTypeDatumSafe(Type tp, char *string, int32 atttypmod, Datum *result)
+{
+	Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+	Oid			typinput = typform->typinput;
+	Oid			typioparam = getTypeIOParam(tp);
+	ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+	return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod,
+									(fmNodePtr) &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/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 4e5624d721..7468f75823 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, char *string, int32 atttypmod);
+extern bool stringTypeDatumSafe(Type tp, char *string, int32 atttypmod,
+								Datum *result);
 
 extern Oid	typeidTypeRelid(Oid type_id);
 extern Oid	typeOrDomainTypeRelid(Oid type_id);
-- 
2.38.1

From 74aaf068a2d071f6fefab27309d7a0252f28ccee Mon Sep 17 00:00:00 2001
From: coreyhuinker <[email-masked]>
Date: Mon, 19 Dec 2022 17:11:49 -0500
Subject: [PATCH 3/3] CAST ON DEFAULT work in progress

---
 src/backend/executor/execExpr.c          | 173 +++++++++++-------
 src/backend/executor/execExprInterp.c    |  35 +++-
 src/backend/jit/llvm/llvmjit_expr.c      |  15 ++
 src/backend/nodes/makefuncs.c            |   4 +-
 src/backend/nodes/nodeFuncs.c            |  15 +-
 src/backend/optimizer/util/clauses.c     |   4 +-
 src/backend/parser/gram.y                |  46 ++++-
 src/backend/parser/parse_agg.c           |  15 +-
 src/backend/parser/parse_coerce.c        | 215 ++++++++++++++++++-----
 src/backend/parser/parse_expr.c          |  87 +++++++--
 src/backend/partitioning/partbounds.c    |   3 +-
 src/backend/rewrite/rewriteSearchCycle.c |   4 +-
 src/include/executor/execExpr.h          |   4 +
 src/include/nodes/execnodes.h            |   4 +
 src/include/nodes/makefuncs.h            |   3 +-
 src/include/nodes/parsenodes.h           |   1 +
 src/include/nodes/primnodes.h            |  12 +-
 src/include/parser/kwlist.h              |   1 +
 src/include/parser/parse_coerce.h        |  15 ++
 src/test/regress/expected/cast.out       |  40 +++++
 src/test/regress/parallel_schedule       |   2 +-
 src/test/regress/sql/cast.sql            |  11 ++
 22 files changed, 551 insertions(+), 158 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 81429b9f05..9aaa53b67e 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -40,6 +40,7 @@
 #include "jit/jit.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "optimizer/optimizer.h"
@@ -61,10 +62,10 @@ typedef struct LastAttnumInfo
 
 static void ExecReadyExpr(ExprState *state);
 static void ExecInitExprRec(Expr *node, ExprState *state,
-							Datum *resv, bool *resnull);
+							Datum *resv, bool *resnull, bool *reserror);
 static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
 						 Oid funcid, Oid inputcollid,
-						 ExprState *state);
+						 ExprState *state, ErrorSaveContext *escontext);
 static void ExecInitExprSlots(ExprState *state, Node *node);
 static void ExecPushExprSlots(ExprState *state, LastAttnumInfo *info);
 static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
@@ -74,11 +75,11 @@ static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable,
 static void ExecInitSubscriptingRef(ExprEvalStep *scratch,
 									SubscriptingRef *sbsref,
 									ExprState *state,
-									Datum *resv, bool *resnull);
+									Datum *resv, bool *resnull, bool *reserror);
 static bool isAssignmentIndirectionExpr(Expr *expr);
 static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 								   ExprState *state,
-								   Datum *resv, bool *resnull);
+								   Datum *resv, bool *resnull, bool *reserror);
 static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
 								  ExprEvalStep *scratch,
 								  FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
@@ -140,7 +141,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
 	ExecInitExprSlots(state, (Node *) node);
 
 	/* Compile the expression proper */
-	ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+	ExecInitExprRec(node, state, &state->resvalue, &state->resnull, &state->reserror);
 
 	/* Finally, append a DONE step */
 	scratch.opcode = EEOP_DONE;
@@ -177,7 +178,7 @@ ExecInitExprWithParams(Expr *node, ParamListInfo ext_params)
 	ExecInitExprSlots(state, (Node *) node);
 
 	/* Compile the expression proper */
-	ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+	ExecInitExprRec(node, state, &state->resvalue, &state->resnull, &state->reserror);
 
 	/* Finally, append a DONE step */
 	scratch.opcode = EEOP_DONE;
@@ -251,7 +252,7 @@ ExecInitQual(List *qual, PlanState *parent)
 		Expr	   *node = (Expr *) lfirst(lc);
 
 		/* first evaluate expression */
-		ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+		ExecInitExprRec(node, state, &state->resvalue, &state->resnull, &state->reserror);
 
 		/* then emit EEOP_QUAL to detect if it's false (or null) */
 		scratch.d.qualexpr.jumpdone = -1;
@@ -455,7 +456,7 @@ ExecBuildProjectionInfo(List *targetList,
 			 * into the ExprState's resvalue/resnull and then move.
 			 */
 			ExecInitExprRec(tle->expr, state,
-							&state->resvalue, &state->resnull);
+							&state->resvalue, &state->resnull, &state->reserror);
 
 			/*
 			 * Column might be referenced multiple times in upper nodes, so
@@ -659,7 +660,7 @@ ExecBuildUpdateProjection(List *targetList,
 			 * path and it doesn't seem worth expending code for that.
 			 */
 			ExecInitExprRec(tle->expr, state,
-							&state->resvalue, &state->resnull);
+							&state->resvalue, &state->resnull, &state->reserror);
 			/* Needn't worry about read-only-ness here, either. */
 			scratch.opcode = EEOP_ASSIGN_TMP;
 			scratch.d.assign_tmp.resultnum = targetattnum - 1;
@@ -688,7 +689,7 @@ ExecBuildUpdateProjection(List *targetList,
 
 			Assert(tle->resjunk);
 			ExecInitExprRec(tle->expr, state,
-							&state->resvalue, &state->resnull);
+							&state->resvalue, &state->resnull, &state->reserror);
 		}
 	}
 
@@ -899,7 +900,7 @@ ExecReadyExpr(ExprState *state)
  */
 static void
 ExecInitExprRec(Expr *node, ExprState *state,
-				Datum *resv, bool *resnull)
+				Datum *resv, bool *resnull, bool *reserror)
 {
 	ExprEvalStep scratch = {0};
 
@@ -910,6 +911,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 	Assert(resv != NULL && resnull != NULL);
 	scratch.resvalue = resv;
 	scratch.resnull = resnull;
+	scratch.reserror = reserror;
 
 	/* cases should be ordered as they are in enum NodeTag */
 	switch (nodeTag(node))
@@ -1126,17 +1128,24 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
 
-				ExecInitSubscriptingRef(&scratch, sbsref, state, resv, resnull);
+				ExecInitSubscriptingRef(&scratch, sbsref, state, resv, resnull, NULL);
 				break;
 			}
 
 		case T_FuncExpr:
 			{
-				FuncExpr   *func = (FuncExpr *) node;
+				FuncExpr		   *func = (FuncExpr *) node;
+				ErrorSaveContext   *escontext = NULL;
+
+				if (unlikely(func->safe_mode))
+				{
+					escontext = palloc0(sizeof(ErrorSaveContext));
+					escontext->type = T_ErrorSaveContext;
+				}
 
 				ExecInitFunc(&scratch, node,
 							 func->args, func->funcid, func->inputcollid,
-							 state);
+							 state, escontext);
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
@@ -1147,7 +1156,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				ExecInitFunc(&scratch, node,
 							 op->args, op->opfuncid, op->inputcollid,
-							 state);
+							 state, NULL);
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
@@ -1158,7 +1167,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				ExecInitFunc(&scratch, node,
 							 op->args, op->opfuncid, op->inputcollid,
-							 state);
+							 state, NULL);
 
 				/*
 				 * Change opcode of call instruction to EEOP_DISTINCT.
@@ -1180,7 +1189,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				ExecInitFunc(&scratch, node,
 							 op->args, op->opfuncid, op->inputcollid,
-							 state);
+							 state, NULL);
 
 				/*
 				 * Change opcode of call instruction to EEOP_NULLIF.
@@ -1263,7 +1272,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				{
 					/* Evaluate scalar directly into left function argument */
 					ExecInitExprRec(scalararg, state,
-									&fcinfo->args[0].value, &fcinfo->args[0].isnull);
+									&fcinfo->args[0].value, &fcinfo->args[0].isnull, NULL);
 
 					/*
 					 * Evaluate array argument into our return value.  There's
@@ -1272,7 +1281,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					 * EEOP_HASHED_SCALARARRAYOP, and will not be passed to
 					 * any other expression.
 					 */
-					ExecInitExprRec(arrayarg, state, resv, resnull);
+					ExecInitExprRec(arrayarg, state, resv, resnull, NULL);
 
 					/* And perform the operation */
 					scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
@@ -1289,7 +1298,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					/* Evaluate scalar directly into left function argument */
 					ExecInitExprRec(scalararg, state,
 									&fcinfo->args[0].value,
-									&fcinfo->args[0].isnull);
+									&fcinfo->args[0].isnull, NULL);
 
 					/*
 					 * Evaluate array argument into our return value.  There's
@@ -1297,7 +1306,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					 * guaranteed to be overwritten by EEOP_SCALARARRAYOP, and
 					 * will not be passed to any other expression.
 					 */
-					ExecInitExprRec(arrayarg, state, resv, resnull);
+					ExecInitExprRec(arrayarg, state, resv, resnull, NULL);
 
 					/* And perform the operation */
 					scratch.opcode = EEOP_SCALARARRAYOP;
@@ -1342,7 +1351,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					Expr	   *arg = (Expr *) lfirst(lc);
 
 					/* Evaluate argument into our output variable */
-					ExecInitExprRec(arg, state, resv, resnull);
+					ExecInitExprRec(arg, state, resv, resnull, NULL);
 
 					/* Perform the appropriate step type */
 					switch (boolexpr->boolop)
@@ -1423,7 +1432,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				FieldSelect *fselect = (FieldSelect *) node;
 
 				/* evaluate row/record argument into result area */
-				ExecInitExprRec(fselect->arg, state, resv, resnull);
+				ExecInitExprRec(fselect->arg, state, resv, resnull, NULL);
 
 				/* and extract field */
 				scratch.opcode = EEOP_FIELDSELECT;
@@ -1460,7 +1469,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				rowcachep->cacheptr = NULL;
 
 				/* emit code to evaluate the composite input value */
-				ExecInitExprRec(fstore->arg, state, resv, resnull);
+				ExecInitExprRec(fstore->arg, state, resv, resnull, NULL);
 
 				/* next, deform the input tuple into our workspace */
 				scratch.opcode = EEOP_FIELDSTORE_DEFORM;
@@ -1514,7 +1523,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					ExecInitExprRec(e, state,
 									&values[fieldnum - 1],
-									&nulls[fieldnum - 1]);
+									&nulls[fieldnum - 1], NULL);
 
 					state->innermost_caseval = save_innermost_caseval;
 					state->innermost_casenull = save_innermost_casenull;
@@ -1536,7 +1545,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/* relabel doesn't need to do anything at runtime */
 				RelabelType *relabel = (RelabelType *) node;
 
-				ExecInitExprRec(relabel->arg, state, resv, resnull);
+				ExecInitExprRec(relabel->arg, state, resv, resnull, NULL);
 				break;
 			}
 
@@ -1547,9 +1556,10 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				bool		typisvarlena;
 				Oid			typioparam;
 				FunctionCallInfo fcinfo_in;
+				ErrorSaveContext *escontext = NULL;
 
 				/* evaluate argument into step's result area */
-				ExecInitExprRec(iocoerce->arg, state, resv, resnull);
+				ExecInitExprRec(iocoerce->arg, state, resv, resnull, reserror);
 
 				/*
 				 * Prepare both output and input function calls, to be
@@ -1581,9 +1591,17 @@ ExecInitExprRec(Expr *node, ExprState *state,
 								 &iofunc, &typioparam);
 				fmgr_info(iofunc, scratch.d.iocoerce.finfo_in);
 				fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_in);
+
+				/* if this is a safe-mode coerce */
+				if (unlikely(iocoerce->safe_mode))
+				{
+					escontext = palloc0(sizeof(ErrorSaveContext));
+					escontext->type = T_ErrorSaveContext;
+				}
+
 				InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_in,
 										 scratch.d.iocoerce.finfo_in,
-										 3, InvalidOid, NULL, NULL);
+										 3, InvalidOid, (fmNodePtr) escontext, NULL);
 
 				/*
 				 * We can preload the second and third arguments for the input
@@ -1606,7 +1624,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				ExprState  *elemstate;
 
 				/* evaluate argument into step's result area */
-				ExecInitExprRec(acoerce->arg, state, resv, resnull);
+				ExecInitExprRec(acoerce->arg, state, resv, resnull, reserror);
 
 				resultelemtype = get_element_type(acoerce->resulttype);
 				if (!OidIsValid(resultelemtype))
@@ -1627,9 +1645,11 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
 				elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+				elemstate->innermost_caseerror = (bool *) palloc(sizeof(bool));
 
 				ExecInitExprRec(acoerce->elemexpr, elemstate,
-								&elemstate->resvalue, &elemstate->resnull);
+								&elemstate->resvalue, &elemstate->resnull,
+								&elemstate->reserror);
 
 				if (elemstate->steps_len == 1 &&
 					elemstate->steps[0].opcode == EEOP_CASE_TESTVAL)
@@ -1677,7 +1697,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				rowcachep[1].cacheptr = NULL;
 
 				/* evaluate argument into step's result area */
-				ExecInitExprRec(convert->arg, state, resv, resnull);
+				ExecInitExprRec(convert->arg, state, resv, resnull, NULL);
 
 				/* and push conversion step */
 				scratch.opcode = EEOP_CONVERT_ROWTYPE;
@@ -1699,6 +1719,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				List	   *adjust_jumps = NIL;
 				Datum	   *caseval = NULL;
 				bool	   *casenull = NULL;
+				bool	   *caseerror = NULL;
 				ListCell   *lc;
 
 				/*
@@ -1711,9 +1732,10 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					/* Evaluate testexpr into caseval/casenull workspace */
 					caseval = palloc(sizeof(Datum));
 					casenull = palloc(sizeof(bool));
+					caseerror = palloc(sizeof(bool));
 
 					ExecInitExprRec(caseExpr->arg, state,
-									caseval, casenull);
+									caseval, casenull, caseerror);
 
 					/*
 					 * Since value might be read multiple times, force to R/O
@@ -1725,8 +1747,10 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						scratch.opcode = EEOP_MAKE_READONLY;
 						scratch.resvalue = caseval;
 						scratch.resnull = casenull;
+						scratch.reserror = caseerror;
 						scratch.d.make_readonly.value = caseval;
 						scratch.d.make_readonly.isnull = casenull;
+						scratch.d.make_readonly.iserror = caseerror;
 						ExprEvalPushStep(state, &scratch);
 						/* restore normal settings of scratch fields */
 						scratch.resvalue = resv;
@@ -1745,6 +1769,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					CaseWhen   *when = (CaseWhen *) lfirst(lc);
 					Datum	   *save_innermost_caseval;
 					bool	   *save_innermost_casenull;
+					bool	   *save_innermost_caseerror;
 					int			whenstep;
 
 					/*
@@ -1759,14 +1784,17 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					 */
 					save_innermost_caseval = state->innermost_caseval;
 					save_innermost_casenull = state->innermost_casenull;
+					save_innermost_caseerror = state->innermost_caseerror;
 					state->innermost_caseval = caseval;
 					state->innermost_casenull = casenull;
+					state->innermost_caseerror = caseerror;
 
 					/* evaluate condition into CASE's result variables */
-					ExecInitExprRec(when->expr, state, resv, resnull);
+					ExecInitExprRec(when->expr, state, resv, resnull, NULL);
 
 					state->innermost_caseval = save_innermost_caseval;
 					state->innermost_casenull = save_innermost_casenull;
+					state->innermost_caseerror = save_innermost_caseerror;
 
 					/* If WHEN result isn't true, jump to next CASE arm */
 					scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
@@ -1778,7 +1806,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					 * If WHEN result is true, evaluate THEN result, storing
 					 * it into the CASE's result variables.
 					 */
-					ExecInitExprRec(when->result, state, resv, resnull);
+					ExecInitExprRec(when->result, state, resv, resnull, reserror);
 
 					/* Emit JUMP step to jump to end of CASE's code */
 					scratch.opcode = EEOP_JUMP;
@@ -1804,7 +1832,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				/* evaluate ELSE expr into CASE's result variables */
 				ExecInitExprRec(caseExpr->defresult, state,
-								resv, resnull);
+								resv, resnull, reserror);
 
 				/* adjust jump targets */
 				foreach(lc, adjust_jumps)
@@ -1833,6 +1861,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				scratch.opcode = EEOP_CASE_TESTVAL;
 				scratch.d.casetest.value = state->innermost_caseval;
 				scratch.d.casetest.isnull = state->innermost_casenull;
+				scratch.d.casetest.iserror = state->innermost_caseerror;
 
 				ExprEvalPushStep(state, &scratch);
 				break;
@@ -1875,7 +1904,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					ExecInitExprRec(e, state,
 									&scratch.d.arrayexpr.elemvalues[elemoff],
-									&scratch.d.arrayexpr.elemnulls[elemoff]);
+									&scratch.d.arrayexpr.elemnulls[elemoff], NULL);
 					elemoff++;
 				}
 
@@ -1969,7 +1998,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					/* Evaluate column expr into appropriate workspace slot */
 					ExecInitExprRec(e, state,
 									&scratch.d.row.elemvalues[i],
-									&scratch.d.row.elemnulls[i]);
+									&scratch.d.row.elemnulls[i], NULL);
 					i++;
 				}
 
@@ -2047,9 +2076,9 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					/* evaluate left and right args directly into fcinfo */
 					ExecInitExprRec(left_expr, state,
-									&fcinfo->args[0].value, &fcinfo->args[0].isnull);
+									&fcinfo->args[0].value, &fcinfo->args[0].isnull, NULL);
 					ExecInitExprRec(right_expr, state,
-									&fcinfo->args[1].value, &fcinfo->args[1].isnull);
+									&fcinfo->args[1].value, &fcinfo->args[1].isnull, NULL);
 
 					scratch.opcode = EEOP_ROWCOMPARE_STEP;
 					scratch.d.rowcompare_step.finfo = finfo;
@@ -2104,6 +2133,13 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				CoalesceExpr *coalesce = (CoalesceExpr *) node;
 				List	   *adjust_jumps = NIL;
 				ListCell   *lc;
+				ExprEvalOp jump_test_opcode;
+
+				/* Coalesce can handle resnull and reserror tests */
+				if (likely(coalesce->op == NULL_TEST))
+					jump_test_opcode = EEOP_JUMP_IF_NOT_NULL;
+				else
+					jump_test_opcode = EEOP_JUMP_IF_NOT_ERROR;
 
 				/* We assume there's at least one arg */
 				Assert(coalesce->args != NIL);
@@ -2117,10 +2153,10 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					Expr	   *e = (Expr *) lfirst(lc);
 
 					/* evaluate argument, directly into result datum */
-					ExecInitExprRec(e, state, resv, resnull);
+					ExecInitExprRec(e, state, resv, resnull, reserror);
 
-					/* if it's not null, skip to end of COALESCE expr */
-					scratch.opcode = EEOP_JUMP_IF_NOT_NULL;
+					/* if it's not null/error, skip to end of COALESCE expr */
+					scratch.opcode = jump_test_opcode;
 					scratch.d.jump.jumpdone = -1;	/* adjust later */
 					ExprEvalPushStep(state, &scratch);
 
@@ -2139,7 +2175,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				{
 					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
 
-					Assert(as->opcode == EEOP_JUMP_IF_NOT_NULL);
+					Assert(as->opcode == jump_test_opcode);
 					Assert(as->d.jump.jumpdone == -1);
 					as->d.jump.jumpdone = state->steps_len;
 				}
@@ -2201,7 +2237,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					ExecInitExprRec(e, state,
 									&scratch.d.minmax.values[off],
-									&scratch.d.minmax.nulls[off]);
+									&scratch.d.minmax.nulls[off], NULL);
 					off++;
 				}
 
@@ -2256,7 +2292,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					ExecInitExprRec(e, state,
 									&scratch.d.xmlexpr.named_argvalue[off],
-									&scratch.d.xmlexpr.named_argnull[off]);
+									&scratch.d.xmlexpr.named_argnull[off], NULL);
 					off++;
 				}
 
@@ -2267,7 +2303,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					ExecInitExprRec(e, state,
 									&scratch.d.xmlexpr.argvalue[off],
-									&scratch.d.xmlexpr.argnull[off]);
+									&scratch.d.xmlexpr.argnull[off], NULL);
 					off++;
 				}
 
@@ -2304,7 +2340,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				/* first evaluate argument into result variable */
 				ExecInitExprRec(ntest->arg, state,
-								resv, resnull);
+								resv, resnull, NULL);
 
 				/* then push the test of that argument */
 				ExprEvalPushStep(state, &scratch);
@@ -2321,7 +2357,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				 * and will get overwritten by the below EEOP_BOOLTEST_IS_*
 				 * step.
 				 */
-				ExecInitExprRec(btest->arg, state, resv, resnull);
+				ExecInitExprRec(btest->arg, state, resv, resnull, NULL);
 
 				switch (btest->booltesttype)
 				{
@@ -2359,7 +2395,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				CoerceToDomain *ctest = (CoerceToDomain *) node;
 
 				ExecInitCoerceToDomain(&scratch, ctest, state,
-									   resv, resnull);
+									   resv, resnull, NULL);
 				break;
 			}
 
@@ -2442,7 +2478,7 @@ ExprEvalPushStep(ExprState *es, const ExprEvalStep *s)
  */
 static void
 ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
-			 Oid inputcollid, ExprState *state)
+			 Oid inputcollid, ExprState *state, ErrorSaveContext *escontext)
 {
 	int			nargs = list_length(args);
 	AclResult	aclresult;
@@ -2483,7 +2519,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 
 	/* Initialize function call parameter structure too */
 	InitFunctionCallInfoData(*fcinfo, flinfo,
-							 nargs, inputcollid, NULL, NULL);
+							 nargs, inputcollid, (fmNodePtr) escontext, NULL);
 
 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -2519,7 +2555,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 		{
 			ExecInitExprRec(arg, state,
 							&fcinfo->args[argno].value,
-							&fcinfo->args[argno].isnull);
+							&fcinfo->args[argno].isnull, NULL);
 		}
 		argno++;
 	}
@@ -2570,6 +2606,7 @@ ExecPushExprSlots(ExprState *state, LastAttnumInfo *info)
 
 	scratch.resvalue = NULL;
 	scratch.resnull = NULL;
+	scratch.reserror = NULL;
 
 	/* Emit steps as needed */
 	if (info->last_inner > 0)
@@ -2834,7 +2871,7 @@ ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state)
  */
 static void
 ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
-						ExprState *state, Datum *resv, bool *resnull)
+						ExprState *state, Datum *resv, bool *resnull, bool *reserror)
 {
 	bool		isAssignment = (sbsref->refassgnexpr != NULL);
 	int			nupper = list_length(sbsref->refupperindexpr);
@@ -2897,7 +2934,7 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 	 * be overwritten by the final EEOP_SBSREF_FETCH/ASSIGN step, which is
 	 * pushed last.
 	 */
-	ExecInitExprRec(sbsref->refexpr, state, resv, resnull);
+	ExecInitExprRec(sbsref->refexpr, state, resv, resnull, NULL);
 
 	/*
 	 * If refexpr yields NULL, and the operation should be strict, then result
@@ -2931,7 +2968,7 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 			/* Each subscript is evaluated into appropriate array entry */
 			ExecInitExprRec(e, state,
 							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+							&sbsrefstate->upperindexnull[i], NULL);
 		}
 		i++;
 	}
@@ -2954,7 +2991,7 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 			/* Each subscript is evaluated into appropriate array entry */
 			ExecInitExprRec(e, state,
 							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+							&sbsrefstate->lowerindexnull[i], NULL);
 		}
 		i++;
 	}
@@ -3018,7 +3055,7 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 
 		/* evaluate replacement value into replacevalue/replacenull */
 		ExecInitExprRec(sbsref->refassgnexpr, state,
-						&sbsrefstate->replacevalue, &sbsrefstate->replacenull);
+						&sbsrefstate->replacevalue, &sbsrefstate->replacenull, NULL);
 
 		state->innermost_caseval = save_innermost_caseval;
 		state->innermost_casenull = save_innermost_casenull;
@@ -3107,11 +3144,12 @@ isAssignmentIndirectionExpr(Expr *expr)
  */
 static void
 ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
-					   ExprState *state, Datum *resv, bool *resnull)
+					   ExprState *state, Datum *resv, bool *resnull, bool *reserror)
 {
 	DomainConstraintRef *constraint_ref;
 	Datum	   *domainval = NULL;
 	bool	   *domainnull = NULL;
+	bool	   *domainerror = NULL;
 	ListCell   *l;
 
 	scratch->d.domaincheck.resulttype = ctest->resulttype;
@@ -3124,7 +3162,7 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 	 * if there's constraint failures there'll be errors, otherwise it's what
 	 * needs to be returned.
 	 */
-	ExecInitExprRec(ctest->arg, state, resv, resnull);
+	ExecInitExprRec(ctest->arg, state, resv, resnull, NULL);
 
 	/*
 	 * Note: if the argument is of varlena type, it could be a R/W expanded
@@ -3196,11 +3234,13 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 						/* Yes, so make output workspace for MAKE_READONLY */
 						domainval = (Datum *) palloc(sizeof(Datum));
 						domainnull = (bool *) palloc(sizeof(bool));
+						domainerror = (bool *) palloc(sizeof(bool));
 
 						/* Emit MAKE_READONLY */
 						scratch2.opcode = EEOP_MAKE_READONLY;
 						scratch2.resvalue = domainval;
 						scratch2.resnull = domainnull;
+						scratch2.reserror = domainerror;
 						scratch2.d.make_readonly.value = resv;
 						scratch2.d.make_readonly.isnull = resnull;
 						ExprEvalPushStep(state, &scratch2);
@@ -3227,7 +3267,7 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 				/* evaluate check expression value */
 				ExecInitExprRec(con->check_expr, state,
 								scratch->d.domaincheck.checkvalue,
-								scratch->d.domaincheck.checknull);
+								scratch->d.domaincheck.checknull, NULL);
 
 				state->innermost_domainval = save_innermost_domainval;
 				state->innermost_domainnull = save_innermost_domainnull;
@@ -3319,7 +3359,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 		{
 			/* evaluate filter expression */
 			ExecInitExprRec(pertrans->aggref->aggfilter, state,
-							&state->resvalue, &state->resnull);
+							&state->resvalue, &state->resnull, NULL);
 			/* and jump out if false */
 			scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
 			scratch.d.jump.jumpdone = -1;	/* adjust later */
@@ -3359,7 +3399,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 				 */
 				ExecInitExprRec(source_tle->expr, state,
 								&trans_fcinfo->args[argno + 1].value,
-								&trans_fcinfo->args[argno + 1].isnull);
+								&trans_fcinfo->args[argno + 1].isnull, NULL);
 			}
 			else
 			{
@@ -3368,7 +3408,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 				/* evaluate argument */
 				ExecInitExprRec(source_tle->expr, state,
 								&ds_fcinfo->args[0].value,
-								&ds_fcinfo->args[0].isnull);
+								&ds_fcinfo->args[0].isnull, NULL);
 
 				/* Dummy second argument for type-safety reasons */
 				ds_fcinfo->args[1].value = PointerGetDatum(NULL);
@@ -3430,7 +3470,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 				 */
 				ExecInitExprRec(source_tle->expr, state,
 								&trans_fcinfo->args[argno + 1].value,
-								&trans_fcinfo->args[argno + 1].isnull);
+								&trans_fcinfo->args[argno + 1].isnull, NULL);
 				argno++;
 			}
 			Assert(pertrans->numTransInputs == argno);
@@ -3448,7 +3488,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 
 			ExecInitExprRec(source_tle->expr, state,
 							&state->resvalue,
-							&state->resnull);
+							&state->resnull, NULL);
 			strictnulls = &state->resnull;
 			argno++;
 
@@ -3471,7 +3511,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 				TargetEntry *source_tle = (TargetEntry *) lfirst(arg);
 
 				ExecInitExprRec(source_tle->expr, state,
-								&values[argno], &nulls[argno]);
+								&values[argno], &nulls[argno], NULL);
 				argno++;
 			}
 			Assert(pertrans->numInputs == argno);
@@ -3585,6 +3625,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 
 	scratch.resvalue = NULL;
 	scratch.resnull = NULL;
+	scratch.reserror = NULL;
 	scratch.opcode = EEOP_DONE;
 	ExprEvalPushStep(state, &scratch);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 1dab2787b7..6cc6fa1717 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -64,6 +64,7 @@
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/miscnodes.h"
 #include "parser/parsetree.h"
 #include "pgstat.h"
 #include "utils/array.h"
@@ -434,6 +435,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_JUMP,
 		&&CASE_EEOP_JUMP_IF_NULL,
 		&&CASE_EEOP_JUMP_IF_NOT_NULL,
+		&&CASE_EEOP_JUMP_IF_NOT_ERROR,
 		&&CASE_EEOP_JUMP_IF_NOT_TRUE,
 		&&CASE_EEOP_NULLTEST_ISNULL,
 		&&CASE_EEOP_NULLTEST_ISNOTNULL,
@@ -729,6 +731,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			*op->resvalue = d;
 			*op->resnull = fcinfo->isnull;
 
+			if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			{
+				ErrorSaveContext *escontext = (ErrorSaveContext *) fcinfo->context;
+				escontext->error_occurred = false;
+				*op->reserror = true;
+			}
+
 			EEO_NEXT();
 		}
 
@@ -966,6 +975,21 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_JUMP_IF_NOT_ERROR)
+		{
+			/* Transfer control if current result is non-error */
+			if (!*op->reserror)
+			{
+				*op->reserror = false;
+				EEO_JUMP(op->d.jump.jumpdone);
+			}
+
+			/* reset error flag */
+			*op->reserror = false;
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_JUMP_IF_NOT_TRUE)
 		{
 			/* Transfer control if current result is null or false */
@@ -1181,10 +1205,17 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 
 				fcinfo_in->isnull = false;
 				d = FunctionCallInvoke(fcinfo_in);
+
 				*op->resvalue = d;
 
-				/* Should get null result if and only if str is NULL */
-				if (str == NULL)
+				if (SOFT_ERROR_OCCURRED(fcinfo_in->context))
+				{
+					ErrorSaveContext *escontext = (ErrorSaveContext *) fcinfo_in->context;
+					escontext->error_occurred = false;
+					*op->reserror = true;
+				}
+				/* If no error, should get null result if and only if str is NULL */
+				else if (str == NULL)
 				{
 					Assert(*op->resnull);
 					Assert(fcinfo_in->isnull);
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index f114337f8e..f99b108ceb 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -919,6 +919,21 @@ llvm_compile_expr(ExprState *state)
 					break;
 				}
 
+			case EEOP_JUMP_IF_NOT_ERROR:
+				{
+					LLVMValueRef v_reserror;
+
+					/* Transfer control if current result is non-error */
+
+					v_resnull = LLVMBuildLoad(b, v_reserrorp, "");
+
+					LLVMBuildCondBr(b,
+									LLVMBuildICmp(b, LLVMIntEQ, v_reserror,
+												  l_sbool_const(0), ""),
+									opblocks[op->d.jump.jumpdone],
+									opblocks[opno + 1]);
+					break;
+				}
 
 			case EEOP_JUMP_IF_NOT_TRUE:
 				{
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c85d8fe975..229d1e84c8 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -517,7 +517,8 @@ makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
  */
 FuncExpr *
 makeFuncExpr(Oid funcid, Oid rettype, List *args,
-			 Oid funccollid, Oid inputcollid, CoercionForm fformat)
+			 Oid funccollid, Oid inputcollid, CoercionForm fformat,
+			 bool safe_mode)
 {
 	FuncExpr   *funcexpr;
 
@@ -530,6 +531,7 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args,
 	funcexpr->funccollid = funccollid;
 	funcexpr->inputcollid = inputcollid;
 	funcexpr->args = args;
+	funcexpr->safe_mode = safe_mode;
 	funcexpr->location = -1;
 
 	return funcexpr;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index af8620ceb7..7bc3e13208 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1522,13 +1522,16 @@ exprLocation(const Node *expr)
 			{
 				const TypeCast *tc = (const TypeCast *) expr;
 
-				/*
-				 * This could represent CAST(), ::, or TypeName 'literal', so
-				 * any of the components might be leftmost.
-				 */
 				loc = exprLocation(tc->arg);
-				loc = leftmostLoc(loc, tc->typeName->location);
-				loc = leftmostLoc(loc, tc->location);
+				if (likely(!tc->safe_mode))
+				{
+					/*
+					 * This could represent CAST(), ::, or TypeName 'literal',
+					 * so any of the components might be leftmost.
+					 */
+					loc = leftmostLoc(loc, tc->typeName->location);
+					loc = leftmostLoc(loc, tc->location);
+				}
 			}
 			break;
 		case T_CollateClause:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index bffc8112aa..e0b0ffb9a6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2894,6 +2894,7 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->resulttype = expr->resulttype;
 				newexpr->resultcollid = expr->resultcollid;
 				newexpr->coerceformat = expr->coerceformat;
+				newexpr->safe_mode = expr->safe_mode;
 				newexpr->location = expr->location;
 				return (Node *) newexpr;
 			}
@@ -3158,7 +3159,7 @@ eval_const_expressions_mutator(Node *node,
 					 * drop following arguments since they will never be
 					 * reached.
 					 */
-					if (IsA(e, Const))
+					if ((coalesceexpr->op == NULL_TEST) && IsA(e, Const))
 					{
 						if (((Const *) e)->constisnull)
 							continue;	/* drop null constant */
@@ -3183,6 +3184,7 @@ eval_const_expressions_mutator(Node *node,
 				newcoalesce->coalescetype = coalesceexpr->coalescetype;
 				newcoalesce->coalescecollid = coalesceexpr->coalescecollid;
 				newcoalesce->args = newargs;
+				newcoalesce->op = coalesceexpr->op;
 				newcoalesce->location = coalesceexpr->location;
 				return (Node *) newcoalesce;
 			}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index adc3f8ced3..3e24fe5f70 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -642,6 +642,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partboundspec> PartitionBoundSpec
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
+%type <node>	cast_on_error_clause
+%type <node>	cast_on_error_action

 /*
@@ -690,7 +692,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
 	DOUBLE_P DROP
 
-	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
+	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE EVENT EXCEPT
 	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
 	EXTENSION EXTERNAL EXTRACT
 
@@ -14399,8 +14401,7 @@ interval_second:
  * you expect!  So we use %prec annotations freely to set precedences.
  */
 a_expr:		c_expr									{ $$ = $1; }
-			| a_expr TYPECAST Typename
-					{ $$ = makeTypeCast($1, $3, @2); }
+			| a_expr TYPECAST Typename { $$ = makeTypeCast($1, $3, @2); }
 			| a_expr COLLATE any_name
 				{
 					CollateClause *n = makeNode(CollateClause);
@@ -15330,8 +15331,26 @@ func_expr_common_subexpr:
 											   COERCE_SQL_SYNTAX,
 											   @1);
 				}
-			| CAST '(' a_expr AS Typename ')'
-				{ $$ = makeTypeCast($3, $5, @1); }
+			| CAST '(' a_expr AS Typename cast_on_error_clause ')'
+				{
+					TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+					if ($6 == NULL)
+						$$ = (Node *) cast;
+					else
+					{
+						/*
+						 * On-error actions must themselves be typecast to the
+						 * same type as the original expression.
+						 */
+						TypeCast *on_err = (TypeCast *) makeTypeCast($6, $5, @6);
+						CoalesceExpr *c = makeNode(CoalesceExpr);
+						cast->safe_mode = true;
+						c->args = list_make2(cast, on_err);
+						c->op = ERROR_TEST;
+						c->location = @1;
+						$$ = (Node *) c;
+					}
+				}
 			| EXTRACT '(' extract_list ')'
 				{
 					$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -15462,6 +15481,7 @@ func_expr_common_subexpr:
 					CoalesceExpr *c = makeNode(CoalesceExpr);
 
 					c->args = $3;
+					c->op = NULL_TEST;
 					c->location = @1;
 					$$ = (Node *) c;
 				}
@@ -15551,6 +15571,15 @@ func_expr_common_subexpr:
 				}
 		;
 
+cast_on_error_clause: cast_on_error_action ON ERROR_P { $$ = $1; }
+			| /* EMPTY */ { $$ = NULL; }
+		;
+
+cast_on_error_action: ERROR_P { $$ = NULL; }
+			| NULL_P { $$ = makeNullAConst(-1); }
+			| DEFAULT a_expr { $$ = $2; }
+		;
+
 /*
  * SQL/XML support
  */
@@ -16138,8 +16167,8 @@ substr_list:
 					 * is unknown or doesn't have an implicit cast to int4.
 					 */
 					$$ = list_make3($1, makeIntConst(1, -1),
-									makeTypeCast($3,
-												 SystemTypeName("int4"), -1));
+									makeTypeCast($3, SystemTypeName("int4"),
+									-1));
 				}
 			| a_expr SIMILAR a_expr ESCAPE a_expr
 				{
@@ -16799,6 +16828,7 @@ unreserved_keyword:
 			| ENCODING
 			| ENCRYPTED
 			| ENUM_P
+			| ERROR_P
 			| ESCAPE
 			| EVENT
 			| EXCLUDE
@@ -17346,6 +17376,7 @@ bare_label_keyword:
 			| ENCRYPTED
 			| END_P
 			| ENUM_P
+			| ERROR_P
 			| ESCAPE
 			| EVENT
 			| EXCLUDE
@@ -17749,6 +17780,7 @@ makeTypeCast(Node *arg, TypeName *typename, int location)
 
 	n->arg = arg;
 	n->typeName = typename;
+	n->safe_mode = false;
 	n->location = location;
 	return (Node *) n;
 }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3ef9e8ee5e..b08422297d 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -2004,7 +2004,8 @@ build_aggregate_transfn_expr(Oid *agg_input_types,
 						 args,
 						 InvalidOid,
 						 agg_input_collation,
-						 COERCE_EXPLICIT_CALL);
+						 COERCE_EXPLICIT_CALL,
+						 NULL);
 	fexpr->funcvariadic = agg_variadic;
 	*transfnexpr = (Expr *) fexpr;
 
@@ -2020,7 +2021,8 @@ build_aggregate_transfn_expr(Oid *agg_input_types,
 								 args,
 								 InvalidOid,
 								 agg_input_collation,
-								 COERCE_EXPLICIT_CALL);
+								 COERCE_EXPLICIT_CALL,
+								 NULL);
 			fexpr->funcvariadic = agg_variadic;
 			*invtransfnexpr = (Expr *) fexpr;
 		}
@@ -2048,7 +2050,8 @@ build_aggregate_serialfn_expr(Oid serialfn_oid,
 						 args,
 						 InvalidOid,
 						 InvalidOid,
-						 COERCE_EXPLICIT_CALL);
+						 COERCE_EXPLICIT_CALL,
+						 NULL);
 	*serialfnexpr = (Expr *) fexpr;
 }
 
@@ -2072,7 +2075,8 @@ build_aggregate_deserialfn_expr(Oid deserialfn_oid,
 						 args,
 						 InvalidOid,
 						 InvalidOid,
-						 COERCE_EXPLICIT_CALL);
+						 COERCE_EXPLICIT_CALL,
+						 NULL);
 	*deserialfnexpr = (Expr *) fexpr;
 }
 
@@ -2109,7 +2113,8 @@ build_aggregate_finalfn_expr(Oid *agg_input_types,
 										 args,
 										 InvalidOid,
 										 agg_input_collation,
-										 COERCE_EXPLICIT_CALL);
+										 COERCE_EXPLICIT_CALL,
+										 NULL);
 	/* finalfn is currently never treated as variadic */
 }
 
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 60908111c8..c00ef1bd70 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -36,13 +36,15 @@ static Node *coerce_type_typmod(Node *node,
 								Oid targetTypeId, int32 targetTypMod,
 								CoercionContext ccontext, CoercionForm cformat,
 								int location,
-								bool hideInputCoercion);
+								bool hideInputCoercion,
+								bool *cast_error_found);
 static void hide_coercion_node(Node *node);
 static Node *build_coercion_expression(Node *node,
 									   CoercionPathType pathtype,
 									   Oid funcId,
 									   Oid targetTypeId, int32 targetTypMod,
 									   CoercionContext ccontext, CoercionForm cformat,
+									   bool *cast_error_found,
 									   int location);
 static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
 									  Oid targetTypeId,
@@ -80,6 +82,19 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
 					  CoercionContext ccontext,
 					  CoercionForm cformat,
 					  int location)
+{
+	return coerce_to_target_type_safe(pstate, expr, exprtype, targettype,
+									  targettypmod, ccontext, cformat,
+									  NULL, location);
+}
+
+Node *
+coerce_to_target_type_safe(ParseState *pstate, Node *expr, Oid exprtype,
+					  Oid targettype, int32 targettypmod,
+					  CoercionContext ccontext,
+					  CoercionForm cformat,
+					  bool *cast_error_found,
+					  int location)
 {
 	Node	   *result;
 	Node	   *origexpr;
@@ -101,19 +116,30 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
 	while (expr && IsA(expr, CollateExpr))
 		expr = (Node *) ((CollateExpr *) expr)->arg;
 
-	result = coerce_type(pstate, expr, exprtype,
-						 targettype, targettypmod,
-						 ccontext, cformat, location);
+	result = coerce_type_safe(pstate, expr, exprtype,
+							  targettype, targettypmod,
+							  ccontext, cformat,
+							  cast_error_found, location);
+
+	/*
+	 * If this coercion failed on bad input, we want to report that to the
+	 * caller.
+	 */
+	if (cast_error_found != NULL && *cast_error_found)
+		return NULL;
 
 	/*
 	 * If the target is a fixed-length type, it may need a length coercion as
 	 * 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,
-								targettype, targettypmod,
+	result = coerce_type_typmod(result, targettype, targettypmod,
 								ccontext, cformat, location,
-								(result != expr && !IsA(result, Const)));
+								(result != expr && !IsA(result, Const)),
+								cast_error_found);
+
+	if (cast_error_found != NULL && *cast_error_found)
+		return NULL;
 
 	if (expr != origexpr && type_is_collatable(targettype))
 	{
@@ -157,6 +183,18 @@ Node *
 coerce_type(ParseState *pstate, Node *node,
 			Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
 			CoercionContext ccontext, CoercionForm cformat, int location)
+{
+	return coerce_type_safe(pstate, node, inputTypeId, targetTypeId,
+							targetTypeMod, ccontext, cformat,
+							NULL, location);
+}
+
+Node *
+coerce_type_safe(ParseState *pstate, Node *node,
+				 Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+				 CoercionContext ccontext, CoercionForm cformat,
+				 bool *cast_error_found,
+				 int location)
 {
 	Node	   *result;
 	CoercionPathType pathtype;
@@ -255,6 +293,7 @@ coerce_type(ParseState *pstate, Node *node,
 		int32		inputTypeMod;
 		Type		baseType;
 		ParseCallbackState pcbstate;
+		bool		coerce_failed = false;
 
 		/*
 		 * If the target type is a domain, we want to call its base type's
@@ -307,21 +346,47 @@ coerce_type(ParseState *pstate, Node *node,
 		 * We assume here that UNKNOWN's internal representation is the same
 		 * as CSTRING.
 		 */
-		if (!con->constisnull)
-			newcon->constvalue = stringTypeDatum(baseType,
-												 DatumGetCString(con->constvalue),
-												 inputTypeMod);
+		if (cast_error_found == NULL)
+		{
+			if (!con->constisnull)
+				newcon->constvalue = stringTypeDatum(baseType,
+													 DatumGetCString(con->constvalue),
+													 inputTypeMod);
+			else
+				newcon->constvalue = stringTypeDatum(baseType,
+													 NULL,
+													 inputTypeMod);
+		}
 		else
-			newcon->constvalue = stringTypeDatum(baseType,
-												 NULL,
-												 inputTypeMod);
+		{
+			/* If we find an error, just carry it up to the caller */
+			Datum val2;
+			bool success;
+
+			if (!con->constisnull)
+				success = stringTypeDatumSafe(baseType,
+											  DatumGetCString(con->constvalue),
+											  inputTypeMod, &val2);
+			else
+				success = stringTypeDatumSafe(baseType, NULL, inputTypeMod,
+											  &val2);
+
+			if (success)
+				newcon->constvalue = val2;
+			else
+			{
+				*cast_error_found = true;
+				coerce_failed = true;
+				result = NULL;
+			}
+		}
 
 		/*
 		 * If it's a varlena value, force it to be in non-expanded
 		 * (non-toasted) format; this avoids any possible dependency on
 		 * external values and improves consistency of representation.
 		 */
-		if (!con->constisnull && newcon->constlen == -1)
+		if (!coerce_failed && !con->constisnull && newcon->constlen == -1)
 			newcon->constvalue =
 				PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
 
@@ -338,32 +403,53 @@ coerce_type(ParseState *pstate, Node *node,
 		 * identical may not get recognized as such.  See pgsql-hackers
 		 * discussion of 2008-04-04.
 		 */
-		if (!con->constisnull && !newcon->constbyval)
+		if (!coerce_failed && !con->constisnull && !newcon->constbyval)
 		{
 			Datum		val2;
 
-			val2 = stringTypeDatum(baseType,
-								   DatumGetCString(con->constvalue),
-								   inputTypeMod);
-			if (newcon->constlen == -1)
-				val2 = PointerGetDatum(PG_DETOAST_DATUM(val2));
-			if (!datumIsEqual(newcon->constvalue, val2, false, newcon->constlen))
-				elog(WARNING, "type %s has unstable input conversion for \"%s\"",
-					 typeTypeName(baseType), DatumGetCString(con->constvalue));
+			if (cast_error_found != NULL)
+			{
+				if (!stringTypeDatumSafe(baseType,
+									     DatumGetCString(con->constvalue),
+									     inputTypeMod, &val2))
+				{
+					coerce_failed = true;
+					*cast_error_found = true;
+					result = NULL;
+				}
+			}
+			else
+				val2 = stringTypeDatum(baseType,
+									   DatumGetCString(con->constvalue),
+									   inputTypeMod);
+			if (!coerce_failed)
+			{
+				if (newcon->constlen == -1)
+					val2 = PointerGetDatum(PG_DETOAST_DATUM(val2));
+				if (!datumIsEqual(newcon->constvalue, val2,
+													false, newcon->constlen))
+					elog(WARNING,
+						 "type %s has unstable input conversion for \"%s\"",
+						 typeTypeName(baseType),
+						 DatumGetCString(con->constvalue));
+			}
 		}
 #endif
 
 		cancel_parser_errposition_callback(&pcbstate);
 
-		result = (Node *) newcon;
+		if (!coerce_failed)
+		{
+			result = (Node *) newcon;
 
-		/* If target is a domain, apply constraints. */
-		if (baseTypeId != targetTypeId)
-			result = coerce_to_domain(result,
-									  baseTypeId, baseTypeMod,
-									  targetTypeId,
-									  ccontext, cformat, location,
-									  false);
+			/* If target is a domain, apply constraints. */
+			if (baseTypeId != targetTypeId)
+				result = coerce_to_domain_safe(result,
+											   baseTypeId, baseTypeMod,
+											   targetTypeId,
+											   ccontext, cformat, location,
+											   false, cast_error_found);
+		}
 
 		ReleaseSysCache(baseType);
 
@@ -396,9 +482,15 @@ coerce_type(ParseState *pstate, Node *node,
 		 */
 		CollateExpr *coll = (CollateExpr *) node;
 
-		result = coerce_type(pstate, (Node *) coll->arg,
-							 inputTypeId, targetTypeId, targetTypeMod,
-							 ccontext, cformat, location);
+		result = coerce_type_safe(pstate, (Node *) coll->arg,
+								  inputTypeId, targetTypeId, targetTypeMod,
+								  ccontext, cformat,
+								  cast_error_found,
+								  location);
+
+		if (cast_error_found != NULL && *cast_error_found)
+			return NULL;
+
 		if (type_is_collatable(targetTypeId))
 		{
 			CollateExpr *newcoll = makeNode(CollateExpr);
@@ -431,17 +523,23 @@ coerce_type(ParseState *pstate, Node *node,
 
 			result = build_coercion_expression(node, pathtype, funcId,
 											   baseTypeId, baseTypeMod,
-											   ccontext, cformat, location);
+											   ccontext, cformat,
+											   cast_error_found,
+											   location);
 
 			/*
 			 * If domain, coerce to the domain type and relabel with domain
 			 * type ID, hiding the previous coercion node.
 			 */
 			if (targetTypeId != baseTypeId)
-				result = coerce_to_domain(result, baseTypeId, baseTypeMod,
+			{
+				result = coerce_to_domain_safe(result, baseTypeId, baseTypeMod,
 										  targetTypeId,
 										  ccontext, cformat, location,
-										  true);
+										  true, cast_error_found);
+				if (cast_error_found != NULL && *cast_error_found)
+					return NULL;
+			}
 		}
 		else
 		{
@@ -454,9 +552,11 @@ 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, InvalidOid, -1, targetTypeId,
+			result = coerce_to_domain_safe(node, InvalidOid, -1, targetTypeId,
 									  ccontext, cformat, location,
-									  false);
+									  false, cast_error_found);
+			if (cast_error_found != NULL && *cast_error_found)
+				return NULL;
 			if (result == node)
 			{
 				/*
@@ -676,6 +776,17 @@ Node *
 coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
 				 CoercionContext ccontext, CoercionForm cformat, int location,
 				 bool hideInputCoercion)
+{
+	return coerce_to_domain_safe(arg, baseTypeId, baseTypeMod, typeId,
+								 ccontext, cformat, location,
+								 hideInputCoercion, NULL);
+}
+
+Node *
+coerce_to_domain_safe(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
+					  CoercionContext ccontext, CoercionForm cformat,
+					  int location, bool hideInputCoercion,
+					  bool *cast_error_found)
 {
 	CoerceToDomain *result;
 
@@ -706,7 +817,7 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
 	 */
 	arg = coerce_type_typmod(arg, baseTypeId, baseTypeMod,
 							 ccontext, COERCE_IMPLICIT_CAST, location,
-							 false);
+							 false, cast_error_found);
 
 	/*
 	 * Now build the domain coercion node.  This represents run-time checking
@@ -753,7 +864,7 @@ static Node *
 coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
 				   CoercionContext ccontext, CoercionForm cformat,
 				   int location,
-				   bool hideInputCoercion)
+				   bool hideInputCoercion, bool *cast_error_found)
 {
 	CoercionPathType pathtype;
 	Oid			funcId;
@@ -780,7 +891,9 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
 	{
 		node = build_coercion_expression(node, pathtype, funcId,
 										 targetTypeId, targetTypMod,
-										 ccontext, cformat, location);
+										 ccontext, cformat,
+										 cast_error_found,
+										 location);
 	}
 	else
 	{
@@ -841,9 +954,10 @@ build_coercion_expression(Node *node,
 						  Oid funcId,
 						  Oid targetTypeId, int32 targetTypMod,
 						  CoercionContext ccontext, CoercionForm cformat,
-						  int location)
+						  bool *cast_error_found, int location)
 {
 	int			nargs = 0;
+	bool		safe_mode = (cast_error_found != NULL);
 
 	if (OidIsValid(funcId))
 	{
@@ -913,13 +1027,14 @@ build_coercion_expression(Node *node,
 		}
 
 		fexpr = makeFuncExpr(funcId, targetTypeId, args,
-							 InvalidOid, InvalidOid, cformat);
+							 InvalidOid, InvalidOid, cformat, safe_mode);
 		fexpr->location = location;
 		return (Node *) fexpr;
 	}
 	else if (pathtype == COERCION_PATH_ARRAYCOERCE)
 	{
 		/* We need to build an ArrayCoerceExpr */
+		/* TODO pass in Safe param */
 		ArrayCoerceExpr *acoerce = makeNode(ArrayCoerceExpr);
 		CaseTestExpr *ctest = makeNode(CaseTestExpr);
 		Oid			sourceBaseTypeId;
@@ -951,14 +1066,20 @@ build_coercion_expression(Node *node,
 		targetElementType = get_element_type(targetTypeId);
 		Assert(OidIsValid(targetElementType));
 
-		elemexpr = coerce_to_target_type(NULL,
+		/*
+		 * No node gets resolved here, so this cast cannot fail
+		 * at parse time.
+		 */
+		elemexpr = coerce_to_target_type_safe(NULL,
 										 (Node *) ctest,
 										 ctest->typeId,
 										 targetElementType,
 										 targetTypMod,
 										 ccontext,
 										 cformat,
+										 cast_error_found,
 										 location);
+
 		if (elemexpr == NULL)	/* shouldn't happen */
 			elog(ERROR, "failed to coerce array element type as expected");
 
@@ -973,6 +1094,7 @@ build_coercion_expression(Node *node,
 		acoerce->resulttypmod = exprTypmod(elemexpr);
 		/* resultcollid will be set by parse_collate.c */
 		acoerce->coerceformat = cformat;
+		acoerce->safe_mode = safe_mode;
 		acoerce->location = location;
 
 		return (Node *) acoerce;
@@ -988,6 +1110,7 @@ build_coercion_expression(Node *node,
 		iocoerce->resulttype = targetTypeId;
 		/* resultcollid will be set by parse_collate.c */
 		iocoerce->coerceformat = cformat;
+		iocoerce->safe_mode = safe_mode;
 		iocoerce->location = location;
 
 		return (Node *) iocoerce;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 150a8099c2..54224b9f06 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -57,7 +57,8 @@ 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 *cast_error_found);
 static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault);
 static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
 static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
@@ -137,7 +138,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:
@@ -1907,7 +1908,8 @@ 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 *cast_error_found)
 {
 	ArrayExpr  *newa = makeNode(ArrayExpr);
 	List	   *newelems = NIL;
@@ -1938,7 +1940,12 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  cast_error_found);
+
+			if (cast_error_found != NULL && *cast_error_found)
+				return NULL;
+
 			/* we certainly have an array here */
 			Assert(array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
@@ -2027,13 +2034,18 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
 		if (coerce_hard)
 		{
-			newe = coerce_to_target_type(pstate, e,
+			newe = coerce_to_target_type_safe(pstate, e,
 										 exprType(e),
 										 coerce_type,
 										 typmod,
 										 COERCION_EXPLICIT,
 										 COERCE_EXPLICIT_CAST,
+										 cast_error_found,
 										 -1);
+
+			if (cast_error_found != NULL && *cast_error_found)
+				return NULL;
+
 			if (newe == NULL)
 				ereport(ERROR,
 						(errcode(ERRCODE_CANNOT_COERCE),
@@ -2105,13 +2117,19 @@ transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c)
 	List	   *newcoercedargs = NIL;
 	ListCell   *args;
 
+
 	foreach(args, c->args)
 	{
 		Node	   *e = (Node *) lfirst(args);
 		Node	   *newe;
 
 		newe = transformExprRecurse(pstate, e);
-		newargs = lappend(newargs, newe);
+		/*
+		 * Some child nodes can return NULL if they would result in a
+		 * safe_mode error. Filter those out here
+		 */
+		if (newe != NULL)
+			newargs = lappend(newargs, newe);
 	}
 
 	newc->coalescetype = select_common_type(pstate, newargs, "COALESCE", NULL);
@@ -2141,6 +2159,7 @@ transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c)
 									exprLocation(pstate->p_last_srf))));
 
 	newc->args = newcoercedargs;
+	newc->op = c->op;
 	newc->location = c->location;
 	return (Node *) newc;
 }
@@ -2345,8 +2364,7 @@ transformXmlSerialize(ParseState *pstate, XmlSerialize *xs)
 	result = coerce_to_target_type(pstate, (Node *) xexpr,
 								   TEXTOID, targetType, targetTypmod,
 								   COERCION_IMPLICIT,
-								   COERCE_IMPLICIT_CAST,
-								   -1);
+								   COERCE_IMPLICIT_CAST, -1);
 	if (result == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_CANNOT_COERCE),
@@ -2524,10 +2542,25 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	Oid			inputType;
 	Oid			targetType;
 	int32		targetTypmod;
-	int			location;
+	int			location = tc->location;
+	TypeName   *typeName = tc->typeName;
+	bool		cast_error;
+	bool	   *cast_error_found;
+
+
+	if (tc->safe_mode)
+	{
+		cast_error = false;
+		cast_error_found = &cast_error;
+	}
+	else
+	{
+		cast_error_found = NULL;
+	}
+
 
 	/* Look up the type name first */
-	typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod);
+	typenameTypeIdAndMod(pstate, typeName, &targetType, &targetTypmod);
 
 	/*
 	 * If the subject of the typecast is an ARRAY[] construct and the target
@@ -2557,7 +2590,16 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 									  (A_ArrayExpr *) arg,
 									  targetBaseType,
 									  elementType,
-									  targetBaseTypmod);
+									  targetBaseTypmod,
+									  cast_error_found);
+
+			/*
+			 * if any element of the tranformation failed, then that means that the
+			 * larger cast has failed. Returning NULL here is safe when the
+			 * parent node (CoalesceExpr) knows to look for it (and filter it out).
+			 */
+			if (cast_error_found != NULL && *cast_error_found)
+				return NULL;
 		}
 		else
 			expr = transformExprRecurse(pstate, arg);
@@ -2574,15 +2616,24 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	 * CAST symbol, but if there is none then use the location of the type
 	 * name (this can happen in TypeName 'string' syntax, for instance).
 	 */
-	location = tc->location;
 	if (location < 0)
-		location = tc->typeName->location;
+		location = typeName->location;
+
+	result = coerce_to_target_type_safe(pstate, expr, inputType,
+										targetType, targetTypmod,
+										COERCION_EXPLICIT,
+										COERCE_EXPLICIT_CAST,
+										cast_error_found,
+										location);
+
+	/*
+	 * If this typecast reported a cast error, and the cast failed outright,
+	 * then we want to replace this node entirely with the default,
+	 * and we signal the parent node to do that by returning NULL.
+	 */
+	if (cast_error_found != NULL && *cast_error_found)
+		return NULL;
 
-	result = coerce_to_target_type(pstate, expr, inputType,
-								   targetType, targetTypmod,
-								   COERCION_EXPLICIT,
-								   COERCE_EXPLICIT_CAST,
-								   location);
 	if (result == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_CANNOT_COERCE),
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 29643fb4ab..92a3c60955 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -4049,7 +4049,8 @@ get_qual_for_hash(Relation parent, PartitionBoundSpec *spec)
 						 args,
 						 InvalidOid,
 						 InvalidOid,
-						 COERCE_EXPLICIT_CALL);
+						 COERCE_EXPLICIT_CALL,
+						 NULL);
 
 	return list_make1(fexpr);
 }
diff --git a/src/backend/rewrite/rewriteSearchCycle.c b/src/backend/rewrite/rewriteSearchCycle.c
index 58f684cd52..ae93c06702 100644
--- a/src/backend/rewrite/rewriteSearchCycle.c
+++ b/src/backend/rewrite/rewriteSearchCycle.c
@@ -191,7 +191,7 @@ make_path_cat_expr(RowExpr *rowexpr, AttrNumber path_varattno)
 	fexpr = makeFuncExpr(F_ARRAY_CAT, RECORDARRAYOID,
 						 list_make2(makeVar(1, path_varattno, RECORDARRAYOID, -1, 0, 0),
 									arr),
-						 InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+						 InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL, NULL);
 
 	return (Expr *) fexpr;
 }
@@ -521,7 +521,7 @@ rewriteSearchAndCycle(CommonTableExpr *cte)
 			fs->resulttype = INT8OID;
 			fs->resulttypmod = -1;
 
-			fexpr = makeFuncExpr(F_INT8INC, INT8OID, list_make1(fs), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+			fexpr = makeFuncExpr(F_INT8INC, INT8OID, list_make1(fs), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL, NULL);
 
 			lfirst(list_head(search_col_rowexpr->args)) = fexpr;
 
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 0557302b92..3ab5f8b000 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -138,6 +138,7 @@ typedef enum ExprEvalOp
 	/* conditional jumps based on current result value */
 	EEOP_JUMP_IF_NULL,
 	EEOP_JUMP_IF_NOT_NULL,
+	EEOP_JUMP_IF_NOT_ERROR,
 	EEOP_JUMP_IF_NOT_TRUE,
 
 	/* perform NULL tests for scalar values */
@@ -273,6 +274,7 @@ typedef struct ExprEvalStep
 	/* where to store the result of this step */
 	Datum	   *resvalue;
 	bool	   *resnull;
+	bool	   *reserror;
 
 	/*
 	 * Inline data for the operation.  Inline data is faster to access, but
@@ -395,6 +397,7 @@ typedef struct ExprEvalStep
 		{
 			Datum	   *value;	/* value to return */
 			bool	   *isnull;
+			bool	   *iserror;
 		}			casetest;
 
 		/* for EEOP_MAKE_READONLY */
@@ -402,6 +405,7 @@ typedef struct ExprEvalStep
 		{
 			Datum	   *value;	/* value to coerce to read-only */
 			bool	   *isnull;
+			bool	   *iserror;
 		}			make_readonly;
 
 		/* for EEOP_IOCOERCE */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9a64a830a2..fb9b2f7963 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -95,6 +95,9 @@ typedef struct ExprState
 #define FIELDNO_EXPRSTATE_RESULTSLOT 4
 	TupleTableSlot *resultslot;
 
+#define FIELDNO_EXPRSTATE_RESERROR 5
+	bool		reserror;
+
 	/*
 	 * Instructions to compute expression's return value.
 	 */
@@ -126,6 +129,7 @@ typedef struct ExprState
 
 	Datum	   *innermost_caseval;
 	bool	   *innermost_casenull;
+	bool	   *innermost_caseerror;
 
 	Datum	   *innermost_domainval;
 	bool	   *innermost_domainnull;
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 50de4c62af..50a9bdde59 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -77,7 +77,8 @@ extern ColumnDef *makeColumnDef(const char *colname,
 								Oid typeOid, int32 typmod, Oid collOid);
 
 extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
-							  Oid funccollid, Oid inputcollid, CoercionForm fformat);
+							  Oid funccollid, Oid inputcollid,
+							  CoercionForm fformat, bool safe_mode);
 
 extern FuncCall *makeFuncCall(List *name, List *args,
 							  CoercionForm funcformat, int location);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8fe9b2fcfe..c4ee9c46d0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -340,6 +340,7 @@ typedef struct TypeCast
 	NodeTag		type;
 	Node	   *arg;			/* the expression being casted */
 	TypeName   *typeName;		/* the target type */
+	bool		safe_mode;		/* cast can ereturn error vs ereport */
 	int			location;		/* token location, or -1 if unknown */
 } TypeCast;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 74f228d959..2ea23914b1 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -604,6 +604,7 @@ typedef struct FuncExpr
 	Oid			funccollid;		/* OID of collation of result */
 	Oid			inputcollid;	/* OID of collation that function should use */
 	List	   *args;			/* arguments to the function */
+	bool		safe_mode;		/* use safe mode */
 	int			location;		/* token location, or -1 if unknown */
 } FuncExpr;
 
@@ -1023,6 +1024,7 @@ typedef struct CoerceViaIO
 	/* output typmod is not stored, but is presumed -1 */
 	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
 	CoercionForm coerceformat;	/* how to display this node */
+	bool		safe_mode;		/* use safe mode */
 	int			location;		/* token location, or -1 if unknown */
 } CoerceViaIO;
 
@@ -1048,6 +1050,7 @@ typedef struct ArrayCoerceExpr
 	int32		resulttypmod;	/* output typmod (also element typmod) */
 	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
 	CoercionForm coerceformat;	/* how to display this node */
+	bool		safe_mode;		/* use safe mode */
 	int			location;		/* token location, or -1 if unknown */
 } ArrayCoerceExpr;
 
@@ -1260,8 +1263,14 @@ typedef struct RowCompareExpr
 	List	   *rargs;			/* the right-hand input arguments */
 } RowCompareExpr;
 
+typedef enum CoalesceOp
+{
+	NULL_TEST,					/* Test for NULL, like SQL COALECE() */
+	ERROR_TEST					/* Test for error flag */
+} CoalesceOp;
+
 /*
- * CoalesceExpr - a COALESCE expression
+ * CoalesceExpr - a COALESCE expression or OnError expression
  */
 typedef struct CoalesceExpr
 {
@@ -1269,6 +1278,7 @@ typedef struct CoalesceExpr
 	Oid			coalescetype;	/* type of expression result */
 	Oid			coalescecollid; /* OID of collation, or InvalidOid if none */
 	List	   *args;			/* the arguments */
+	CoalesceOp	op;				/* test for NULL or test for ERROR */
 	int			location;		/* token location, or -1 if unknown */
 } CoalesceExpr;
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 957ee18d84..ed7d670819 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -151,6 +151,7 @@ PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("end", END_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD, AS_LABEL)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index ddbc995077..db8e15f225 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,15 +43,30 @@ extern Node *coerce_to_target_type(ParseState *pstate,
 								   CoercionContext ccontext,
 								   CoercionForm cformat,
 								   int location);
+extern Node *coerce_to_target_type_safe(ParseState *pstate,
+										Node *expr, Oid exprtype,
+										Oid targettype, int32 targettypmod,
+										CoercionContext ccontext,
+										CoercionForm cformat,
+										bool *cast_error_found,
+										int location);
 extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
 							CoercionContext ccontext);
 extern Node *coerce_type(ParseState *pstate, Node *node,
 						 Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
 						 CoercionContext ccontext, CoercionForm cformat, int location);
+extern Node *coerce_type_safe(ParseState *pstate, Node *node,
+							  Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+							  CoercionContext ccontext, CoercionForm cformat,
+							  bool *cast_error_found, int location);
 extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
 							  Oid typeId,
 							  CoercionContext ccontext, CoercionForm cformat, int location,
 							  bool hideInputCoercion);
+extern Node *coerce_to_domain_safe(Node *arg, Oid baseTypeId, int32 baseTypeMod,
+								   Oid typeId,
+								   CoercionContext ccontext, CoercionForm cformat, int location,
+								   bool hideInputCoercion, bool *cast_error_found);
 
 extern Node *coerce_to_boolean(ParseState *pstate, Node *node,
 							   const char *constructName);
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 0000000000..87cd1c101b
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,40 @@
+/* ON ERROR behavior */
+VALUES (CAST('error' AS integer));
+ERROR:  invalid input syntax for type integer: "error"
+LINE 2: VALUES (CAST('error' AS integer));
+                     ^
+VALUES (CAST('error' AS integer ERROR ON ERROR));
+ERROR:  invalid input syntax for type integer: "error"
+LINE 1: VALUES (CAST('error' AS integer ERROR ON ERROR));
+                     ^
+VALUES (CAST('error' AS integer NULL ON ERROR));
+ column1 
+---------
+        
+(1 row)
+
+VALUES (CAST('error' AS integer DEFAULT 42 ON ERROR));
+ column1 
+---------
+      42
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON ERROR) as array_test1;
+ array_test1 
+-------------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR) as array_test2;
+ array_test2 
+-------------
+ {-1011}
+(1 row)
+
+SELECT CAST(u.arg AS integer DEFAULT -1 ON ERROR) AS unnest_test1 FROM unnest('{345,ghi,678}'::text[]) AS u(arg);
+ unnest_test1 
+--------------
+          345
+           -1
+          678
+(3 rows)
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 9a139f1e24..1d49b9b95f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -41,7 +41,7 @@ test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comment
 # execute two copy tests in parallel, to check that copy itself
 # is concurrent safe.
 # ----------
-test: copy copyselect copydml insert insert_conflict
+test: copy copyselect copydml insert insert_conflict cast
 
 # ----------
 # More groups of parallel tests
diff --git a/src/test/regress/sql/cast.sql b/src/test/regress/sql/cast.sql
new file mode 100644
index 0000000000..fab8cb7d33
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,11 @@
+/* ON ERROR behavior */
+
+VALUES (CAST('error' AS integer));
+VALUES (CAST('error' AS integer ERROR ON ERROR));
+VALUES (CAST('error' AS integer NULL ON ERROR));
+VALUES (CAST('error' AS integer DEFAULT 42 ON ERROR));
+
+SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON ERROR) as array_test1;
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR) as array_test2;
+
+SELECT CAST(u.arg AS integer DEFAULT -1 ON ERROR) AS unnest_test1 FROM unnest('{345,ghi,678}'::text[]) AS u(arg);
-- 
2.38.1

---

From: jian he <[email-masked]>
Date: Tue, 22 Jul 2025 09:59:19 +0800

hi.
more preparation work has been committed.

1. SQL/JSON patch [1] added keyword ERROR
2. CoerceViaIo, CoerceToDomain can be evaluated error safe. see commit [2].
3. ExprState added ErrorSaveContext point, so before calling ExecInitExprRec
set valid ErrorSaveContext for ExprState->escontext we should evaluate
expression error softly.
see commit [2] also.

I only found oracle implement, [3].
Based on my reading of [4], it seems CAST(EXPRESSION AS TYPE DEFAULT
def_expr ON ERROR)
is not included in SQL:2023.

anyway, just share my POC based on the previous patch in this thread.
it will work for domain over composite, composite over domain.
example:
CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON ERROR);
--return NULL

[1]: https://git.postgresql.org/cgit/postgresql.git/diff/src/backend/parser/gram.y?id=6185c9737cf48c9540782d88f12bd2912d6ca1cc
[2]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
[3] https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/CAST.html
[4] https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new
From 47c181eee593468c3d7b7cb57aec3a1ea8cb3c1d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 18 Jul 2025 13:00:19 +0800
Subject: [PATCH v1 1/2] make ArrayCoerceExpr error safe

similar to https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
---
 src/backend/executor/execExpr.c       | 3 +++
 src/backend/executor/execExprInterp.c | 4 ++++
 src/backend/utils/adt/arrayfuncs.c    | 7 +++++++
 3 files changed, 14 insertions(+)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..1f3f899874f 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1702,6 +1702,9 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
 				elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
 
+				if (state->escontext != NULL)
+					elemstate->escontext = state->escontext;
+
 				ExecInitExprRec(acoerce->elemexpr, elemstate,
 								&elemstate->resvalue, &elemstate->resnull);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 1a37737d4a2..81e46cff725 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3644,6 +3644,10 @@ 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;
 }
 
 /*
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index c8f53c6fbe7..b5f98bf22f9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3288,6 +3288,13 @@ array_map(Datum arrayd,
 		/* Apply the given expression to source element */
 		values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
 
+		if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+		{
+			pfree(values);
+			pfree(nulls);
+			return (Datum) 0;
+		}
+
 		if (nulls[i])
 			hasnulls = true;
 		else
-- 
2.34.1

From be7e2bb7cecd1a24ebcce9266759a385dfdf55a1 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sun, 20 Jul 2025 22:11:59 +0800
Subject: [PATCH v1 2/2] CAST(expr AS newtype DEFAULT ON ERROR)

Type cast nodes are generally one of: FuncExpr, CoerceViaIO, or ArrayCoerceExpr;
see build_coercion_expression.  We've already made CoerceViaIO error safe[0]; we
just need to teach it to evaluate default expression after coercion failures.
ArrayCoerceExpr is also handling errors safely.
However, FuncExpr can't be made error-safe directly (see example like int84), so
instead, we construct a CoerceViaIO node using the source expression as its
argument.

[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
context: https://postgr.es/m/CADkLM=[email-masked]
discussion: https://postgr.es/m/

demo:
SELECT CAST('1' AS date  DEFAULT '2011-01-01' ON ERROR),
       CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR);

    date    |  int4
------------+---------
 2011-01-01 | {-1011}
---
 src/backend/executor/execExpr.c       | 110 ++++++++++++++
 src/backend/executor/execExprInterp.c |  30 ++++
 src/backend/nodes/nodeFuncs.c         |  57 +++++++
 src/backend/optimizer/util/clauses.c  |  24 +++
 src/backend/parser/gram.y             |  31 +++-
 src/backend/parser/parse_expr.c       | 205 ++++++++++++++++++++++++++
 src/backend/parser/parse_target.c     |  14 ++
 src/backend/parser/parse_type.c       |  13 ++
 src/backend/utils/adt/arrayfuncs.c    |   6 +
 src/backend/utils/adt/ruleutils.c     |  15 ++
 src/backend/utils/fmgr/fmgr.c         |  13 ++
 src/include/executor/execExpr.h       |   8 +
 src/include/fmgr.h                    |   3 +
 src/include/nodes/execnodes.h         |  17 +++
 src/include/nodes/parsenodes.h        |   6 +
 src/include/nodes/primnodes.h         |  33 +++++
 src/include/parser/parse_type.h       |   2 +
 src/test/regress/expected/misc.out    | 127 ++++++++++++++++
 src/test/regress/sql/misc.sql         |  54 +++++++
 src/tools/pgindent/typedefs.list      |   3 +
 20 files changed, 769 insertions(+), 2 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 1f3f899874f..5085821c016 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -99,6 +99,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
 static void ExecInitJsonExpr(JsonExpr *jsexpr, 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,
@@ -1702,6 +1705,9 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
 				elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
 
+				if (state->escontext != NULL)
+					elemstate->escontext = state->escontext;
+
 				if (state->escontext != NULL)
 					elemstate->escontext = state->escontext;
 
@@ -2180,6 +2186,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr   *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				ExecInitSafeTypeCastExpr(stcexpr, state, resv, resnull, &scratch);
+				break;
+			}
+
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesce = (CoalesceExpr *) node;
@@ -4744,6 +4758,102 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary expressions.
+ * We already handle CoerceViaIO, CoerceToDomain, and ArrayCoerceExpr error
+ * softly.  However, FuncExpr (e.g., int84) cannot be made error-safe.
+ * In such cases, we wrap the source expression and target type information into
+ * a CoerceViaIO node instead.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+						 Datum *resv, bool *resnull,
+						 ExprEvalStep *scratch)
+{
+	/*
+	 * If coercion to the target type fails, fall back to the default expression
+	 * specified in the ON ERROR clause.
+	*/
+	if (stcexpr->cast_expr == NULL)
+	{
+		ExecInitExprRec((Expr *) stcexpr->default_expr,
+						state, resv, resnull);
+		return;
+	}
+	else if (stcexpr->binarycoercible)
+	{
+		/*
+		 * source type binary coerceable with target type, just evaulate the
+		 * source expression
+		 */
+		ExecInitExprRec((Expr *) stcexpr->source_expr,
+						state, resv, resnull);
+		return;
+	}
+	else
+	{
+		CoerceViaIO *newexpr;
+		SafeTypeCastState *stcstate;
+		ErrorSaveContext *escontext;
+		ErrorSaveContext *saved_escontext;
+		List	   *jumps_to_end = NIL;
+
+		stcstate = palloc0(sizeof(SafeTypeCastState));
+		stcstate->stcexpr = stcexpr;
+		stcstate->escontext.type = T_ErrorSaveContext;
+		escontext = &stcstate->escontext;
+		state->escontext = escontext;
+
+		Assert(stcexpr->cast_expr);
+		/* evaluate argument expression into step's result area */
+		if (IsA(stcexpr->cast_expr, CoerceViaIO) ||
+			IsA(stcexpr->cast_expr, CoerceToDomain) ||
+			IsA(stcexpr->cast_expr, ArrayCoerceExpr))
+			ExecInitExprRec((Expr *) stcexpr->cast_expr,
+							state, resv, resnull);
+		else
+		{
+			newexpr = makeNode(CoerceViaIO);
+			newexpr->arg = (Expr *) stcexpr->source_expr;
+			newexpr->resulttype = stcexpr->resulttype;
+			newexpr->resultcollid = exprCollation(stcexpr->source_expr);
+			newexpr->coerceformat = COERCE_EXPLICIT_CAST;
+			newexpr->location = exprLocation(stcexpr->source_expr);
+
+			ExecInitExprRec((Expr *) newexpr,
+							state, resv, resnull);
+		}
+
+		scratch->opcode = EEOP_SAFETYPE_CAST;
+		scratch->d.stcexpr.stcstate = stcstate;
+		ExprEvalPushStep(state, scratch);
+
+		stcstate->jump_error = state->steps_len;
+		/* JUMP to end if false, that is, skip the ON ERROR expression. */
+		jumps_to_end = lappend_int(jumps_to_end, state->steps_len);
+		scratch->opcode = EEOP_JUMP_IF_NOT_TRUE;
+		scratch->resvalue = &stcstate->error.value;
+		scratch->resnull = &stcstate->error.isnull;
+		scratch->d.jump.jumpdone = -1;	/* set below */
+		ExprEvalPushStep(state, scratch);
+
+		/* Steps to evaluate the ON ERROR expression */
+		saved_escontext = state->escontext;
+		state->escontext = NULL;
+		ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+						state, resv, resnull);
+		state->escontext = saved_escontext;
+
+		foreach_int(lc, jumps_to_end)
+		{
+			ExprEvalStep *as = &state->steps[lc];
+
+			as->d.jump.jumpdone = state->steps_len;
+		}
+		stcstate->jump_end = state->steps_len;
+	}
+}
+
 /*
  * 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 81e46cff725..f31b67a47e0 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -568,6 +568,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,
@@ -1926,6 +1927,11 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SAFETYPE_CAST)
+		{
+			EEO_JUMP(ExecEvalSafeTypeCast(state, op));
+		}
+
 		EEO_CASE(EEOP_JSONEXPR_PATH)
 		{
 			/* too complex for an inline implementation */
@@ -5187,6 +5193,30 @@ GetJsonBehaviorValueString(JsonBehavior *behavior)
 	return pstrdup(behavior_names[behavior->btype]);
 }
 
+int
+ExecEvalSafeTypeCast(ExprState *state, ExprEvalStep *op)
+{
+	SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+	if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+	{
+		*op->resvalue = (Datum) 0;
+		*op->resnull = true;
+
+		stcstate->error.value = BoolGetDatum(true);
+
+		/*
+		 * Reset for next use such as for catching errors when coercing a
+		 * JsonBehavior expression.
+		 */
+		stcstate->escontext.error_occurred = false;
+		stcstate->escontext.details_wanted = false;
+
+		return stcstate->jump_error;
+	}
+	return stcstate->jump_end;
+}
+
 /*
  * Checks if an error occurred in ExecEvalJsonCoercion().  If so, this sets
  * JsonExprState.error to trigger the ON ERROR handling steps, unless the
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..5da7bd5d88f 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -206,6 +206,9 @@ exprType(const Node *expr)
 		case T_RowCompareExpr:
 			type = BOOLOID;
 			break;
+		case T_SafeTypeCastExpr:
+			type = ((const SafeTypeCastExpr *) expr)->resulttype;
+			break;
 		case T_CoalesceExpr:
 			type = ((const CoalesceExpr *) expr)->coalescetype;
 			break;
@@ -450,6 +453,8 @@ exprTypmod(const Node *expr)
 				return typmod;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			return ((const SafeTypeCastExpr *) expr)->resulttypmod;
 		case T_CoalesceExpr:
 			{
 				/*
@@ -965,6 +970,9 @@ 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_CoalesceExpr:
 			coll = ((const CoalesceExpr *) expr)->coalescecollid;
 			break;
@@ -1232,6 +1240,9 @@ 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_CoalesceExpr:
 			((CoalesceExpr *) expr)->coalescecollid = collation;
 			break;
@@ -1554,6 +1565,15 @@ exprLocation(const Node *expr)
 			/* just use leftmost argument's location */
 			loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs);
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				const SafeTypeCastExpr *cast_expr = (const SafeTypeCastExpr *) expr;
+				if (cast_expr->cast_expr)
+					loc = exprLocation(cast_expr->cast_expr);
+				else
+					loc = exprLocation(cast_expr->default_expr);
+				break;
+			}
 		case T_CoalesceExpr:
 			/* COALESCE keyword should always be the first thing */
 			loc = ((const CoalesceExpr *) expr)->location;
@@ -2325,6 +2345,18 @@ expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr   *scexpr = (SafeTypeCastExpr *) node;
+
+				if (WALK(scexpr->source_expr))
+					return true;
+				if (WALK(scexpr->cast_expr))
+					return true;
+				if (WALK(scexpr->default_expr))
+					return true;
+			}
+			break;
 		case T_CoalesceExpr:
 			return WALK(((CoalesceExpr *) node)->args);
 		case T_MinMaxExpr:
@@ -3334,6 +3366,19 @@ expression_tree_mutator_impl(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr   *scexpr = (SafeTypeCastExpr *) node;
+				SafeTypeCastExpr   *newnode;
+
+				FLATCOPY(newnode, scexpr, SafeTypeCastExpr);
+				MUTATE(newnode->source_expr, scexpr->source_expr, Node *);
+				MUTATE(newnode->cast_expr, scexpr->cast_expr, Node *);
+				MUTATE(newnode->default_expr, scexpr->default_expr, Node *);
+
+				return (Node *) newnode;
+			}
+			break;
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
@@ -4468,6 +4513,18 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr   *stc = (SafeTypeCastExpr *) node;
+
+				if (WALK(stc->source_expr))
+					return true;
+				if (WALK(stc->cast_expr))
+					return true;
+				if (WALK(stc->default_expr))
+					return true;
+			}
+			break;
 		case T_CollateClause:
 			return WALK(((CollateClause *) node)->arg);
 		case T_SortBy:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index f45131c34c5..df781c444fd 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2938,6 +2938,30 @@ eval_const_expressions_mutator(Node *node,
 												  copyObject(jve->format));
 			}
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stc = (SafeTypeCastExpr *) node;
+				SafeTypeCastExpr	   *newexpr;
+				Node	   *source_expr = stc->source_expr;
+				Node	   *default_expr = stc->default_expr;
+
+				/*
+				 * We can not fold cast_expr to a constant now, it may error
+				 * out.  E.g. CAST(1 AS DATE DEFAULT NULL ON ERROR).
+				 */
+				source_expr = eval_const_expressions_mutator(source_expr, context);
+				default_expr = eval_const_expressions_mutator(default_expr, context);
+
+				newexpr = makeNode(SafeTypeCastExpr);
+				newexpr->source_expr = source_expr;
+				newexpr->cast_expr = stc->cast_expr;
+				newexpr->default_expr = default_expr;
+				newexpr->binarycoercible = stc->binarycoercible;
+				newexpr->resulttype = stc->resulttype;
+				newexpr->resulttypmod = stc->resulttypmod;
+				newexpr->resultcollid = stc->resultcollid;
+				return (Node *) newexpr;
+			}
 		case T_SubPlan:
 		case T_AlternativeSubPlan:
 
index 73345bb3c70..7e16b5fc9f3 100644
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -15943,8 +15945,25 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_CURRENT_SCHEMA, -1, @1);
 				}
-			| CAST '(' a_expr AS Typename ')'
-				{ $$ = makeTypeCast($3, $5, @1); }
+			| CAST '(' a_expr AS Typename cast_on_error_clause ')'
+				{
+					TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+					if ($6 == NULL)
+						$$ = (Node *) cast;
+					else
+					{
+						SafeTypeCast *safecast = makeNode(SafeTypeCast);
+
+						safecast->cast = (Node *) cast;
+						safecast->expr = $6;
+
+						/*
+						 * On-error actions must themselves be typecast to the
+						 * same type as the original expression.
+						 */
+						$$ = (Node *) safecast;
+					}
+				}
 			| EXTRACT '(' extract_list ')'
 				{
 					$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -16330,6 +16349,14 @@ func_expr_common_subexpr:
 				}
 			;

 /*
  * SQL/XML support
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c6..92998087e67 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/timestamp.h"
 #include "utils/xml.h"
 
@@ -76,6 +77,7 @@ static Node *transformWholeRowRef(ParseState *pstate,
 								  int sublevels_up, int location);
 static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
+static Node *transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
 											JsonObjectConstructor *ctor);
@@ -170,6 +172,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			result = transformTypeCast(pstate, (TypeCast *) expr);
 			break;
 
+		case T_SafeTypeCast:
+			result = transformTypeSafeCast(pstate, (SafeTypeCast *) expr);
+			break;
+
 		case T_CollateClause:
 			result = transformCollateClause(pstate, (CollateClause *) expr);
 			break;
@@ -2779,6 +2785,205 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	return result;
 }
 
+
+/*
+ * Recursively checks if the given expression, or its sub-node in some cases,
+ * is valid for using as an ON ERROR / ON EMPTY DEFAULT expression.
+ */
+static bool
+ValidCastDefaultExpr(Node *expr, void *context)
+{
+	if (expr == NULL)
+		return false;
+
+	switch (nodeTag(expr))
+	{
+			/* Acceptable expression nodes */
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			return true;
+
+			/* Acceptable iff arg of the following nodes is one of the above */
+		case T_CoerceViaIO:
+		case T_CoerceToDomain:
+		case T_ArrayCoerceExpr:
+		case T_ConvertRowtypeExpr:
+		case T_RelabelType:
+			return expression_tree_walker(expr, ValidCastDefaultExpr,
+										  context);
+		default:
+			break;
+	}
+
+	return false;
+}
+
+
+/*
+ * Handle an explicit CAST construct.
+ *
+ * Transform the argument, look up the type name, and apply any necessary
+ * coercion function(s).
+ * must do via coerce via IO.
+ * See transformJsonFuncExpr, ExecInitJsonExpr
+ * maybe we also need a JsonExpr Node represent
+ */
+static Node *
+transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc)
+{
+	SafeTypeCastExpr   *result;
+	Node	   *def_expr;
+	Node	   *cast_expr = NULL;
+	Oid			inputType;
+	Oid			targetType;
+	int32		targetTypmod;
+	int			location;
+	Node	   *source_expr = NULL;
+	TypeCast   *tcast = (TypeCast *) tc->cast;
+	Node	   *tc_arg = tcast->arg;
+	bool		can_coerce = true;
+	int			def_expr_loc = -1;
+
+	result = makeNode(SafeTypeCastExpr);
+
+	/* Look up the type name first */
+	typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+
+	result->resulttype = targetType;
+	result->resulttypmod = targetTypmod;
+
+	/* now looking at cast fail default expression */
+	def_expr_loc = exprLocation(tc->expr);
+	def_expr = transformExprRecurse(pstate, tc->expr);
+
+	if (IsA(def_expr, CollateExpr))
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("DEFAULT expression can not as COLLATE clause"),
+				parser_errposition(pstate, def_expr_loc));
+	if (!ValidCastDefaultExpr(def_expr, NULL))
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("can only specify a constant, non-aggregate function, or operator expression for DEFAULT"),
+				parser_errposition(pstate, def_expr_loc));
+	if (contain_var_clause(def_expr))
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("DEFAULT expression must not contain column references"),
+				parser_errposition(pstate, def_expr_loc));
+	if (expression_returns_set(def_expr))
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("DEFAULT expression must not return a set"),
+				parser_errposition(pstate, def_expr_loc));
+
+	def_expr = coerce_to_target_type(pstate, def_expr, exprType(def_expr),
+									 targetType, targetTypmod,
+									 COERCION_EXPLICIT,
+									 COERCE_EXPLICIT_CAST,
+									 exprLocation(def_expr));
+	if (def_expr == NULL)
+		ereport(ERROR,
+				errcode(ERRCODE_CANNOT_COERCE),
+				errmsg("cannot cast on_error default expression to type %s",
+						format_type_be(targetType)),
+				parser_coercion_errposition(pstate, def_expr_loc, def_expr));
+
+	/*
+	 * 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.  Otherwise, just
+	 * transform the argument normally.
+	 */
+	if (IsA(tc_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))
+		{
+			source_expr = transformArrayExpr(pstate,
+										   (A_ArrayExpr *) tc_arg,
+										   targetBaseType,
+										   elementType,
+										   targetBaseTypmod);
+		}
+		else
+			source_expr = transformExprRecurse(pstate, tc_arg);
+	}
+	else
+		source_expr = transformExprRecurse(pstate, tc_arg);
+
+	inputType = exprType(source_expr);
+	if (inputType == InvalidOid)
+		return (Node *) result;			/* do nothing if NULL input */
+
+	/*
+	 * Location of the coercion is preferentially the location of the :: or
+	 * CAST symbol, but if there is none then use the location of the type
+	 * name (this can happen in TypeName 'string' syntax, for instance).
+	 */
+	location = tcast->location;
+	if (location < 0)
+		location = tcast->typeName->location;
+
+	if (exprType(source_expr) == UNKNOWNOID && IsA(source_expr, Const))
+	{
+		Const	   *con = (Const *) source_expr;
+		int32		inputTypeMod;
+		bool		converted;
+		Datum		datum;
+		Type		baseType;
+		baseType = typeidType(targetType);
+
+		if (targetType == INTERVALOID)
+			inputTypeMod = targetTypmod;
+		else
+			inputTypeMod = -1;
+
+		if (!con->constisnull)
+			converted = stringTypeDatumSafe(baseType,
+											DatumGetCString(con->constvalue),
+											inputTypeMod,
+											&datum);
+		else
+			converted = stringTypeDatumSafe(baseType,
+											NULL,
+											inputTypeMod,
+											&datum);
+		if(!converted)
+			can_coerce = false;
+
+		ReleaseSysCache(baseType);
+	}
+
+	if (can_coerce)
+		cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+										  targetType, targetTypmod,
+										  COERCION_EXPLICIT,
+										  COERCE_EXPLICIT_CAST,
+										  location);
+
+	result->source_expr = source_expr;
+	result->cast_expr = cast_expr;
+	result->default_expr = def_expr;
+	result->binarycoercible = IsBinaryCoercible(inputType, targetType);
+
+	return (Node *) result;
+}
+
 /*
  * Handle an explicit COLLATE clause.
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..812ed18c162 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1823,6 +1823,20 @@ FigureColnameInternal(Node *node, char **name)
 				}
 			}
 			break;
+		case T_SafeTypeCast:
+			strength = FigureColnameInternal(((SafeTypeCast *) node)->cast,
+											 name);
+			if (strength <= 1)
+			{
+				TypeCast *node_cast;
+				node_cast = (TypeCast *)((SafeTypeCast *) node)->cast;
+				if (node_cast->typeName != NULL)
+				{
+					*name = strVal(llast(node_cast->typeName->names));
+					return 1;
+				}
+			}
+			break;
 		case T_CollateClause:
 			return FigureColnameInternal(((CollateClause *) node)->arg, name);
 		case T_GroupingFunc:
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..02e5f9c92d7 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "utils/array.h"
@@ -660,6 +661,18 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
 	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
 }
 
+bool
+stringTypeDatumSafe(Type tp, char *string, int32 atttypmod, Datum *result)
+{
+	Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+	Oid			typinput = typform->typinput;
+	Oid			typioparam = getTypeIOParam(tp);
+	ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+	return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod,
+									(fmNodePtr) &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/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index b5f98bf22f9..6bd8a989dbd 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3295,6 +3295,12 @@ array_map(Datum arrayd,
 			return (Datum) 0;
 		}
 
+		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 3d6e6bdbfd2..eb47bc336b2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10534,6 +10534,21 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr   *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				appendStringInfoString(buf, "CAST(");
+				get_rule_expr(stcexpr->source_expr, context, showimplicit);
+
+				appendStringInfo(buf, " AS %s ",
+								 format_type_with_typemod(stcexpr->resulttype, stcexpr->resulttypmod));
+
+				appendStringInfoString(buf, "DEFAULT ");
+				get_rule_expr(stcexpr->default_expr, context, showimplicit);
+				appendStringInfoString(buf, " ON ERROR)");
+			}
+			break;
 		case T_JsonExpr:
 			{
 				JsonExpr   *jexpr = (JsonExpr *) node;
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 782291d9998..9de895e682f 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1759,6 +1759,19 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
 	return InputFunctionCall(&flinfo, str, typioparam, typmod);
 }
 
+bool
+OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+						 int32 typmod, fmNodePtr escontext,
+						 Datum *result)
+{
+	FmgrInfo			flinfo;
+
+	fmgr_info(functionId, &flinfo);
+
+	return InputFunctionCallSafe(&flinfo, str, typioparam, typmod,
+								 escontext, result);
+}
+
 char *
 OidOutputFunctionCall(Oid functionId, Datum val)
 {
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..0afcf09c086 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -265,6 +265,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,
@@ -750,6 +751,12 @@ typedef struct ExprEvalStep
 			JsonIsPredicate *pred;	/* original expression node */
 		}			is_json;
 
+		/* for EEOP_SAFECAST */
+		struct
+		{
+			struct SafeTypeCastState *stcstate;	/* original expression node */
+		}			stcexpr;
+
 		/* for EEOP_JSONEXPR_PATH */
 		struct
 		{
@@ -892,6 +899,7 @@ extern int	ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op,
 								 ExprContext *econtext);
 extern void ExecEvalJsonCoercion(ExprState *state, ExprEvalStep *op,
 								 ExprContext *econtext);
+int ExecEvalSafeTypeCast(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalMergeSupportFunc(ExprState *state, ExprEvalStep *op,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 0fe7b4ebc77..299d4eef4ed 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -750,6 +750,9 @@ extern bool DirectInputFunctionCallSafe(PGFunction func, char *str,
 										Datum *result);
 extern Datum OidInputFunctionCall(Oid functionId, char *str,
 								  Oid typioparam, int32 typmod);
+extern bool OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+									 int32 typmod, fmNodePtr escontext,
+									 Datum *result);
 extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
 extern char *OidOutputFunctionCall(Oid functionId, Datum val);
 extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, fmStringInfo buf,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e107d6e5f81..fd26e1c98b6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1058,6 +1058,23 @@ typedef struct DomainConstraintState
 	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;
 
+typedef struct SafeTypeCastState
+{
+	NodeTag		type;
+
+	SafeTypeCastExpr *stcexpr;
+
+	/* Set to true if source_expr evaluation cause an error.  */
+	NullableDatum error;
+
+	int 		jump_error;
+
+	int 		jump_end;
+
+	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 86a236bd58b..95174e0feef 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -399,6 +399,12 @@ typedef struct TypeCast
 	ParseLoc	location;		/* token location, or -1 if unknown */
 } TypeCast;
 
+typedef struct SafeTypeCast
+{
+	NodeTag		type;
+	Node		*cast;
+	Node	   	*expr;		/* default expr */
+} SafeTypeCast;
 /*
  * CollateClause - a COLLATE expression
  */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..a02eef27800 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -756,6 +756,39 @@ typedef enum CoercionForm
 	COERCE_SQL_SYNTAX,			/* display with SQL-mandated special syntax */
 } CoercionForm;
 
+/*
+ * SafeTypeCastExpr -
+ *		Transformed representation of
+ * CAST(expr AS typename DEFAULT expr2 ON ERROR)
+ * CAST(expr AS typename NULL ON ERROR)
+ */
+typedef struct SafeTypeCastExpr
+{
+	Expr		xpr;
+
+	/* transformed expression being casted */
+	Node	   *source_expr;
+
+	/* transformed cast expression, it maybe NULL! */
+	Node	   *cast_expr;
+
+	/* in case cast expression evaulation failed, fallback default expression */
+	Node	   *default_expr;
+
+	/* does source type binary coerceable with target type */
+	bool	   	binarycoercible;
+
+	/* cast result data type */
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
+
+	/* cast result data type typmod (usually -1) */
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
+
+	/* cast result data type collation (usually -1) */
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
+
+} SafeTypeCastExpr;
+
 /*
  * FuncExpr - expression node for a function call
  *
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..12381aed64c 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, char *string, int32 atttypmod);
+extern bool stringTypeDatumSafe(Type tp, char *string, int32 atttypmod,
+								Datum *result);
 
 extern Oid	typeidTypeRelid(Oid type_id);
 extern Oid	typeOrDomainTypeRelid(Oid type_id);
diff --git a/src/test/regress/expected/misc.out b/src/test/regress/expected/misc.out
index 6e816c57f1f..a23e847f268 100644
--- a/src/test/regress/expected/misc.out
+++ b/src/test/regress/expected/misc.out
@@ -396,3 +396,130 @@ SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
 --
 -- rewrite rules
 --
+-- CAST DEFAULT ON ERROR
+VALUES (CAST('error' AS integer ERROR ON ERROR));
+ERROR:  invalid input syntax for type integer: "error"
+LINE 1: VALUES (CAST('error' AS integer ERROR ON ERROR));
+                     ^
+VALUES (CAST('error' AS integer NULL ON ERROR));
+ column1 
+---------
+        
+(1 row)
+
+VALUES (CAST('error' AS integer DEFAULT 42 ON ERROR));
+ column1 
+---------
+      42
+(1 row)
+
+SELECT CAST(1 AS int8 DEFAULT NULL ON ERROR);
+ int8 
+------
+    1
+(1 row)
+
+CREATE OR REPLACE FUNCTION ret_int8() RETURNS bigint AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON ERROR); --error
+ERROR:  integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON ERROR); --error
+ERROR:  cannot cast on_error default expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON ERROR);
+                                        ^
+-- test valid DEFAULT expression for CAST = ON ERROR
+CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
+$$
+BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_setint() ON ERROR) FROM hobbies_r; --error
+ERROR:  DEFAULT expression must not return a set
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON ERROR) FROM h...
+                                       ^
+SELECT CAST('1' as text DEFAULT '2' collate "C" ON ERROR); --error
+ERROR:  DEFAULT expression can not as COLLATE clause
+LINE 1: SELECT CAST('1' as text DEFAULT '2' collate "C" ON ERROR);
+                                        ^
+SELECT CAST('a' as int DEFAULT sum(1) ON ERROR); --error
+ERROR:  can only specify a constant, non-aggregate function, or operator expression for DEFAULT
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) ON ERROR);
+                                       ^
+SELECT CAST('a' as int DEFAULT 'b' ON ERROR); --error
+ERROR:  invalid input syntax for type integer: "b"
+LINE 1: SELECT CAST('a' as int DEFAULT 'b' ON ERROR);
+                                       ^
+SELECT CAST('a' as text DEFAULT name || 'j' ON ERROR) FROM hobbies_r; --error
+ERROR:  DEFAULT expression must not contain column references
+LINE 1: SELECT CAST('a' as text DEFAULT name || 'j' ON ERROR) FROM h...
+                                        ^
+-- test array coerce
+SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON ERROR) as array_test1;
+ array_test1 
+-------------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR) as array_test2;
+ array_test2 
+-------------
+ {-1011}
+(1 row)
+
+SELECT CAST(u.arg AS integer DEFAULT -1 ON ERROR) AS unnest_test1 FROM unnest('{345,ghi,678}'::text[]) AS u(arg);
+ unnest_test1 
+--------------
+          345
+           -1
+          678
+(3 rows)
+
+-- test with domain
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON ERROR); --error
+ERROR:  value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON ERROR); --ok
+ d_int42 
+---------
+      42
+(1 row)
+
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON ERROR); --error
+ERROR:  domain d_int42 does not allow null values
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON ERROR); --ok
+ d_int42 
+---------
+      42
+(1 row)
+
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON ERROR);
+ comp_domain_with_typmod 
+-------------------------
+ 
+(1 row)
+
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON ERROR);
+ comp_domain_with_typmod 
+-------------------------
+ ("1  ",2)
+(1 row)
+
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON ERROR); --error
+ERROR:  value too long for type character(3)
+LINE 1: ...ST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)'...
+                                                             ^
+-- test deparse
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON ERROR),
+       CAST('a' as int DEFAULT random(min=>1, max=>1::int) ON ERROR) as safecast;
+\sv safecastview
+CREATE OR REPLACE VIEW public.safecastview AS
+ SELECT CAST('1234' AS character(3) DEFAULT '-1111'::integer::character(3) ON ERROR) AS bpchar,
+    CAST('a' AS integer DEFAULT random(min => 1, max => 1) ON ERROR) AS safecast
+CREATE INdEX cast_error_idx  ON hobbies_r((cast(name as int DEFAULT random(min=>1, max=>1) ON ERROR))); --error
+ERROR:  functions in index expression must be marked IMMUTABLE
diff --git a/src/test/regress/sql/misc.sql b/src/test/regress/sql/misc.sql
index 165a2e175fb..d9b3368b099 100644
--- a/src/test/regress/sql/misc.sql
+++ b/src/test/regress/sql/misc.sql
@@ -273,3 +273,57 @@ SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
 --
 -- rewrite rules
 --
+
+-- CAST DEFAULT ON ERROR
+VALUES (CAST('error' AS integer ERROR ON ERROR));
+VALUES (CAST('error' AS integer NULL ON ERROR));
+VALUES (CAST('error' AS integer DEFAULT 42 ON ERROR));
+SELECT CAST(1 AS int8 DEFAULT NULL ON ERROR);
+
+CREATE OR REPLACE FUNCTION ret_int8() RETURNS bigint AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON ERROR); --error
+
+-- test valid DEFAULT expression for CAST = ON ERROR
+CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
+$$
+BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_setint() ON ERROR) FROM hobbies_r; --error
+
+SELECT CAST('1' as text DEFAULT '2' collate "C" ON ERROR); --error
+SELECT CAST('a' as int DEFAULT sum(1) ON ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON ERROR); --error
+SELECT CAST('a' as text DEFAULT name || 'j' ON ERROR) FROM hobbies_r; --error
+
+-- test array coerce
+SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON ERROR) as array_test1;
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR) as array_test2;
+SELECT CAST(u.arg AS integer DEFAULT -1 ON ERROR) AS unnest_test1 FROM unnest('{345,ghi,678}'::text[]) AS u(arg);
+
+-- test with domain
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON ERROR); --ok
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON ERROR); --ok
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON ERROR); --error
+
+-- test deparse
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON ERROR),
+       CAST('a' as int DEFAULT random(min=>1, max=>1::int) ON ERROR) as safecast;
+\sv safecastview
+
+CREATE INdEX cast_error_idx  ON hobbies_r((cast(name as int DEFAULT random(min=>1, max=>1) ON ERROR))); --error
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ff050e93a50..2e79046d83b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2648,6 +2648,9 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

---

From: Vik Fearing <[email-masked]>
Date: Tue, 22 Jul 2025 08:45:15 +0200

On 22/07/2025 03:59, jian he wrote:
> [4]https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new

It was accepted into the standard after 2023 was released.  I am the 
author of this change in the standard, so feel free to ask me anything 
you're unsure about.

-- 

Vik Fearing

---

From: jian he <[email-masked]>
Date: Tue, 22 Jul 2025 18:19:18 +0800

> author of this change in the standard, so feel free to ask me anything

is the generally syntax as mentioned in this thread:
CAST(source_expression AS target_type DEFAULT default_expression ON ERROR)

if so, what's the restriction of default_expression?

---

From: Vik Fearing <[email-masked]>
Date: Tue, 22 Jul 2025 14:26:25 +0200

> if so, what's the restriction of default_expression?

The actual syntax is:

<cast specification> ::=
     CAST <left paren>
         <cast operand> AS <cast target>
         [ FORMAT <cast template> ]
         [ <cast error behavior> ON CONVERSION ERROR ]
         <right paren>

"CONVERSION" is probably a noise word, but it is there because A) Oracle 
wanted it there, and B) it makes sense because if the <cast error 
behavior> fails, that is still a failure of the entire CAST.

The <cast error behavior> is:

<cast error behavior> ::=
     ERROR
   | NULL
   | DEFAULT <value expression>

but I am planning on removing the NULL variant in favor of having the 
<value expression> be a <contextually typed value specification>.  So it 
would be either ERROR ON CONVERSION ERROR (postgres's current behavior), 
or DEFAULT NULL ON CONVERSION ERROR.

An example of B) above would be: CAST('five' AS INTEGER DEFAULT 'six' ON 
CONVERSION ERROR).  'six' is no more an integer than 'five' is, so that 
would error out because the conversion error does not happen on the 
operand but on the default clause. CAST('five' AS INTEGER DEFAULT 6 ON 
CONVERSION ERROR) would work.

-- 

Vik Fearing

---

From: Vik Fearing <[email-masked]>
Date: Tue, 22 Jul 2025 14:45:42 +0200

On 22/07/2025 14:26, Vik Fearing wrote:
> <value expression> be a <contextually typed value specification>.  So 
> it would be either ERROR ON CONVERSION ERROR (postgres's current 
> behavior), or DEFAULT NULL ON CONVERSION ERROR. 

Sorry, I meant <implicitly typed value specification>.

The point being that CAST(ARRAY['1', '2', 'three'] AS INTEGER ARRAY 
DEFAULT NULL ON CONVERSION ERROR) will give you (CAST NULL AS INTEGER 
ARRAY) and *not* ARRAY[1, 2, NULL].

-- 

Vik Fearing

---

From: Corey Huinker <[email-masked]>
Date: Tue, 22 Jul 2025 11:55:33 -0400

On Tue, Jul 22, 2025 at 2:45 AM Vik Fearing <[email-masked]> wrote:

> > [4]
> https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new
>
>
That's excellent news. I was already planning on retrying this for v19, but
I'll try sooner now.

---

From: jian he <[email-masked]>
Date: Thu, 24 Jul 2025 09:22:19 +0800

hi.

>    | DEFAULT <value expression>

for <value expression>
I disallow it from returning a set, or using aggregate or window functions.
For example, the following three cases will fail:

+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) --error
(ret_setint function is warped as (select 1 union all select 2))

for array coerce, which you already mentioned, i think the following
is what we expected.
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON
CONVERSION ERROR);
+  int4
+---------
+ {-1011}
+(1 row)

I didn't implement the [ FORMAT <cast template> ] part for now.
please check the attached regress test and tests expected result.

---

From: jian he <[email-masked]>
Date: Thu, 24 Jul 2025 21:44:22 +0800

hi.

just want to confirm my understanding of ``[ FORMAT <cast template> ]``.

SELECT CAST('2022-13-32' AS DATE FORMAT 'YYYY-MM-DD' DEFAULT NULL ON
CONVERSION ERROR);
will return NULL.
because  ``SELECT to_date('2022-13-32', 'YYYY-MM-DD');``
will error out, so the above query will fall back to the DEFAULT
expression evaluation.

---

From: Vik Fearing <[email-masked]>
Date: Thu, 24 Jul 2025 16:10:16 +0200

> +SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error

This seems like an arbitrary restriction.  Can you explain why this is 
necessary?  Those same expressions are allowed as the <cast operand>.

> (ret_setint function is warped as (select 1 union all select 2))

This makes sense to me.

> +SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON

Yes, that looks correct to me.

> I didn't implement the [ FORMAT <cast template> ] part for now.

That is fine, since it's separate feature

> please check the attached regress test and tests expected result.

Except for the weird restriction on the default value, this all looks 
good to me (with the usual caveat that I am not an expert in C).

Are you planning to also implement the <castable predicate>?

-- 

Vik Fearing

---

From: Vik Fearing <[email-masked]>
Date: Thu, 24 Jul 2025 16:19:34 +0200

> will error out, so the above query will fall back to the DEFAULT

That is correct.  Any error produced during typecasting will fall back 
to the DEFAULT value.  If not supplied, the behavior is ERROR ON ERROR 
as it currently is.

Any error produced while converting the DEFAULT value to the requested 
type is raised as an error.

-- 

Vik Fearing

---

From: Corey Huinker <[email-masked]>
Date: Wed, 30 Jul 2025 15:14:58 -0400

> please check the attached regress test and tests expected result.

Question about this:

+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
expressions.
+ * We already handle CoerceViaIO, CoerceToDomain, and ArrayCoerceExpr error
+ * softly.  However, FuncExpr (e.g., int84) cannot be made error-safe.
+ * In such cases, we wrap the source expression and target type
information into
+ * a CoerceViaIO node instead.
+ */

I'm not sure we _can_ just fall back to the CoerceViaIO if there is a
defined cast from TypeA -> TypeB. I seem to recall there was some reason we
couldn't do that, possibly to do with how it handled rounding, but I have
no clear memory of it.

Aside from that, I like what you've done with making SafeTypeCastExpr be
its own node type and not saddling regular typecasts with the overhead.

---

From: jian he <[email-masked]>
Date: Fri, 1 Aug 2025 13:55:44 +0800

On Thu, Jul 31, 2025 at 3:15 AM Corey Huinker <[email-masked]> wrote:
> I'm not sure we _can_ just fall back to the CoerceViaIO if there is a defined cast from TypeA -> TypeB. I seem to recall there was some reason we couldn't do that, possibly to do with how it handled rounding, but I have no clear memory of it.
>

indeed.
select ('11.1'::numeric::int);
return 11, but '11.1' string can not coerce to int 11. So in this
case, we can not use CoerceViaIO.

so we need to handle numeric source types with fractional points with
special care.
currently, this applies only to numeric, float4, and float8.
(hope this is all the corner case we need to catch...)

select castsource::regtype, casttarget::regtype, castfunc::regproc, castcontext
from   pg_cast pc
where  castsource::regtype = ANY('{numeric, float4, float8}'::regtype[])
and    castmethod = 'f';

only return 17 rows. one row is cast numreic to money, function numeric_cash.
numeric_cash seems more trickly to be error safe, because it will call
numeric_mul.
so I made these 16 function errors safe.
see v3-0001-make-some-numeric-cast-function-error-safe.patch
From 0fa2361b7d7692a9c032d8a8a7f2b04ab4302849 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 18 Jul 2025 13:00:19 +0800
Subject: [PATCH v3 2/3] make ArrayCoerceExpr error safe

 

 /*

From 798db5f9a05a5398c36641af0e33505311939330 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 1 Aug 2025 13:22:59 +0800
Subject: [PATCH v3 1/3] make some numeric cast function error safe

The following function are changed to make it error safe.
numeric_int2
numeric_int4
numeric_int8
numeric_float4
numeric_float8
dtoi8
dtoi4
dtoi2
ftoi8
ftoi4
ftoi2
ftod
dtof
float4_numeric
float8_numeric
numeric (oid 1703)

This is need for evaulation CAST(... DEFAULT... ON CONVERSION ERROR).
discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/float.c   | 16 +++++---
 src/backend/utils/adt/int8.c    |  4 +-
 src/backend/utils/adt/numeric.c | 67 +++++++++++++++++++++++++++------
 3 files changed, 67 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..6461e9c94b1 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,13 @@ dtof(PG_FUNCTION_ARGS)
 
 	result = (float4) num;
 	if (unlikely(isinf(result)) && !isinf(num))
-		float_overflow_error();
+		ereturn(fcinfo->context, (Datum) 0,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: overflow"));
 	if (unlikely(result == 0.0f) && num != 0.0)
-		float_underflow_error();
+		ereturn(fcinfo->context, (Datum) 0,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: underflow"));
 
 	PG_RETURN_FLOAT4(result);
 }
@@ -1224,7 +1228,7 @@ dtoi4(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num)))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("integer out of range")));
 
@@ -1249,7 +1253,7 @@ dtoi2(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num)))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
@@ -1298,7 +1302,7 @@ ftoi4(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num)))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("integer out of range")));
 
@@ -1323,7 +1327,7 @@ ftoi2(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num)))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 9dd5889f34c..4b9a7620671 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1307,7 +1307,7 @@ dtoi8(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num)))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("bigint out of range")));
 
@@ -1342,7 +1342,7 @@ ftoi8(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num)))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("bigint out of range")));
 
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index c9233565d57..cf498627a9a 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1261,7 +1261,8 @@ numeric		(PG_FUNCTION_ARGS)
 	 */
 	if (NUMERIC_IS_SPECIAL(num))
 	{
-		(void) apply_typmod_special(num, typmod, NULL);
+		if (!apply_typmod_special(num, typmod, fcinfo->context))
+			PG_RETURN_NULL();
 		PG_RETURN_NUMERIC(duplicate_numeric(num));
 	}
 
@@ -4564,7 +4565,22 @@ numeric_int4(PG_FUNCTION_ARGS)
 {
 	Numeric		num = PG_GETARG_NUMERIC(0);
 
-	PG_RETURN_INT32(numeric_int4_opt_error(num, NULL));
+	if (likely(fcinfo->context == NULL))
+		PG_RETURN_INT32(numeric_int4_opt_error(num, NULL));
+	else
+	{
+		bool	has_error;
+		int32	result;
+		Node	*escontext = fcinfo->context;
+
+		result = numeric_int4_opt_error(num, &has_error);
+		if (has_error)
+			ereturn(escontext, (Datum) 0,
+					errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					errmsg("integer out of range"));
+
+		PG_RETURN_INT32(result);
+	}
 }
 
 /*
@@ -4652,7 +4668,22 @@ numeric_int8(PG_FUNCTION_ARGS)
 {
 	Numeric		num = PG_GETARG_NUMERIC(0);
 
-	PG_RETURN_INT64(numeric_int8_opt_error(num, NULL));
+	if (likely(fcinfo->context == NULL))
+		PG_RETURN_INT64(numeric_int8_opt_error(num, NULL));
+	else
+	{
+		bool	has_error;
+		int64	result;
+		Node	*escontext = fcinfo->context;
+
+		result = numeric_int8_opt_error(num, &has_error);
+		if (has_error)
+			ereturn(escontext, (Datum) 0,
+					errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					errmsg("bigint out of range"));
+
+		PG_RETURN_INT64(result);
+	}
 }

@@ -4669,6 +4700,7 @@ Datum
 numeric_int2(PG_FUNCTION_ARGS)
 {
 	Numeric		num = PG_GETARG_NUMERIC(0);
+	Node	   *escontext = fcinfo->context;
 	NumericVar	x;
 	int64		val;
 	int16		result;
@@ -4676,11 +4708,11 @@ numeric_int2(PG_FUNCTION_ARGS)
 	if (NUMERIC_IS_SPECIAL(num))
 	{
 		if (NUMERIC_IS_NAN(num))
-			ereport(ERROR,
+			ereturn(escontext, (Datum) 0,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert NaN to %s", "smallint")));
 		else
-			ereport(ERROR,
+			ereturn(escontext, (Datum) 0,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert infinity to %s", "smallint")));
 	}
@@ -4689,12 +4721,12 @@ numeric_int2(PG_FUNCTION_ARGS)
 	init_var_from_num(num, &x);
 
 	if (!numericvar_to_int64(&x, &val))
-		ereport(ERROR,
+		ereturn(escontext, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
 	if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
-		ereport(ERROR,
+		ereturn(escontext, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
@@ -4759,10 +4791,14 @@ numeric_float8(PG_FUNCTION_ARGS)
 
 	tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
 											  NumericGetDatum(num)));
-
-	result = DirectFunctionCall1(float8in, CStringGetDatum(tmp));
-
-	pfree(tmp);
+	if (!DirectInputFunctionCallSafe(float8in, tmp,
+									 InvalidOid, -1,
+									 (Node *) fcinfo->context,
+									 &result))
+	{
+		pfree(tmp);
+		PG_RETURN_NULL();
+	}
 
 	PG_RETURN_DATUM(result);
 }
@@ -4854,7 +4890,14 @@ numeric_float4(PG_FUNCTION_ARGS)
 	tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
 											  NumericGetDatum(num)));
 
-	result = DirectFunctionCall1(float4in, CStringGetDatum(tmp));
+	if (!DirectInputFunctionCallSafe(float4in, tmp,
+									 InvalidOid, -1,
+									 (Node *) fcinfo->context,
+									 &result))
+	{
+		pfree(tmp);
+		PG_RETURN_NULL();
+	}
 
 	pfree(tmp);
 
-- 
2.34.1

From 6741e1ca2473f10d9d0f2bb3ec81eed6886f2787 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 1 Aug 2025 13:42:55 +0800
Subject: [PATCH v3 3/3] CAST(expr AS newtype DEFAULT ON ERROR)

When casting data types, we generally uses one of three node types: FuncExpr,
CoerceViaIO, or ArrayCoerceExpr represent the type coercion.

* CoerceViaIO is already error-safe, see[0]
* ArrayCoerceExpr is also error safe, see previous commit.
* However, not all **FuncExpr** nodes is error-safe. For example, a function
like int84 is not error safe. In these situations, we create a CoerceViaIO node
and use the original expression as its argument, allowing us to safely handle
the cast.

Now that the type coercion node is error-safe, we also need to ensure that when
a coercion fails, it falls back to evaluating the default node.

[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
discussion: https://postgr.es/m/CADkLM=[email-masked]

demo:

    date    |  int4
------------+---------
 2011-01-01 | {-1011}
---
 src/backend/executor/execExpr.c       | 117 +++++++++-
 src/backend/executor/execExprInterp.c |  30 +++
 src/backend/nodes/nodeFuncs.c         |  57 +++++
 src/backend/nodes/queryjumblefuncs.c  |  14 ++
 src/backend/optimizer/util/clauses.c  |  19 ++
 src/backend/parser/gram.y             |  31 ++-
 src/backend/parser/parse_expr.c       | 316 +++++++++++++++++++++++++-
 src/backend/parser/parse_target.c     |  14 ++
 src/backend/parser/parse_type.c       |  13 ++
 src/backend/utils/adt/arrayfuncs.c    |   6 +
 src/backend/utils/adt/ruleutils.c     |  15 ++
 src/backend/utils/fmgr/fmgr.c         |  13 ++
 src/include/executor/execExpr.h       |   8 +
 src/include/fmgr.h                    |   3 +
 src/include/nodes/execnodes.h         |  30 +++
 src/include/nodes/parsenodes.h        |   6 +
 src/include/nodes/primnodes.h         |  35 +++
 src/include/parser/parse_type.h       |   2 +
 src/test/regress/expected/cast.out    | 230 +++++++++++++++++++
 src/test/regress/parallel_schedule    |   2 +-
 src/test/regress/sql/cast.sql         | 109 +++++++++
 src/tools/pgindent/typedefs.list      |   3 +
 22 files changed, 1063 insertions(+), 10 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

index 1f3f899874f..03cafad525d 100644
@@ -2180,6 +2183,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
@@ -2746,7 +2757,7 @@ 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;
@@ -4744,6 +4755,110 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions. We already handle errors softly for coercion nodes like
+ * CoerceViaIO, CoerceToDomain, ArrayCoerceExpr, and some FuncExprs. However,
+ * most of FuncExprs node (for example, int84) is not error-safe. For these
+ * cases, we instead wrap the source expression and target type information
+ * within a CoerceViaIO node.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+						 Datum *resv, bool *resnull,
+						 ExprEvalStep *scratch)
+{
+	/*
+	 * If coercion to the target type fails, fallback to the default expression
+	 * specified in the ON CONVERSION ERROR clause.
+	*/
+	if (stcexpr->cast_expr == NULL)
+	{
+		ExecInitExprRec((Expr *) stcexpr->default_expr,
+						state, resv, resnull);
+		return;
+	}
+	else
+	{
+		CoerceViaIO *newexpr;
+		SafeTypeCastState *stcstate;
+		ErrorSaveContext *escontext;
+		ErrorSaveContext *saved_escontext;
+		List	   *jumps_to_end = NIL;
+		bool		numeric_fraction = false;
+		Oid			source_oid;
+
+		stcstate = palloc0(sizeof(SafeTypeCastState));
+		stcstate->stcexpr = stcexpr;
+		stcstate->escontext.type = T_ErrorSaveContext;
+		escontext = &stcstate->escontext;
+		state->escontext = escontext;
+
+		source_oid = getBaseType(exprType(stcexpr->source_expr));
+
+		/*
+		 * We can't use CoerceViaIO to safely cast numeric values with
+		 * fractional parts to other numerical types, because of rounding
+		 * issues. For example, '11.1'::NUMERIC::INT would fail because '11.1'
+		 * isn't a valid string representation for an integer.  Instead, we
+		 * should use FuncExpr for these cases.
+		*/
+		if (source_oid == NUMERICOID ||
+			source_oid == FLOAT4OID ||
+			source_oid == FLOAT8OID)
+			numeric_fraction = true;
+
+		/* evaluate argument expression into step's result area */
+		if (IsA(stcexpr->cast_expr, CoerceViaIO) ||
+			IsA(stcexpr->cast_expr, CoerceToDomain) ||
+			IsA(stcexpr->cast_expr, ArrayCoerceExpr) ||
+			IsA(stcexpr->cast_expr, Var) ||
+			numeric_fraction)
+			ExecInitExprRec((Expr *) stcexpr->cast_expr,
+							state, resv, resnull);
+		else
+		{
+			newexpr = makeNode(CoerceViaIO);
+			newexpr->arg = (Expr *) stcexpr->source_expr;
+			newexpr->resulttype = stcexpr->resulttype;
+			newexpr->resultcollid = exprCollation(stcexpr->source_expr);
+			newexpr->coerceformat = COERCE_EXPLICIT_CAST;
+			newexpr->location = exprLocation(stcexpr->source_expr);
+
+			ExecInitExprRec((Expr *) newexpr,
+							state, resv, resnull);
+		}
+
+		scratch->opcode = EEOP_SAFETYPE_CAST;
+		scratch->d.stcexpr.stcstate = stcstate;
+		ExprEvalPushStep(state, scratch);
+
+		stcstate->jump_error = state->steps_len;
+		/* JUMP to end if false, that is, skip the ON ERROR expression. */
+		jumps_to_end = lappend_int(jumps_to_end, state->steps_len);
+		scratch->opcode = EEOP_JUMP_IF_NOT_TRUE;
+		scratch->resvalue = &stcstate->error.value;
+		scratch->resnull = &stcstate->error.isnull;
+		scratch->d.jump.jumpdone = -1;	/* set below */
+		ExprEvalPushStep(state, scratch);
+
+		/* Steps to evaluate the ON ERROR expression */
+		saved_escontext = state->escontext;
+		state->escontext = NULL;
+		ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+						state, resv, resnull);
+		state->escontext = saved_escontext;
+
+		foreach_int(lc, jumps_to_end)
+		{
+			ExprEvalStep *as = &state->steps[lc];
+
+			as->d.jump.jumpdone = state->steps_len;
+		}
+		stcstate->jump_end = state->steps_len;
+	}
+}
+
 /*
  * 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 81e46cff725..98e8a9bde62 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -568,6 +568,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,
@@ -1926,6 +1927,11 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}

+int
+		 * stcexpr expression.
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 31f97151977..76426c88e9a 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -74,6 +74,7 @@ static void _jumbleElements(JumbleState *jstate, List *elements, Node *node);
 static void _jumbleParam(JumbleState *jstate, Node *node);
 static void _jumbleA_Const(JumbleState *jstate, Node *node);
 static void _jumbleVariableSetStmt(JumbleState *jstate, Node *node);
+static void _jumbleSafeTypeCastExpr(JumbleState *jstate, Node *node);
 static void _jumbleRangeTblEntry_eref(JumbleState *jstate,
 									  RangeTblEntry *rte,
 									  Alias *expr);
@@ -758,6 +759,19 @@ _jumbleVariableSetStmt(JumbleState *jstate, Node *node)
 	JUMBLE_LOCATION(location);
 }
 
+static void
+_jumbleSafeTypeCastExpr(JumbleState *jstate, Node *node)
+{
+	SafeTypeCastExpr *expr = (SafeTypeCastExpr *) node;
+
+	if (expr->cast_expr == NULL)
+		JUMBLE_NODE(source_expr);
+	else
+		JUMBLE_NODE(cast_expr);
+
+	JUMBLE_NODE(default_expr);
+}
+
 /*
  * Custom query jumble function for RangeTblEntry.eref.
  */
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6f0b338d2cd..c04ca88b0fb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2942,6 +2942,25 @@ eval_const_expressions_mutator(Node *node,
 												  copyObject(jve->format));
 			}

index db43034b9db..20293d75524 100644
 
@@ -15931,8 +15933,25 @@ func_expr_common_subexpr:
@@ -16318,6 +16337,14 @@ func_expr_common_subexpr:
 				}
 			;
 
+cast_on_error_clause: cast_on_error_action ON CONVERSION_P ERROR_P { $$ = $1; }
 
 /*
  * SQL/XML support
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c6..2f5a3059aca 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "miscadmin.h"
@@ -37,6 +38,7 @@
 #include "utils/date.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/timestamp.h"
 #include "utils/xml.h"
 
@@ -60,7 +62,10 @@ 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 *can_coerce);
+static Node *transformArrayExprSafe(ParseState *pstate, A_ArrayExpr *a,
+									Oid array_type, Oid element_type, int32 typmod);
 static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault);
 static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
 static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
@@ -76,6 +81,7 @@ static Node *transformWholeRowRef(ParseState *pstate,
 								  int sublevels_up, int location);
 static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
+static Node *transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
 											JsonObjectConstructor *ctor);
@@ -106,6 +112,8 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
 							  Node *ltree, Node *rtree, int location);
 static Node *make_nulltest_from_distinct(ParseState *pstate,
 										 A_Expr *distincta, Node *arg);
+static bool CovertUnknownConstSafe(ParseState *pstate, Node *node,
+								   Oid targetType, int32 targetTypeMod);

 /*
@@ -163,13 +171,17 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
+			result = transformTypeSafeCast(pstate, (SafeTypeCast *) expr);
+			break;
+
 		case T_CollateClause:
 			result = transformCollateClause(pstate, (CollateClause *) expr);
 			break;
@@ -2004,16 +2016,127 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 	return result;
 }
 
+/*
+ * Return true iff successfuly coerced a Unknown Const to targetType
+*/
+static bool CovertUnknownConstSafe(ParseState *pstate, Node *node,
+								   Oid targetType, int32 targetTypeMod)
+{
+	Oid			baseTypeId;
+	int32		baseTypeMod;
+	int32		inputTypeMod;
+	Type		baseType;
+	char		*string;
+	Datum		datum;
+	bool		converted;
+	Const	   *con;
+
+	Assert(IsA(node, Const));
+	Assert(exprType(node) == UNKNOWNOID);
+
+	con = (Const *) node;
+	baseTypeMod = targetTypeMod;
+	baseTypeId = getBaseTypeAndTypmod(targetType, &baseTypeMod);
+
+	if (baseTypeId == INTERVALOID)
+		inputTypeMod = baseTypeMod;
+	else
+		inputTypeMod = -1;
+	baseType = typeidType(baseTypeId);
+
+	/*
+	 * We assume here that UNKNOWN's internal representation is the same as
+	 * CSTRING.
+	*/
+	if (!con->constisnull)
+		string = DatumGetCString(con->constvalue);
+	else
+		string = NULL;
+
+	converted = stringTypeDatumSafe(baseType,
+									string,
+									inputTypeMod,
+									&datum);
+
+	ReleaseSysCache(baseType);
+
+	return converted;
+}
+
+/*
+ * As with transformArrayExpr, we need to correctly parse back a query like
+ * CAST(ARRAY['three', 'a'] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR).  We
+ * cannot allow eval_const_expressions to fold the A_ArrayExpr into a Const
+ * node, as this may cause an error too early. The A_ArrayExpr still need
+ * transformed into an ArrayExpr for the deparse purpose.
+ */
+static Node *
+transformArrayExprSafe(ParseState *pstate, A_ArrayExpr *a,
+					   Oid array_type, Oid element_type, int32 typmod)
+{
+	ArrayExpr  *newa = makeNode(ArrayExpr);
+	List	   *newelems = NIL;
+	ListCell   *element;
+
+	newa->multidims = false;
+	foreach(element, a->elements)
+	{
+		Node	   *e = (Node *) lfirst(element);
+		Node	   *newe;
+
+		/*
+		 * If an element is itself an A_ArrayExpr, recurse directly so that we
+		 * can pass down any target type we were given.
+		 */
+		if (IsA(e, A_ArrayExpr))
+		{
+			newe = transformArrayExprSafe(pstate,(A_ArrayExpr *) e, array_type, element_type, typmod);
+			/* we certainly have an array here */
+			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			newa->multidims = true;
+		}
+		else
+		{
+			newe = transformExprRecurse(pstate, e);
+
+			if (!newa->multidims)
+			{
+				Oid			newetype = exprType(newe);
+
+				if (newetype != INT2VECTOROID && newetype != OIDVECTOROID &&
+					type_is_array(newetype))
+					newa->multidims = true;
+			}
+		}
+
+		newelems = lappend(newelems, newe);
+	}
+
+	newa->array_typeid = array_type;
+	/* array_collid will be set by parse_collate.c */
+	newa->element_typeid = element_type;
+	newa->elements = newelems;
+	newa->list_start = a->list_start;
+	newa->list_end = -1;
+	newa->location = -1;
+
+	return (Node *) newa;
+}
+
 /*
  * transformArrayExpr
  *
  * If the caller specifies the target type, the resulting array will
  * be of exactly that type.  Otherwise we try to infer a common type
  * for the elements using select_common_type().
+ *
+ * can_coerce is not null only when CAST(DEFAULT... ON CONVERSION ERROR) is
+ * specified. If we found out we can not cast to target type and can_coerce is
+ * not null, return NULL earlier and set can_coerce set false.
  */
 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 *can_coerce)
 {
 	ArrayExpr  *newa = makeNode(ArrayExpr);
 	List	   *newelems = NIL;
@@ -2044,9 +2167,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  can_coerce);
 			/* we certainly have an array here */
-			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			Assert(can_coerce || array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
 		}
 		else
@@ -2072,6 +2196,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		newelems = lappend(newelems, newe);
 	}
 
+	if (can_coerce && !*can_coerce)
+		return NULL;
+
 	/*
 	 * Select a target type for the elements.
 	 *
@@ -2139,6 +2266,17 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		Node	   *e = (Node *) lfirst(element);
 		Node	   *newe;
 
+		if (can_coerce && (*can_coerce) && IsA(e, Const) && exprType(e) == UNKNOWNOID)
+		{
+			if (!CovertUnknownConstSafe(pstate, e, coerce_type, typmod))
+			{
+				*can_coerce = false;
+				list_free(newcoercedelems);
+				newcoercedelems = NIL;
+				return NULL;
+			}
+		}
+
 		if (coerce_hard)
 		{
 			newe = coerce_to_target_type(pstate, e,
@@ -2742,7 +2880,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 									  (A_ArrayExpr *) arg,
 									  targetBaseType,
 									  elementType,
-									  targetBaseTypmod);
+									  targetBaseTypmod,
+									  NULL);
 		}
 		else
 			expr = transformExprRecurse(pstate, arg);
@@ -2779,6 +2918,171 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	return result;
 }
 
+
+/*
+ * Handle an explicit CAST(... DEFAULT ... ON CONVERSION ERROR) construct.
+ *
+ * Transform SafeTypeCast node, look up the type name, and apply any necessary
+ * coercion function(s).
+ */
+static Node *
+transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc)
+{
+	SafeTypeCastExpr   *result;
+	Node	   *def_expr;
+	Node	   *cast_expr = NULL;
+	Node	   *source_expr = NULL;
+	Node	   *array_expr = NULL;
+	Oid			inputType = InvalidOid;
+	Oid			targetType;
+	int32		targetTypmod;
+	int			location;
+	TypeCast   *tcast = (TypeCast *) tc->cast;
+	Node	   *tc_arg = tcast->arg;
+	bool		can_coerce = true;
+	int			def_expr_loc = -1;
+
+	result = makeNode(SafeTypeCastExpr);
+	/* Look up the type name first */
+	typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+
+	result->resulttype = targetType;
+	result->resulttypmod = targetTypmod;
+
+	/* now looking at cast fail default expression */
+	def_expr_loc = exprLocation(tc->expr);
+	def_expr = transformExprRecurse(pstate, tc->expr);
+
+	if (expression_returns_set(def_expr))
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("DEFAULT expression must not return a set"),
+				parser_coercion_errposition(pstate, def_expr_loc, def_expr));
+
+	if (IsA(def_expr, Aggref) || IsA(def_expr, WindowFunc))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("DEFAULT expression function must be a normal function"),
+				parser_coercion_errposition(pstate, def_expr_loc, def_expr));
+
+	if (IsA(def_expr, FuncExpr))
+	{
+		if (get_func_prokind(((FuncExpr *) def_expr)->funcid) != PROKIND_FUNCTION)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					errmsg("DEFAULT expression function must be a normal function"),
+					parser_coercion_errposition(pstate, def_expr_loc, def_expr));
+	}
+
+	def_expr = coerce_to_target_type(pstate, def_expr, exprType(def_expr),
+									 targetType, targetTypmod,
+									 COERCION_EXPLICIT,
+									 COERCE_EXPLICIT_CAST,
+									 exprLocation(def_expr));
+	if (def_expr == NULL)
+		ereport(ERROR,
+				errcode(ERRCODE_CANNOT_COERCE),
+				errmsg("cannot cast on_error default expression to type %s",
+						format_type_be(targetType)),
+				parser_coercion_errposition(pstate, def_expr_loc, def_expr));
+
+	/*
+	 * 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.  Otherwise, just
+	 * transform the argument normally.
+	 */
+	if (IsA(tc_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))
+		{
+			array_expr = copyObject(tc_arg);
+
+			source_expr = transformArrayExpr(pstate,
+											 (A_ArrayExpr *) tc_arg,
+											 targetBaseType,
+											 elementType,
+											 targetBaseTypmod,
+											 &can_coerce);
+			if (!can_coerce)
+				source_expr =  transformArrayExprSafe(pstate,
+													  (A_ArrayExpr *) array_expr,
+													  targetBaseType,
+													  elementType,
+													  targetBaseTypmod);
+		}
+		else
+			source_expr = transformExprRecurse(pstate, tc_arg);
+	}
+	else
+		source_expr = transformExprRecurse(pstate, tc_arg);
+
+	inputType = exprType(source_expr);
+	if (inputType == InvalidOid && can_coerce)
+		return (Node *) result;			/* do nothing if NULL input */
+
+
+	if (can_coerce && IsA(source_expr, Const) && exprType(source_expr) == UNKNOWNOID)
+		can_coerce = CovertUnknownConstSafe(pstate,
+											source_expr,
+											targetType,
+											targetTypmod);
+
+	/*
+	 * Location of the coercion is preferentially the location of the :: or
+	 * CAST symbol, but if there is none then use the location of the type
+	 * name (this can happen in TypeName 'string' syntax, for instance).
+	 */
+	location = tcast->location;
+	if (location < 0)
+		location = tcast->typeName->location;
+
+	if (can_coerce)
+	{
+		Oid		inputbase = getBaseType(inputType);
+		Oid 	targetbase = getBaseType(targetType);
+
+		/*
+		 * It's hard to make function numeric_cash error safe, especially callee
+		 * numeric_mul. So we have to disallow safe cast from numeric to money
+		 */
+		if (inputbase == NUMERICOID && targetbase == MONEYOID)
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("cannot cast type %s to %s when %s clause behavior is not %s",
+						   format_type_be(inputType),
+						   format_type_be(targetType),
+						   "CAST ... ON CONVERSION ERROR", "ERROR"),
+					parser_errposition(pstate, exprLocation(source_expr)));
+		cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+										  targetType, targetTypmod,
+										  COERCION_EXPLICIT,
+										  COERCE_EXPLICIT_CAST,
+										  location);
+	}
+
+	result->source_expr = source_expr;
+	result->cast_expr = cast_expr;
+	result->default_expr = def_expr;
+
+	return (Node *) result;
+}
+
 /*
  * Handle an explicit COLLATE clause.
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..812ed18c162 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1823,6 +1823,20 @@ FigureColnameInternal(Node *node, char **name)
 				}
 			}
 			break;
+		case T_SafeTypeCast:
+			strength = FigureColnameInternal(((SafeTypeCast *) node)->cast,
+											 name);
+			if (strength <= 1)
+			{
+				TypeCast *node_cast;
+				node_cast = (TypeCast *)((SafeTypeCast *) node)->cast;
+				if (node_cast->typeName != NULL)
+				{
+					*name = strVal(llast(node_cast->typeName->names));
+					return 1;
+				}
+			}
+			break;
 		case T_CollateClause:
 			return FigureColnameInternal(((CollateClause *) node)->arg, name);
 		case T_GroupingFunc:
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..02e5f9c92d7 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "utils/array.h"
@@ -660,6 +661,18 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
 	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
 }
 
+bool
 
index 3d6e6bdbfd2..ee868de2c64 100644
 
+				appendStringInfoString(buf, " ON CONVERSION ERROR)");
 
+bool
 
index e107d6e5f81..282bfa770ef 100644
@@ -1058,6 +1058,36 @@ typedef struct DomainConstraintState
 
+typedef struct SafeTypeCastState
+{
+	SafeTypeCastExpr *stcexpr;
+
+	/* Set to true if type cast cause an error. */
+	NullableDatum error;
+
+	/*
+	 * Addresses of steps that implement DEFAULT expr ON CONVERSION ERROR for
+	 * safe type cast.
+	 */
+	int 		jump_error;
+
+	/*
+	 * Address to jump to when skipping all the steps to evaulate the default
+	 * expression after performing ExecEvalSafeTypeCast().
+	 */
+	int 		jump_end;
+
+	/*
+	 * For error-safe evaluation of coercions.  When DEFAULT expr ON CONVERSION
+	 * ON ERROR is specified, a pointer to this is passed to ExecInitExprRec()
+	 * when initializing the coercion expressions, see ExecInitSafeTypeCastExpr.
+	 *
+	 * Reset for 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 86a236bd58b..95174e0feef 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -399,6 +399,12 @@ typedef struct TypeCast
 	ParseLoc	location;		/* token location, or -1 if unknown */
 } TypeCast;
 
+typedef struct SafeTypeCast
+{
+	NodeTag		type;
+	Node		*cast;
+	Node	   	*expr;		/* default expr */
+} SafeTypeCast;
 /*
  * CollateClause - a COLLATE expression
  */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..d2df7c28932 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -756,6 +756,41 @@ typedef enum CoercionForm
 	COERCE_SQL_SYNTAX,			/* display with SQL-mandated special syntax */
 } CoercionForm;
 
+/*
+ * SafeTypeCastExpr -
+	pg_node_attr(custom_query_jumble)
+	 * The transformed cast expression; this may be NULL if the two types can't
+	 * be cast.
+	/* Fall back to the default expression if the cast evaluation fails. */
 
 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..2915e6d7a3a
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,230 @@
+SET extra_float_digits = 0;
+-- 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));
+                     ^
+VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR));
+ column1 
+---------
+        
+(1 row)
+
+VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR));
+ column1 
+---------
+      42
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  cannot cast type numeric to money when CAST ... ON CONVERSION ERROR clause behavior is not ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+                    ^
+CREATE OR REPLACE 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 cast on_error default expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+                                        ^
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
+$$
+BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM hobbies_r; --error
+ERROR:  DEFAULT expression must not return a set
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+                                       ^
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR:  DEFAULT expression function must be a normal function
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR);
+                                       ^
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error
+ERROR:  DEFAULT expression function must be a normal function
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+                                       ^
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+ERROR:  invalid input syntax for type integer: "b"
+LINE 1: SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR);
+                                       ^
+-- test array coerce
+SELECT CAST('{123,abc,456}' AS integer[] 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)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array 
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+       array       
+-------------------
+ {{1,2},{three,a}}
+(1 row)
+
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY);
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t;
+     t     
+-----------
+ {21,22,1}
+ {21,22,2}
+ {21,22,3}
+ {21,22,4}
+(4 rows)
+
+SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t;
+     a     
+-----------
+ {12}
+ {21,22,2}
+ {21,22,3}
+ {13}
+(4 rows)
+
+-- test with domain
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+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(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+ d_int42 
+---------
+      42
+(1 row)
+
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  domain d_int42 does not allow null values
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+ d_int42 
+---------
+      42
+(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('(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)'...
+                                                             ^
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[]);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '11.1234', '{11.1234}'::numeric[]);
+INSERT INTO safecast VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+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 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 safecast;
+  float4   | to_int2 | to_int4 | 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 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 safecast;
+  float8   | to_int2 | to_int4 | 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)
+
+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 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 safecast;
+  numeric  | to_int2 | to_int4 | 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)
+
+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 safecast;
+          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)
+
+-- test deparse
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+       CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as safecast,
+       CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview
+CREATE OR REPLACE VIEW public.safecastview AS
+ SELECT CAST('1234' AS character(3) DEFAULT '-1111'::integer::character(3) ON CONVERSION ERROR) AS bpchar,
+    CAST(1 AS date DEFAULT now()::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS safecast,
+    CAST(ARRAY[ARRAY['1'], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+    CAST(ARRAY[ARRAY['1'::text, '2'::text], ARRAY['three'::text, 'a'::text]] AS text[] DEFAULT '{21,22}'::text[] ON CONVERSION ERROR) AS cast2
+CREATE INDEX cast_error_idx  ON hobbies_r((cast(name as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
+ERROR:  functions in index expression must be marked IMMUTABLE
+RESET extra_float_digits;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fbffc67ae60..ebbd454c450 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: brin_bloom brin_multi
 test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual
 
 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
-test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252
+test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252 cast
 
 # ----------
 # Run these alone so they don't run out of parallel workers
diff --git a/src/test/regress/sql/cast.sql b/src/test/regress/sql/cast.sql
new file mode 100644
index 00000000000..657db393996
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,109 @@
+SET extra_float_digits = 0;
+
+-- CAST DEFAULT ON CONVERSION ERROR
+VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); --error
+VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR));
+VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR));
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE OR REPLACE 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 valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
+$$
+BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM hobbies_r; --error
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+
+-- test array coerce
+SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON CONVERSION ERROR);
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY);
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t;
+SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t;
+
+-- test with domain
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+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('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error
+
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[]);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '11.1234', '{11.1234}'::numeric[]);
+INSERT INTO safecast VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+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 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 safecast;
+
+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 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 safecast;
+
+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 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 safecast;
+
+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 safecast;
+
+-- test deparse
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+       CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as safecast,
+       CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview
+
+CREATE INDEX cast_error_idx  ON hobbies_r((cast(name as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
+RESET extra_float_digits;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e6f2e93b2d6..5b32df3a2db 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2652,6 +2652,9 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

---

From: jian he <[email-masked]>
Date: Mon, 4 Aug 2025 09:46:45 +0800

hi.

fix the regress tests failure in
https://api.cirrus-ci.com/v1/artifact/task/5894868779663360/testrun/build/testrun/regress/regress/regression.diffs
From e60e5190511326568eba8e6748062adb47f1134c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 18 Jul 2025 13:00:19 +0800
Subject: [PATCH v4 2/3] make ArrayCoerceExpr error safe

index f1569879b5..1f3f899874 100644

index 8a72b5e70a..636d9ef9e6 100644
 
 /*
index c8f53c6fbe..b5f98bf22f 100644

From e3854ee1d362646ea97a8baa4e262cc3a9e95913 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 1 Aug 2025 13:22:59 +0800
Subject: [PATCH v4 1/3] make some numeric cast function error safe

index 7b97d2be6c..6461e9c94b 100644
 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */

 	/* Range check */

 	/* Range check */
 
index 9dd5889f34..4b9a762067 100644
 
 	/* Range check */

 	/* Range check */
 
index c9233565d5..cf498627a9 100644

 
 /*

 

 
 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 138c6db6386d5c937fce6d903b3608f50950ee3a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 4 Aug 2025 09:03:22 +0800
Subject: [PATCH v4 3/3] CAST(expr AS newtype DEFAULT ON ERROR)

demo:

    date    |  int4
------------+---------
 2011-01-01 | {-1011}
---
 src/backend/jit/llvm/llvmjit_expr.c   |  49 ++++
 src/backend/nodes/nodeFuncs.c         |  67 ++++++
 23 files changed, 1122 insertions(+), 10 deletions(-)

index 1f3f899874..03cafad525 100644

 
 
+/*
index 636d9ef9e6..f6ed5953c2 100644

+int
index 890bcb0b0a..a2dfb82393 100644
@@ -2256,6 +2256,55 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
+			case EEOP_SAFETYPE_CAST:
+				{
+					SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+					LLVMValueRef v_ret;
+
+					/*
+					 * Call ExecEvalSafeTypeCast().  It returns the address of
+					 * the step to perform next.
+					 */
+					v_ret = build_EvalXFunc(b, mod, "ExecEvalSafeTypeCast",
+											v_state, op, v_econtext);
+
+					/*
+					 * Build a switch to map the return value (v_ret above),
+					 * which is a runtime value of the step address to perform
+					 * next to jump_error
+					 */
+					if (stcstate->jump_error >= 0)
+					{
+						LLVMValueRef v_jump_error;
+						LLVMValueRef v_switch;
+						LLVMBasicBlockRef b_done,
+									b_error;
+
+						b_error =
+							l_bb_before_v(opblocks[opno + 1],
+										  "op.%d.stcexpr_error", opno);
+						b_done =
+							l_bb_before_v(opblocks[opno + 1],
+										  "op.%d.stcexpr_done", opno);
+
+						v_switch = LLVMBuildSwitch(b,
+												   v_ret,
+												   b_done,
+												   1);
+
+						/* Returned stcstate->jump_error? */
+						v_jump_error = l_int32_const(lc, stcstate->jump_error);
+						LLVMAddCase(v_switch, v_jump_error, b_error);
+
+						/* ON ERROR code */
+						LLVMPositionBuilderAtEnd(b, b_error);
+						LLVMBuildBr(b, opblocks[stcstate->jump_error]);
+
+						LLVMPositionBuilderAtEnd(b, b_done);
+					}
+					LLVMBuildBr(b, opblocks[stcstate->jump_end]);
+					break;
+				}
 			case EEOP_JSONEXPR_PATH:
 				{
 					JsonExprState *jsestate = op->d.jsonexpr.jsestate;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f..212f3e3c50 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -206,6 +206,9 @@ exprType(const Node *expr)
 		case T_RowCompareExpr:
 			type = BOOLOID;
 			break;
+		case T_SafeTypeCastExpr:
+			type = ((const SafeTypeCastExpr *) expr)->resulttype;
+			break;
 		case T_CoalesceExpr:
 			type = ((const CoalesceExpr *) expr)->coalescetype;
 			break;
@@ -450,6 +453,8 @@ exprTypmod(const Node *expr)
 				return typmod;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			return ((const SafeTypeCastExpr *) expr)->resulttypmod;
 		case T_CoalesceExpr:
 			{
 				/*
@@ -965,6 +970,9 @@ 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_CoalesceExpr:
 			coll = ((const CoalesceExpr *) expr)->coalescecollid;
 			break;
@@ -1232,6 +1240,9 @@ 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_CoalesceExpr:
 			((CoalesceExpr *) expr)->coalescecollid = collation;
 			break;
@@ -1554,6 +1565,15 @@ exprLocation(const Node *expr)
 			/* just use leftmost argument's location */
 			loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs);
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				const SafeTypeCastExpr *cast_expr = (const SafeTypeCastExpr *) expr;
+				if (cast_expr->cast_expr)
+					loc = exprLocation(cast_expr->cast_expr);
+				else
+					loc = exprLocation(cast_expr->default_expr);
+				break;
+			}
 		case T_CoalesceExpr:
 			/* COALESCE keyword should always be the first thing */
 			loc = ((const CoalesceExpr *) expr)->location;
@@ -2325,6 +2345,18 @@ expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr   *scexpr = (SafeTypeCastExpr *) node;
+
+				if (WALK(scexpr->source_expr))
+					return true;
+				if (WALK(scexpr->cast_expr))
+					return true;
+				if (WALK(scexpr->default_expr))
+					return true;
+			}
+			break;
 		case T_CoalesceExpr:
 			return WALK(((CoalesceExpr *) node)->args);
 		case T_MinMaxExpr:
@@ -3334,6 +3366,19 @@ expression_tree_mutator_impl(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr   *scexpr = (SafeTypeCastExpr *) node;
+				SafeTypeCastExpr   *newnode;
+
+				FLATCOPY(newnode, scexpr, SafeTypeCastExpr);
+				MUTATE(newnode->source_expr, scexpr->source_expr, Node *);
+				MUTATE(newnode->cast_expr, scexpr->cast_expr, Node *);
+				MUTATE(newnode->default_expr, scexpr->default_expr, Node *);
+
+				return (Node *) newnode;
+			}
+			break;
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
@@ -4468,6 +4513,28 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_SafeTypeCast:
+			{
+				SafeTypeCast   *sc = (SafeTypeCast *) node;
+
+				if (WALK(sc->cast))
+					return true;
+				if (WALK(sc->expr))
+					return true;
+			}
+			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr   *stc = (SafeTypeCastExpr *) node;
+
+				if (WALK(stc->source_expr))
+					return true;
+				if (WALK(stc->cast_expr))
+					return true;
+				if (WALK(stc->default_expr))
+					return true;
+			}
+			break;
 		case T_CollateClause:
 			return WALK(((CollateClause *) node)->arg);
 		case T_SortBy:
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 31f9715197..76426c88e9 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -74,6 +74,7 @@ static void _jumbleElements(JumbleState *jstate, List *elements, Node *node);
 static void _jumbleParam(JumbleState *jstate, Node *node);
 static void _jumbleA_Const(JumbleState *jstate, Node *node);
 static void _jumbleVariableSetStmt(JumbleState *jstate, Node *node);
+static void _jumbleSafeTypeCastExpr(JumbleState *jstate, Node *node);
 static void _jumbleRangeTblEntry_eref(JumbleState *jstate,
 									  RangeTblEntry *rte,
 									  Alias *expr);
@@ -758,6 +759,19 @@ _jumbleVariableSetStmt(JumbleState *jstate, Node *node)
 	JUMBLE_LOCATION(location);
 }
 
+static void
index f45131c34c..a7b4e80971 100644
@@ -2938,6 +2938,25 @@ eval_const_expressions_mutator(Node *node,

index 70a0d832a1..5176d7e4d2 100644
 
@@ -15936,8 +15938,25 @@ func_expr_common_subexpr:
@@ -16323,6 +16342,14 @@ func_expr_common_subexpr:
 				}
 			;

 /*
  * SQL/XML support
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c..2f5a3059ac 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -16,6 +16,7 @@
 #include "postgres.h"

 /*
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
 
+/*

 
+
+/*
index 4aba0d9d4d..812ed18c16 100644
index 7713bdc6af..02e5f9c92d 100644
 
+bool
index b5f98bf22f..6bd8a989db 100644
 
index 3d6e6bdbfd..ee868de2c6 100644
 
index 782291d999..9de895e682 100644
 
+bool
index 7536620370..0afcf09c08 100644
 
index 0fe7b4ebc7..299d4eef4e 100644
index e107d6e5f8..282bfa770e 100644
 
index 28e2e8dc0f..2e071b2abf 100644
 
index 6dfca3cb35..d2df7c2893 100644
 
+/*
+ * SafeTypeCastExpr -
index 0d919d8bfa..12381aed64 100644
 
index 0000000000..a7d7d6d674
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY, c text default '1');
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error
+CREATE INDEX cast_error_idx  ON tcast((cast(c as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
index a424be2a6b..7c3ed55f90 100644

 # ----------
index 0000000000..95a813e5ff
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY, c text default '1');
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error
+CREATE INDEX cast_error_idx  ON tcast((cast(c as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
index 8319203857..c22f3ed9e6 100644

---

From: Corey Huinker <[email-masked]>
Date: Mon, 4 Aug 2025 01:09:21 -0400

> (hope this is all the corner case we need to catch...)

I'm fairly certain that the committers won't like us special-casing the
internal cast functions, as we would have to maintain these special cases
as new core types are added, and it still bypasses the defined cast
function for user-defined types, which could have similar issues similar to
the rounding issue.

I think the way forward here is either to:

1.  add a second function definition to CAST. The potential syntax forr a
second function gets clumsy, but might look something like this:

CREATE CAST (source_type AS target_type)
    WITH FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]
   [
    WITH SAFE FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]
   ]

That doesn't seem right to me, it seems easier to:

2. Modify the CAST definition to indicate whether the existing cast
function has the regular function signature or a -Safe one. In cases where
a CAST has a defined function but the safe flag is turned off, we would
have to fail the query with an error like "Defined CAST function from
srctype to desttype is not error-safe".

This would involve changing the syntax of CREATE CAST by adding an option
SAFE, or ERROR SAFE, or similar:

CREATE CAST (source_type AS target_type)
    WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]

We would add a new value to pg_cast.castmethod, 's' for "safe".

We could refactor all the numeric types to use the modified functions, so
no special-case code there anymore, and it gives extension writers an
incentive to (eventually) make their own cast functions error-safe.

While method 2 seems a lot cleaner, there may be a performance regression
in the now error-safe typecast functions. If so, that might tip the balance
to having two functions defined.

---

From: jian he <[email-masked]>
Date: Mon, 4 Aug 2025 22:29:22 +0800

On Mon, Aug 4, 2025 at 1:09 PM Corey Huinker <[email-masked]> wrote:
> I'm fairly certain that the committers won't like us special-casing the internal cast functions, as we would have to maintain these special cases as new core types are added, and it still bypasses the defined cast function for user-defined types, which could have similar issues similar to the rounding issue.
>
It's not special-casing the internal cast functions.
It's how the cast being evaluated.
There are two ways: CoerceViaIO, FuncExpr.

generally if there is a pg_cast entry, postgres will use FuncExpr. but to safely
cast evaluation (DEFAULT ON CONVERSION ERROR) we can not use FuncExpr in some
cases. Because the FuncExpr associate function is not error safe.
So in v4, we try to use CoerceViaIO to evaluate the case, but it turns
out CoerceViaIO results are
not the same as FuncExpr.
one of the example is:
select ('11.1'::numeric::int);

In the end, it seems we need to make all these functions in the below
query error safe.
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
order by castsource::regtype;
It's a lot of work, but seems doable, after playing around with it.

I don't think we need to change the pg_cast catalog entry,
we just need to make these function (pg_cast.castmethod) errors safe.

---

From: Corey Huinker <[email-masked]>
Date: Tue, 5 Aug 2025 00:10:33 -0400

> It's a lot of work, but seems doable, after playing around with it.

It is do-able. But that's just the cast functions that are part of core
postgres.

> we just need to make these function (pg_cast.castmethod) errors safe.

That would break any user-defined cast functions that were not also error
safe, which is to say all of them.

We need a way for user-defined cast functions to indicate whether or not
they are error safe, and handle both situations accordingly (i.e. fail a
CAST ON DEFAULT when the user-defined cast is not error-safe).

---

From: jian he <[email-masked]>
Date: Mon, 11 Aug 2025 14:18:44 +0800

On Tue, Aug 5, 2025 at 12:10 PM Corey Huinker <[email-masked]> wrote:
> It is do-able. But that's just the cast functions that are part of core postgres.
>
hi.
it's doable for most of the data types.
but I found geometric type related cast function refactoring to error
safe is quite challenging,
so I skip that part refactoring.

> That would break any user-defined cast functions that were not also error safe, which is to say all of them.
>
> We need a way for user-defined cast functions to indicate whether or not they are error safe, and handle both situations accordingly (i.e. fail a CAST ON DEFAULT when the user-defined cast is not error-safe).
>

I understand what you mean now.
CREATE CAST can use built-in functions too, we have no way to check
whether these CREATE CAST
associated functions are error safe or not.
for example:
CREATE CAST (jsonpath  AS bytea) WITH FUNCTION jsonpath_send (jsonpath
) AS ASSIGNMENT;
select '$'::jsonpath::bytea;

To avoid this situation, we have to add a new column to pg_cast to
indicate whether a cast function is error-safe.
It's unlikely a pg_cast entry has two functions, one is error safe, one is not,
adding pg_cast.casterrorsafefunc would not be that appropriate.

so I choose pg_cast.casterrorsafe would be fine.
pg_cast.casterrorsafe true means castfunc function is error safe, we
can use it as safe cast evaluation
(CAST... DEFAULT defexpr ON CONVERSION ERROR)

please check the attached V6 script:

v6-0001 to v6-0016 is about making existing pg_cast.castfunc function
error safe.
(commit message have associated query to explain the refactoring, as mentioned
above, geometric and money associated type not refactored yet)

v6-0017-make-ArrayCoerceExpr-error-safe.patch
v6-0018-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patch
is about (CAST... DEFAULT defexpr ON CONVERSION ERROR).
From 8efc16c4b2e1c6fa14a4851325956c10232e75e9 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sun, 10 Aug 2025 01:36:17 +0800
Subject: [PATCH v6 16/18] error safe for casting character varying to other
 types per pg_cast

select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;

    castsource     |    casttarget     | castfunc | castcontext | castmethod |    prosrc     | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
 character varying | regclass          |     1079 | i           | f          | text_regclass | regclass
 character varying | "char"            |      944 | a           | f          | text_char     | char
 character varying | name              |     1400 | i           | f          | text_name     | name
 character varying | xml               |     2896 | e           | f          | texttoxml     | xml
 character varying | character varying |      669 | i           | f          | varchar       | varchar
(5 rows)

texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
---
 src/backend/utils/adt/varchar.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 5cb5c8c46f9..08f1bf5a24d 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
 	{
 		for (i = maxmblen; i < len; i++)
 			if (s_data[i] != ' ')
-				ereport(ERROR,
+				ereturn(fcinfo->context, (Datum) 0,
 						(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
 						 errmsg("value too long for type character varying(%d)",
 								maxlen)));
-- 
2.34.1

From b1cd2195beefa7a4995dcb2ec8a390ff99ca7c4d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sun, 10 Aug 2025 01:33:36 +0800
Subject: [PATCH v6 15/18] error safe for casting timestamp to other types per
 pg_cast

select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;

         castsource          |         casttarget          | castfunc | castcontext | castmethod |        prosrc         |   proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
 timestamp without time zone | date                        |     2029 | a           | f          | timestamp_date        | date
 timestamp without time zone | time without time zone      |     1316 | a           | f          | timestamp_time        | time
 timestamp without time zone | timestamp with time zone    |     2028 | i           | f          | timestamp_timestamptz | timestamptz
 timestamp without time zone | timestamp without time zone |     1961 | i           | f          | timestamp_scale       | timestamp
(4 rows)
---
 src/backend/utils/adt/date.c      | 13 +++++++++++--
 src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
 2 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 111bfd8f519..c5562b563e5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1373,7 +1373,16 @@ timestamp_date(PG_FUNCTION_ARGS)
 	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);
 	DateADT		result;
 
-	result = timestamp2date_opt_overflow(timestamp, NULL);
+	if (likely(!fcinfo->context))
+		result = timestamptz2date_opt_overflow(timestamp, NULL);
+	else
+	{
+		int			overflow;
+		result = timestamp2date_opt_overflow(timestamp, &overflow);
+
+		if (overflow != 0)
+			PG_RETURN_NULL();
+	}
 	PG_RETURN_DATEADT(result);
 }
 
@@ -2089,7 +2098,7 @@ timestamp_time(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 
 	if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 8648a16db7f..b40dfd224d2 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
 
 	result = timestamp;
 
-	AdjustTimestampForTypmod(&result, typmod, NULL);
+	if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+		PG_RETURN_NULL();
 
 	PG_RETURN_TIMESTAMP(result);
 }
@@ -6451,7 +6452,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 {
 	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);
 
-	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+	if (likely(!fcinfo->context))
+		PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+	else
+	{
+		TimestampTz result;
+		int			overflow;
+
+		result = timestamp2timestamptz_opt_overflow(timestamp, &overflow);
+		if (overflow != 0)
+			PG_RETURN_NULL();
+		else
+			PG_RETURN_TIMESTAMPTZ(result);
+	}
 }
 
 /*
-- 
2.34.1

From a6e538d1ffdd0530879b7ad681180944c7390eea Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sun, 10 Aug 2025 01:31:09 +0800
Subject: [PATCH v6 14/18] error safe for casting timestamptz to other types
 per pg_cast

select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;

        castsource        |         casttarget          | castfunc | castcontext | castmethod |        prosrc         |   proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
 timestamp with time zone | date                        |     1178 | a           | f          | timestamptz_date      | date
 timestamp with time zone | time without time zone      |     2019 | a           | f          | timestamptz_time      | time
 timestamp with time zone | timestamp without time zone |     2027 | a           | f          | timestamptz_timestamp | timestamp
 timestamp with time zone | time with time zone         |     1388 | a           | f          | timestamptz_timetz    | timetz
 timestamp with time zone | timestamp with time zone    |     1967 | i           | f          | timestamptz_scale     | timestamptz
(5 rows)
---
 src/backend/utils/adt/date.c      | 16 +++++++++++++---
 src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
 2 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4f0f3d26989..111bfd8f519 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1468,7 +1468,17 @@ timestamptz_date(PG_FUNCTION_ARGS)
 	TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
 	DateADT		result;
 
-	result = timestamptz2date_opt_overflow(timestamp, NULL);
+	if (likely(!fcinfo->context))
+		result = timestamptz2date_opt_overflow(timestamp, NULL);
+	else
+	{
+		int			overflow;
+		result = timestamptz2date_opt_overflow(timestamp, &overflow);
+
+		if (overflow != 0)
+			PG_RETURN_NULL();
+	}
+
 	PG_RETURN_DATEADT(result);
 }
 
@@ -2110,7 +2120,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 
 	if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
@@ -3029,7 +3039,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 
 	if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index f1e96b84a6f..8648a16db7f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
 
 	result = timestamp;
 
-	AdjustTimestampForTypmod(&result, typmod, NULL);
+	if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+		PG_RETURN_NULL();
 
 	PG_RETURN_TIMESTAMPTZ(result);
 }
@@ -6528,7 +6529,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
 {
 	TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
 
-	PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+	if (likely(!fcinfo->context))
+		PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+	else
+	{
+		int			overflow;
+		Timestamp	result;
+
+		result = timestamptz2timestamp_opt_overflow(timestamp, &overflow);
+		if (overflow != 0)
+			PG_RETURN_NULL();
+		else
+			PG_RETURN_TIMESTAMP(result);
+	}
 }
 
 /*
-- 
2.34.1

From 2d6daf36a294f5ff64b30b453bb319e09114c1d3 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sun, 10 Aug 2025 01:41:14 +0800
Subject: [PATCH v6 17/18] make ArrayCoerceExpr error safe

similar to https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
---
 src/backend/executor/execExpr.c       | 1 +
 src/backend/executor/execExprInterp.c | 7 +++++++
 src/backend/utils/adt/arrayfuncs.c    | 7 +++++++
 3 files changed, 15 insertions(+)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..921ec4e0bc1 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1701,6 +1701,7 @@ ExecInitExprRec(Expr *node, ExprState *state,

 				ExecInitExprRec(acoerce->elemexpr, elemstate,
 								&elemstate->resvalue, &elemstate->resnull);
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 67f4e00eac4..998674c180d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3644,6 +3644,13 @@ 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;
+	}
+
 }
 
 /*

From 51bef18b4cc1025848ef7600737449b86015509b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 11 Aug 2025 14:11:42 +0800
Subject: [PATCH v6 18/18] CAST(expr AS newtype DEFAULT ON ERROR)

draft doc also added.

We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.

demo:
SELECT CAST('1' AS date  DEFAULT '2011-01-01' ON ERROR),
       CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR);
    date    |  int4
------------+---------
 2011-01-01 | {-1011}
---
 doc/src/sgml/catalogs.sgml                |  11 +
 doc/src/sgml/syntax.sgml                  |  15 +
 src/backend/catalog/pg_cast.c             |   1 +
 src/backend/executor/execExpr.c           |  83 +++-
 src/backend/executor/execExprInterp.c     |  30 ++
 src/backend/jit/llvm/llvmjit_expr.c       |  49 ++
 src/backend/nodes/nodeFuncs.c             |  67 +++
 src/backend/nodes/queryjumblefuncs.c      |  14 +
 src/backend/optimizer/util/clauses.c      |  19 +
 src/backend/parser/gram.y                 |  31 +-
 src/backend/parser/parse_expr.c           | 345 +++++++++++++-
 src/backend/parser/parse_target.c         |  14 +
 src/backend/parser/parse_type.c           |  13 +
 src/backend/utils/adt/arrayfuncs.c        |   6 +
 src/backend/utils/adt/ruleutils.c         |  15 +
 src/backend/utils/fmgr/fmgr.c             |  13 +
 src/include/catalog/pg_cast.dat           | 302 ++++++------
 src/include/catalog/pg_cast.h             |   4 +
 src/include/executor/execExpr.h           |   8 +
 src/include/fmgr.h                        |   3 +
 src/include/nodes/execnodes.h             |  30 ++
 src/include/nodes/parsenodes.h            |   6 +
 src/include/nodes/primnodes.h             |  35 ++
 src/include/parser/parse_type.h           |   2 +
 src/test/regress/expected/cast.out        | 556 ++++++++++++++++++++++
 src/test/regress/expected/create_cast.out |   5 +
 src/test/regress/expected/opr_sanity.out  |  24 +-
 src/test/regress/parallel_schedule        |   2 +-
 src/test/regress/sql/cast.sql             | 259 ++++++++++
 src/test/regress/sql/create_cast.sql      |   1 +
 src/tools/pgindent/typedefs.list          |   3 +
 31 files changed, 1793 insertions(+), 173 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index da8a7882580..6398a6a0339 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>casterrorsafe</structfield> <type>bool</type>
+      </para>
+      <para>
+       This indicates whether the <structfield>castfunc</structfield> function is error-safe.
+       If true, the <structfield>castfunc</structfield> function is error-safe; if false, it is not.
+      </para></entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 916189a7d68..4854b78966e 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2104,6 +2104,21 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
     The <literal>CAST</literal> syntax conforms to SQL; the syntax with
     <literal>::</literal> is historical <productname>PostgreSQL</productname>
     usage.
+    The alternative syntax is
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
+   </para>
+
+   <para>
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+    For example, the following query will evaluate the default expression and return 42.
+    TODO: more explanation
+<programlisting>
+SELECT (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR));
+</programlisting>
    </para>
 
    <para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..6fe65d24d31 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
 	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
 	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
 	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+	values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
 
 	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
 
index 921ec4e0bc1..eaed2d2be67 100644
@@ -2178,6 +2181,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
@@ -2744,7 +2755,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4742,6 +4753,76 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
index 998674c180d..351923d7ec4 100644
 
@@ -5190,6 +5196,30 @@ GetJsonBehaviorValueString(JsonBehavior *behavior)
 
+int
index 890bcb0b0a7..a2dfb823932 100644
 
index 7bc823507f1..212f3e3c50c 100644
 
+static void

 

 /*
  * SQL/XML support
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c6..f837699d4b7 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -16,6 +16,8 @@
 #include "postgres.h"
 
+#include "catalog/pg_cast.h"
@@ -37,6 +39,7 @@
 
@@ -60,7 +63,10 @@ static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
@@ -76,6 +82,7 @@ static Node *transformWholeRowRef(ParseState *pstate,
@@ -106,6 +113,8 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,

 /*
@@ -163,13 +172,17 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
+			result = transformTypeSafeCast(pstate, (SafeTypeCast *) expr);
+			break;
+
 		case T_CollateClause:
 			result = transformCollateClause(pstate, (CollateClause *) expr);
 			break;
@@ -2004,16 +2017,127 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 	return result;
 }
 
+/*
@@ -2044,9 +2168,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
@@ -2072,6 +2197,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
+	if (can_coerce && !*can_coerce)
+		return NULL;
+
 	/*
 	 * Select a target type for the elements.
 	 *
@@ -2139,6 +2267,17 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		Node	   *e = (Node *) lfirst(element);
 		Node	   *newe;
 
+		if (can_coerce && (*can_coerce) && IsA(e, Const) && exprType(e) == UNKNOWNOID)
+		{
+			if (!CovertUnknownConstSafe(pstate, e, coerce_type, typmod))
+			{
+				*can_coerce = false;
+				list_free(newcoercedelems);
+				newcoercedelems = NIL;
+				return NULL;
+			}
+		}
+
 		if (coerce_hard)
 		{
 			newe = coerce_to_target_type(pstate, e,
@@ -2742,7 +2881,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 									  (A_ArrayExpr *) arg,
 									  targetBaseType,
 									  elementType,
-									  targetBaseTypmod);
+									  targetBaseTypmod,
+									  NULL);
 		}
 		else
 			expr = transformExprRecurse(pstate, arg);
@@ -2779,6 +2919,199 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	return result;
 }
 
+
+/*
+				errmsg("cannot cast DEFAULT clause for CAST ... ON CONVERSION ERROR to type %s",
+				Assert(source_expr == NULL);
+		Node	   *origexpr;
+		origexpr = cast_expr;
+		while (cast_expr && IsA(cast_expr, CollateExpr))
+			cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+		if (cast_expr && IsA(cast_expr, FuncExpr))
+			HeapTuple	tuple;
+			ListCell   *lc;
+			Node	   *sexpr;
+			FuncExpr   *fexpr = (FuncExpr *) cast_expr;
+			lc = list_head(fexpr->args);
+			sexpr = (Node *) lfirst(lc);
+			/* Look in pg_cast */
+			tuple = SearchSysCache2(CASTSOURCETARGET,
+									ObjectIdGetDatum(exprType(sexpr)),
+									ObjectIdGetDatum(targetType));
+			if (HeapTupleIsValid(tuple))
+				Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+				if (!castForm->casterrorsafe)
+							errmsg("cannot cast type %s to %s when %s clause is specified for %s",
+								   "DEFAULT",
+								   "CAST ... ON CONVERSION ERROR"),
+							errhint("Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast"),
+				elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+							format_type_be(targetType));
+			ReleaseSysCache(tuple);
+		cast_expr = origexpr;
 
+bool

 
+bool
+OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+						 int32 typmod, fmNodePtr escontext,
+						 Datum *result)
+{
+	FmgrInfo			flinfo;
+
+	fmgr_info(functionId, &flinfo);
+
+	return InputFunctionCallSafe(&flinfo, str, typioparam, typmod,
+								 escontext, result);
+}
+
 char *
 OidOutputFunctionCall(Oid functionId, Datum val)
 {
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..3adef6b4faf 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
 # int2->int4->int8->numeric->float4->float8, while casts in the
 # reverse direction are assignment-only.
 { castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float4', casttarget => 'numeric',
-  castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+  castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'float8', casttarget => 'numeric',
-  castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+  castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'numeric', casttarget => 'float4',
-  castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+  castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'numeric', casttarget => 'float8',
-  castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+  castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
   castcontext => 'a', castmethod => 'f' },
 { castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
 
 # Allow explicit coercions between int4 and bool
 { castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 
 # Allow explicit coercions between xid8 and xid
 { castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 
 # OID category: allow implicit conversion from any integral type (including
 # int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
 # casts from text and varchar to regclass, which exist mainly to support
 # legacy forms of nextval() and related functions.
 { castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'oid', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
 { castsource => 'regproc', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regproc', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regproc', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
 { castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
 { castsource => 'regoper', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regoper', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regoper', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
 { castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
 { castsource => 'regclass', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regclass', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regclass', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
 { castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
 { castsource => 'regtype', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regtype', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regtype', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
 { castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
 { castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'oid', casttarget => 'regrole', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regrole', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regrole', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regrole', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
 { castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
 { castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 
@@ -302,57 +302,57 @@
 { castsource => 'text', casttarget => 'varchar', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'varchar', casttarget => 'text', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 
 # Allow explicit coercions between bytea and integer types
 { castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 
 # Allow explicit coercions between int4 and "char"
 { castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 
 # pg_node_tree can be coerced to, but not from, text
 { castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,31 +378,31 @@
 
 # Datetime category
 { castsource => 'date', casttarget => 'timestamp',
-  castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+  castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'date', casttarget => 'timestamptz',
-  castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+  castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timestamp', casttarget => 'date',
-  castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+  castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timestamp', casttarget => 'time',
-  castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+  castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timestamp', casttarget => 'timestamptz',
-  castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+  castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timestamptz', casttarget => 'date',
-  castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+  castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timestamptz', casttarget => 'time',
-  castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+  castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timestamptz', casttarget => 'timestamp',
-  castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+  castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timestamptz', casttarget => 'timetz',
-  castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+  castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 
 # Geometric category
 { castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
@@ -436,15 +436,15 @@
 
 # MAC address category
 { castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 
 # INET category
 { castsource => 'cidr', casttarget => 'inet', castfunc => '0',
   castcontext => 'i', castmethod => 'b' },
 { castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 
 # BitString category
 { castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
 
 # Cross-category casts between bit and int4, int8
 { castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 
 # Cross-category casts to and from TEXT
 # We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
 # behavior will ensue when the automatic cast is applied instead of the
 # pg_cast entry!
 { castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'xml', casttarget => 'text', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'text', casttarget => 'xml', castfunc => 'xml',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 
 # Cross-category casts to and from VARCHAR
 # We support all the same casts as for TEXT.
 { castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'xml', casttarget => 'varchar', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 
 # Cross-category casts to and from BPCHAR
 # We support all the same casts as for TEXT.
 { castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
   castcontext => 'a', castmethod => 'b' },
 { castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 
 # Length-coercion functions
 { castsource => 'bpchar', casttarget => 'bpchar',
   castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'varchar', casttarget => 'varchar',
   castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timestamp', casttarget => 'timestamp',
   castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timestamptz', casttarget => 'timestamptz',
   castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'interval', casttarget => 'interval',
   castfunc => 'interval(interval,int4)', castcontext => 'i',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'timetz', casttarget => 'timetz',
-  castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+  castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
-  castcontext => 'i', castmethod => 'f' },
+  castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'numeric', casttarget => 'numeric',
-  castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+  castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f',  casterrorsafe => 't' },
 
 # json to/from jsonb
 { castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
 
 # jsonb to numeric and bool types
 { castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 
 # range to multirange
 { castsource => 'int4range', casttarget => 'int4multirange',
   castfunc => 'int4multirange(int4range)', castcontext => 'e',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'int8range', casttarget => 'int8multirange',
   castfunc => 'int8multirange(int8range)', castcontext => 'e',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'numrange', casttarget => 'nummultirange',
   castfunc => 'nummultirange(numrange)', castcontext => 'e',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'daterange', casttarget => 'datemultirange',
   castfunc => 'datemultirange(daterange)', castcontext => 'e',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'tsrange', casttarget => 'tsmultirange',
-  castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+  castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f',  casterrorsafe => 't' },
 { castsource => 'tstzrange', casttarget => 'tstzmultirange',
   castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
-  castmethod => 'f' },
+  castmethod => 'f',  casterrorsafe => 't' },
 ]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..218d81d535a 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
 
 	/* cast method */
 	char		castmethod;
+
+	/* cast function error safe */
+	bool		casterrorsafe BKI_DEFAULT(f);
+
 } FormData_pg_cast;
 
 /* ----------------

 
 
+/*
+ * SafeTypeCastExpr -
 
 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..bf62e9348f1
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,556 @@
+SET extra_float_digits = 0;
+-- 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));
+                     ^
+VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR));
+ column1 
+---------
+        
+(1 row)
+
+VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR));
+ column1 
+---------
+      42
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  cannot cast type numeric to money when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char 
+------
+ 
+(1 row)
+
+CREATE OR REPLACE 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 cast DEFAULT clause for CAST ... ON CONVERSION ERROR to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+                                        ^
+-- test array coerce
+SELECT CAST('{123,abc,456}' AS integer[] 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)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array 
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+       array       
+-------------------
+ {{1,2},{three,a}}
+(1 row)
+
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
+$$
+BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY, c text default '1');
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error
+ERROR:  DEFAULT expression must not return a set
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+                                       ^
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR:  DEFAULT expression function must be a normal function
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR);
+                                       ^
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error
+ERROR:  DEFAULT expression function must be a normal function
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+                                       ^
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+ERROR:  invalid input syntax for type integer: "b"
+LINE 1: SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR);
+                                       ^
+SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t;
+     t     
+-----------
+ {21,22,1}
+ {21,22,2}
+ {21,22,3}
+ {21,22,4}
+(4 rows)
+
+SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t;
+     a     
+-----------
+ {12}
+ {21,22,2}
+ {21,22,3}
+ {13}
+(4 rows)
+
+-- test with domain
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+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(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+ d_int42 
+---------
+      42
+(1 row)
+
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  domain d_int42 does not allow null values
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+ d_int42 
+---------
+      42
+(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('(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)'...
+                                                             ^
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8 
+------
+     
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4 
+------
+     
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2 
+------
+     
+(1 row)
+
+-----safe cast from range type to other type
+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 from geometry to other geometry is not supported
+SELECT CAST(NULL::point AS box DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type point to box when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::point AS box DEFAULT NULL ON CONVERSION ER...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::lseg AS point DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type lseg to point when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::lseg AS point DEFAULT NULL ON CONVERSION E...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::path AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type path to polygon when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::path AS polygon DEFAULT NULL ON CONVERSION...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::box AS point DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type box to point when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::box AS point DEFAULT NULL ON CONVERSION ER...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::box AS lseg DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type box to lseg when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::box AS lseg DEFAULT NULL ON CONVERSION ERR...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::box AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type box to polygon when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::box AS polygon DEFAULT NULL ON CONVERSION ...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::box AS circle DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type box to circle when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::box AS circle DEFAULT NULL ON CONVERSION E...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::polygon AS point DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type polygon to point when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::polygon AS point DEFAULT NULL ON CONVERSIO...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::polygon AS path DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type polygon to path when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::polygon AS path DEFAULT NULL ON CONVERSION...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::polygon AS box DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type polygon to box when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::polygon AS box DEFAULT NULL ON CONVERSION ...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::polygon AS circle DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type polygon to circle when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::polygon AS circle DEFAULT NULL ON CONVERSI...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::circle AS point DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type circle to point when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::circle AS point DEFAULT NULL ON CONVERSION...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::circle AS box DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type circle to box when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::circle AS box DEFAULT NULL ON CONVERSION E...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::circle AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type circle to polygon when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::circle AS polygon DEFAULT NULL ON CONVERSI...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+-----safe cast from money or money cast to other type is not supported
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type bigint to money when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type integer to money when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type numeric to money when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  cannot cast type money to numeric when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[],
+                        col5 int2 default 32767,
+                        col6 int4 default 32768,
+                        col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO safecast VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+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 safecast;
+  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 safecast;
+  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)
+
+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 safecast;
+       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 safecast;
+          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 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 safecast;
+ 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 safecast;
+ 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 safecast;
+    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)
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 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 safecast1;
+   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 safecast1;
+ 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 safecast1;
+ 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 safecast1;
+ 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) 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 safecast1;
+    time     |  to_times   |   to_timetz    |          to_interval          |       to_interval_scale       
+-------------+-------------+----------------+-------------------------------+-------------------------------
+ 15:36:39    | 15:36:39    | 15:36:39-07    | @ 15 hours 36 mins 39 secs    | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | @ 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 safecast1;
+     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)
+
+CREATE TABLE safecast2(col0 jsonb, col1 text );
+INSERT INTO safecast2 VALUES ('"test"', '<value>one</value');
+SELECT col1 as text,
+       CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+       CAST(col0 AS "char"  DEFAULT NULL ON CONVERSION ERROR) as to_char,
+       CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+       CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM safecast2;
+       text        | to_regclass | to_char | to_name | to_xml 
+-------------------+-------------+---------+---------+--------
+ <value>one</value |             |         | "test"  | 
+(1 row)
+
+SELECT col0 as jsonb,
+       CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+       CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+       CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM safecast2;
+ jsonb  | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint 
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" |            |            |         |           |           |         |            
+(1 row)
+
+-- test deparse
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+       CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as safecast,
+       CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview
+CREATE OR REPLACE VIEW public.safecastview AS
+ SELECT CAST('1234' AS character(3) DEFAULT '-1111'::integer::character(3) ON CONVERSION ERROR) AS bpchar,
+    CAST(1 AS date DEFAULT now()::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS safecast,
+    CAST(ARRAY[ARRAY['1'], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+    CAST(ARRAY[ARRAY['1'::text, '2'::text], ARRAY['three'::text, 'a'::text]] AS text[] DEFAULT '{21,22}'::text[] ON CONVERSION ERROR) AS cast2
+CREATE INDEX cast_error_idx  ON tcast((cast(c as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
+ERROR:  functions in index expression must be marked IMMUTABLE
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index fd4871d94db..1ec99eef670 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -86,6 +86,11 @@ 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 when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
 -- 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/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 20bf9ea9cdf..25478364b7c 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
 FROM pg_cast c
 WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
     OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod 
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe 
+-----+------------+------------+----------+-------------+------------+---------------
 (0 rows)
 
 -- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
 FROM pg_cast c
 WHERE (castmethod = 'f' AND castfunc = 0)
    OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod 
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe 
+-----+------------+------------+----------+-------------+------------+---------------
 (0 rows)
 
 -- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
 SELECT *
 FROM pg_cast c
 WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod 
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe 
+-----+------------+------------+----------+-------------+------------+---------------
 (0 rows)
 
 SELECT c.*
 FROM pg_cast c, pg_proc p
 WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod 
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe 
+-----+------------+------------+----------+-------------+------------+---------------
 (0 rows)
 
 -- Look for cast functions that don't have the right signature.  The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
              OR (c.castsource = 'character'::regtype AND
                  p.proargtypes[0] = 'text'::regtype))
      OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod 
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe 
+-----+------------+------------+----------+-------------+------------+---------------
 (0 rows)
 
 SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
 WHERE c.castfunc = p.oid AND
     ((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
      (p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod 
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe 
+-----+------------+------------+----------+-------------+------------+---------------
 (0 rows)
 
 -- Look for binary compatible casts that do not have the reverse

 # ----------
 # Run these alone so they don't run out of parallel workers
diff --git a/src/test/regress/sql/cast.sql b/src/test/regress/sql/cast.sql
new file mode 100644
index 00000000000..27334212c8c
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,259 @@
+SET extra_float_digits = 0;
+
+-- CAST DEFAULT ON CONVERSION ERROR
+VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); --error
+VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR));
+VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR));
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE OR REPLACE 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 array coerce
+SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON CONVERSION ERROR);
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
+$$
+BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY, c text default '1');
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+
+SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t;
+SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t;
+
+-- test with domain
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+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('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error
+
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from range type to other type
+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 from geometry to other geometry is not supported
+SELECT CAST(NULL::point AS box DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::lseg AS point DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::path AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::box AS point DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::box AS lseg DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::box AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::box AS circle DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::polygon AS point DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::polygon AS path DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::polygon AS box DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::polygon AS circle DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::circle AS point DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::circle AS box DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::circle AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+
+-----safe cast from money or money cast to other type is not supported
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR); --error
+
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[],
+                        col5 int2 default 32767,
+                        col6 int4 default 32768,
+                        col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO safecast VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 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 safecast1;
+
+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 safecast1;
+
+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 safecast1;
+
+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 safecast1;
+
+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) 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 safecast1;
+
+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 safecast1;
+
+CREATE TABLE safecast2(col0 jsonb, col1 text );
+INSERT INTO safecast2 VALUES ('"test"', '<value>one</value');
+
+SELECT col1 as text,
+       CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+       CAST(col0 AS "char"  DEFAULT NULL ON CONVERSION ERROR) as to_char,
+       CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+       CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM safecast2;
+
+SELECT col0 as jsonb,
+       CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+       CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+       CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM safecast2;
+
+-- test deparse
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+       CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as safecast,
+       CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview
+
+CREATE INDEX cast_error_idx  ON tcast((cast(c as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ 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
 
 -- check dependencies generated for that
 SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e6f2e93b2d6..5b32df3a2db 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2652,6 +2652,9 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

From 2efaf3b582b15d62b3250535dfbc0840a7444dcb Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sun, 10 Aug 2025 01:56:03 +0800
Subject: [PATCH v6 10/18] error safe for casting text to other types per
 pg_cast

and castsource::regtype = 'text'::regtype

 castsource | casttarget | castfunc | castcontext | castmethod |    prosrc     | proname
------------+------------+----------+-------------+------------+---------------+----------
 text       | regclass   |     1079 | i           | f          | text_regclass | regclass
 text       | "char"     |      944 | a           | f          | text_char     | char
 text       | name       |      407 | i           | f          | text_name     | name
 text       | xml        |     2896 | e           | f          | texttoxml     | xml
(4 rows)

already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
---
 src/backend/catalog/namespace.c | 167 ++++++++++++++++++++++++++++++++
 src/backend/utils/adt/regproc.c |  22 ++++-
 src/backend/utils/adt/varlena.c |  42 ++++++++
 src/include/catalog/namespace.h |   4 +
 src/include/utils/varlena.h     |   1 +
 5 files changed, 233 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d97d632a7ef..4a5506eeac5 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -641,6 +641,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	return relId;
 }
 
+/* see RangeVarGetRelidExtended also */
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
+							 uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
+							 Node *escontext)
+{
+	uint64		inval_count;
+	Oid			relId;
+	Oid			oldRelId = InvalidOid;
+	bool		retry = false;
+	bool		missing_ok = (flags & RVR_MISSING_OK) != 0;
+
+	/* verify that flags do no conflict */
+	Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED)));
+
+	/*
+	 * We check the catalog name and then ignore it.
+	 */
+	if (relation->catalogname)
+	{
+		if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
+			ereturn(escontext, InvalidOid,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
+							relation->catalogname, relation->schemaname,
+							relation->relname));
+	}
+
+	for (;;)
+	{
+		inval_count = SharedInvalidMessageCounter;
+
+		if (relation->relpersistence == RELPERSISTENCE_TEMP)
+		{
+			if (!OidIsValid(myTempNamespace))
+				relId = InvalidOid;
+			else
+			{
+				if (relation->schemaname)
+				{
+					Oid			namespaceId;
+
+					namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+
+					/*
+					 * For missing_ok, allow a non-existent schema name to
+					 * return InvalidOid.
+					 */
+					if (namespaceId != myTempNamespace)
+						ereturn(escontext, InvalidOid,
+								errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+								errmsg("temporary tables cannot specify a schema name"));
+				}
+
+				relId = get_relname_relid(relation->relname, myTempNamespace);
+			}
+		}
+		else if (relation->schemaname)
+		{
+			Oid			namespaceId;
+
+			/* use exact schema given */
+			namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+			if (missing_ok && !OidIsValid(namespaceId))
+				relId = InvalidOid;
+			else
+				relId = get_relname_relid(relation->relname, namespaceId);
+		}
+		else
+		{
+			/* search the namespace path */
+			relId = RelnameGetRelid(relation->relname);
+		}
+
+		if (callback)
+			callback(relation, relId, oldRelId, callback_arg);
+
+		if (lockmode == NoLock)
+			break;
+
+		if (retry)
+		{
+			if (relId == oldRelId)
+				break;
+			if (OidIsValid(oldRelId))
+				UnlockRelationOid(oldRelId, lockmode);
+		}
+
+		if (!OidIsValid(relId))
+			AcceptInvalidationMessages();
+		else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED)))
+			LockRelationOid(relId, lockmode);
+		else if (!ConditionalLockRelationOid(relId, lockmode))
+		{
+			if (relation->schemaname)
+				ereport(DEBUG1,
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s.%s\"",
+								relation->schemaname, relation->relname));
+			else
+				ereport(DEBUG1,
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s\"",
+							   relation->relname));
+
+			return InvalidOid;
+		}
+
+		if (inval_count == SharedInvalidMessageCounter)
+			break;
+
+		retry = true;
+		oldRelId = relId;
+	}
+
+	if (!OidIsValid(relId))
+	{
+		if (relation->schemaname)
+			ereport(DEBUG1,
+					errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s.%s\" does not exist",
+							relation->schemaname, relation->relname));
+		else
+			ereport(DEBUG1,
+					errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s\" does not exist",
+							relation->relname));
+	}
+	return relId;
+}
+
 /*
  * RangeVarGetCreationNamespace
  *		Given a RangeVar describing a to-be-created relation,
@@ -3580,6 +3711,42 @@ makeRangeVarFromNameList(const List *names)
 	return rel;
 }
 
+/*
+ * makeRangeVarFromNameListSafe
+ *		Utility routine to convert a qualified-name list into RangeVar form.
+ * The result maybe NULL.
+ */
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
+{
+	RangeVar   *rel = makeRangeVar(NULL, NULL, -1);
+
+	switch (list_length(names))
+	{
+		case 1:
+			rel->relname = strVal(linitial(names));
+			break;
+		case 2:
+			rel->schemaname = strVal(linitial(names));
+			rel->relname = strVal(lsecond(names));
+			break;
+		case 3:
+			rel->catalogname = strVal(linitial(names));
+			rel->schemaname = strVal(lsecond(names));
+			rel->relname = strVal(lthird(names));
+			break;
+		default:
+			errsave(escontext,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("improper relation name (too many dotted names): %s",
+							NameListToString(names)));
+			rel = NULL;
+			break;
+	}
+
+	return rel;
+}
+
 /*
  * NameListToString
  *		Utility routine to convert a qualified-name list into a string.
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index b8bbe95e82e..e7f259e8a0a 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1895,10 +1895,26 @@ text_regclass(PG_FUNCTION_ARGS)
 	Oid			result;
 	RangeVar   *rv;
 
-	rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+	if (likely(!fcinfo->context))
+	{
+		rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
 
-	/* We might not even have permissions on this relation; don't lock it. */
-	result = RangeVarGetRelid(rv, NoLock, false);
+		/* We might not even have permissions on this relation; don't lock it. */
+		result = RangeVarGetRelid(rv, NoLock, false);
+	}
+	else
+	{
+		List   		*rvnames;
+
+		rvnames = textToQualifiedNameListSafe(relname, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			PG_RETURN_NULL();
+
+		rv = makeRangeVarFromNameList(rvnames);
+		result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			PG_RETURN_NULL();
+	}
 
 	PG_RETURN_OID(result);
 }
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 11b442a5941..80852d5e922 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2724,6 +2724,48 @@ textToQualifiedNameList(text *textval)
 	return result;
 }
 
+/* see textToQualifiedNameList also */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
+{
+	char	   *rawname;
+	List	   *result = NIL;
+	List	   *namelist;
+	ListCell   *l;
+
+	/* Convert to C string (handles possible detoasting). */
+	/* Note we rely on being able to modify rawname below. */
+	rawname = text_to_cstring(textval);
+
+	if (!SplitIdentifierString(rawname, '.', &namelist))
+	{
+		errsave(escontext,
+				errcode(ERRCODE_INVALID_NAME),
+				errmsg("invalid name syntax"));
+		return NIL;
+	}
+
+	if (namelist == NIL)
+	{
+		errsave(escontext,
+				errcode(ERRCODE_INVALID_NAME),
+				errmsg("invalid name syntax"));
+		return NIL;
+	}
+
+	foreach(l, namelist)
+	{
+		char	   *curname = (char *) lfirst(l);
+
+		result = lappend(result, makeString(pstrdup(curname)));
+	}
+
+	pfree(rawname);
+	list_free(namelist);
+
+	return result;
+}
+
 /*
  * SplitIdentifierString --- parse a string containing identifiers
  *
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 8c7ccc69a3c..e095cbe70fa 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -85,6 +85,9 @@ extern Oid	RangeVarGetRelidExtended(const RangeVar *relation,
 									 LOCKMODE lockmode, uint32 flags,
 									 RangeVarGetRelidCallback callback,
 									 void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
+										uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
+										Node *escontext);
 extern Oid	RangeVarGetCreationNamespace(const RangeVar *newRelation);
 extern Oid	RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
 												 LOCKMODE lockmode,
@@ -148,6 +151,7 @@ extern Oid	LookupCreationNamespace(const char *nspname);
 extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
 extern Oid	QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
 extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
 extern char *NameListToString(const List *names);
 extern char *NameListToQuotedString(const List *names);
 
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int	varstr_levenshtein_less_equal(const char *source, int slen,
 										  int ins_c, int del_c, int sub_c,
 										  int max_d, bool trusted);
 extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
 extern bool SplitIdentifierString(char *rawstring, char separator,
 								  List **namelist);
 extern bool SplitDirectoriesString(char *rawstring, char separator,
-- 
2.34.1

From f118cff3a42d350e0e2a7274ac54f6f9a8771ed9 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sun, 10 Aug 2025 01:17:27 +0800
Subject: [PATCH v6 12/18] error safe for casting interval to other types per
 pg_cast

and castsource::regtype = 'interval'::regtype

 castsource |       casttarget       | castfunc | castcontext | castmethod |     prosrc     | proname
------------+------------------------+----------+-------------+------------+----------------+----------
 interval   | time without time zone |     1419 | a           | f          | interval_time  | time
 interval   | interval               |     1200 | i           | f          | interval_scale | interval
(2 rows)
---
 src/backend/utils/adt/date.c      | 2 +-
 src/backend/utils/adt/timestamp.c | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c7a3cde2d81..4f0f3d26989 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2180,7 +2180,7 @@ interval_time(PG_FUNCTION_ARGS)
 	TimeADT		result;
 
 	if (INTERVAL_NOT_FINITE(span))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("cannot convert infinite interval to time")));
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e640b48205b..f1e96b84a6f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
 	result = palloc(sizeof(Interval));
 	*result = *interval;
 
-	AdjustIntervalForTypmod(result, typmod, NULL);
+	if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+		PG_RETURN_NULL();
 
 	PG_RETURN_INTERVAL_P(result);
 }
-- 
2.34.1

From 44d59f188fb4dfad880d9f5d25cd3e5625529c8e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 5 Aug 2025 16:55:44 +0800
Subject: [PATCH v6 13/18] error safe for casting macaddr8 to other types per
 pg_cast

select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;

 castsource | casttarget | castfunc | castcontext | castmethod |      prosrc       | proname
------------+------------+----------+-------------+------------+-------------------+---------
 macaddr8   | macaddr    |     4124 | i           | f          | macaddr8tomacaddr | macaddr
(1 row)
---
 src/backend/utils/adt/mac8.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..1c903f152de 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
 	result = (macaddr *) palloc0(sizeof(macaddr));
 
 	if ((addr->d != 0xFF) || (addr->e != 0xFE))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("macaddr8 data out of range to convert to macaddr"),
 				 errhint("Only addresses that have FF and FE as values in the "
-- 
2.34.1

From 5e9ced90d27245b90b80294dde7c64405abd6f5d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 9 Aug 2025 22:35:57 +0800
Subject: [PATCH v6 09/18] error safe for casting date to other types per
 pg_cast

and castsource::regtype = 'date'::regtype

castsource |         casttarget          | castfunc | castcontext | castmethod |      prosrc      |   proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
 date       | timestamp without time zone |     2024 | i           | f          | date_timestamp   | timestamp
 date       | timestamp with time zone    |     1174 | i           | f          | date_timestamptz | timestamptz
(2 rows)
---
 src/backend/utils/adt/date.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..c7a3cde2d81 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1350,7 +1350,16 @@ date_timestamp(PG_FUNCTION_ARGS)
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
 	Timestamp	result;
 
-	result = date2timestamp(dateVal);
+	if (likely(!fcinfo->context))
+		result = date2timestamp(dateVal);
+	else
+	{
+		int			overflow;
+
+		result = date2timestamp_opt_overflow(dateVal, &overflow);
+		if (overflow != 0)
+			PG_RETURN_NULL();
+	}
 
 	PG_RETURN_TIMESTAMP(result);
 }
@@ -1435,7 +1444,16 @@ date_timestamptz(PG_FUNCTION_ARGS)
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
 	TimestampTz result;
 
-	result = date2timestamptz(dateVal);
+	if (likely(!fcinfo->context))
+		result = date2timestamptz(dateVal);
+	else
+	{
+		int			overflow;
+		result = date2timestamptz_opt_overflow(dateVal, &overflow);
+
+		if (overflow != 0)
+			PG_RETURN_NULL();
+	}
 
 	PG_RETURN_TIMESTAMP(result);
 }
-- 
2.34.1

From fe3598c63639bc81051b0127c15b1aef3e2115f5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 5 Aug 2025 15:42:58 +0800
Subject: [PATCH v6 11/18] error safe for casting inet to other types per
 pg_cast

and castsource::regtype = 'inet'::regtype

 castsource |    casttarget     | castfunc | castcontext | castmethod |    prosrc    | proname
------------+-------------------+----------+-------------+------------+--------------+---------
 inet       | cidr              |     1715 | a           | f          | inet_to_cidr | cidr
 inet       | text              |      730 | a           | f          | network_show | text
 inet       | character varying |      730 | a           | f          | network_show | text
 inet       | character         |      730 | a           | f          | network_show | text
(4 rows)
---
 src/backend/utils/adt/network.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 9fd211b2d45..0e2021bcc3c 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1167,7 +1167,7 @@ network_show(PG_FUNCTION_ARGS)
 
 	if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
 						 tmp, sizeof(tmp)) == NULL)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
 				 errmsg("could not format inet value: %m")));
 
-- 
2.34.1

From 6a409bdb5c41ed3b0e2b8b51530bd6d6be0dc9f0 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 6 Aug 2025 12:12:31 +0800
Subject: [PATCH v6 08/18] error safe for casting jsonb to other types per
 pg_cast

and castsource::regtype = 'jsonb'::regtype

 castsource |    casttarget    | castfunc | castcontext | castmethod |    prosrc     | proname
------------+------------------+----------+-------------+------------+---------------+---------
 jsonb      | boolean          |     3556 | e           | f          | jsonb_bool    | bool
 jsonb      | numeric          |     3449 | e           | f          | jsonb_numeric | numeric
 jsonb      | smallint         |     3450 | e           | f          | jsonb_int2    | int2
 jsonb      | integer          |     3451 | e           | f          | jsonb_int4    | int4
 jsonb      | bigint           |     3452 | e           | f          | jsonb_int8    | int8
 jsonb      | real             |     3453 | e           | f          | jsonb_float4  | float4
 jsonb      | double precision |     2580 | e           | f          | jsonb_float8  | float8
(7 rows)
---
 src/backend/utils/adt/jsonb.c | 35 ++++++++++++++++++-----------------
 1 file changed, 18 insertions(+), 17 deletions(-)

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index da94d424d61..7f3715b1684 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2004,8 +2004,8 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
 /*
  * Emit correct, translatable cast error message
  */
-static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+static Datum
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
 {
 	static const struct
 	{
@@ -2026,12 +2026,13 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
 
 	for (i = 0; i < lengthof(messages); i++)
 		if (messages[i].type == type)
-			ereport(ERROR,
+			ereturn(escontext, (Datum) 0,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg(messages[i].msg, sqltype)));
 
 	/* should be unreachable */
 	elog(ERROR, "unknown jsonb type: %d", (int) type);
+	return (Datum) 0;
 }
 
 Datum
@@ -2041,7 +2042,7 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	JsonbValue	v;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "boolean");
+		return cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
 
 	if (v.type == jbvNull)
 	{
@@ -2050,7 +2051,7 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvBool)
-		cannotCastJsonbValue(v.type, "boolean");
+		return cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
 
 	PG_FREE_IF_COPY(in, 0);
 
@@ -2065,7 +2066,7 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	Numeric		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "numeric");
+		return cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
 
 	if (v.type == jbvNull)
 	{
@@ -2074,7 +2075,7 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "numeric");
+		return cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
 
 	/*
 	 * v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2095,7 +2096,7 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "smallint");
+		return cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
 
 	if (v.type == jbvNull)
 	{
@@ -2104,7 +2105,7 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "smallint");
+		return cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
 
 	retValue = DirectFunctionCall1(numeric_int2,
 								   NumericGetDatum(v.val.numeric));
@@ -2122,7 +2123,7 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "integer");
+		return cannotCastJsonbValue(v.type, "integer", fcinfo->context);
 
 	if (v.type == jbvNull)
 	{
@@ -2131,7 +2132,7 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "integer");
+		return cannotCastJsonbValue(v.type, "integer", fcinfo->context);
 
 	retValue = DirectFunctionCall1(numeric_int4,
 								   NumericGetDatum(v.val.numeric));
@@ -2149,7 +2150,7 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "bigint");
+		return cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
 
 	if (v.type == jbvNull)
 	{
@@ -2158,7 +2159,7 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "bigint");
+		return cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
 
 	retValue = DirectFunctionCall1(numeric_int8,
 								   NumericGetDatum(v.val.numeric));
@@ -2176,7 +2177,7 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "real");
+		return cannotCastJsonbValue(v.type, "real", fcinfo->context);
 
 	if (v.type == jbvNull)
 	{
@@ -2185,7 +2186,7 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "real");
+		return cannotCastJsonbValue(v.type, "real", fcinfo->context);
 
 	retValue = DirectFunctionCall1(numeric_float4,
 								   NumericGetDatum(v.val.numeric));
@@ -2203,7 +2204,7 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "double precision");
+		return cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
 
 	if (v.type == jbvNull)
 	{
@@ -2212,7 +2213,7 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "double precision");
+		return cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
 
 	retValue = DirectFunctionCall1(numeric_float8,
 								   NumericGetDatum(v.val.numeric));
-- 
2.34.1

From 60c9c8cac8cd24803ec7319eb19ff4cdd838cb72 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 9 Aug 2025 22:17:48 +0800
Subject: [PATCH v6 06/18] error safe for casting float4 to other types per
 pg_cast

and castsource::regtype = 'float4'::regtype

 castsource |    casttarget    | castfunc | castcontext | castmethod |     prosrc     | proname
------------+------------------+----------+-------------+------------+----------------+---------
 real       | bigint           |      653 | a           | f          | ftoi8          | int8
 real       | smallint         |      238 | a           | f          | ftoi2          | int2
 real       | integer          |      319 | a           | f          | ftoi4          | int4
 real       | double precision |      311 | i           | f          | ftod           | float8
 real       | numeric          |     1742 | a           | f          | float4_numeric | numeric
(5 rows)

float4 to float8, numeric is error safe, so no need refactor ftod,
float4_numeric.
---
 src/backend/utils/adt/float.c | 4 ++--
 src/backend/utils/adt/int8.c  | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..ab5c38d723d 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,7 @@ ftoi4(PG_FUNCTION_ARGS)
 
 	/* Range check */
 
@@ -1323,7 +1323,7 @@ ftoi2(PG_FUNCTION_ARGS)
 
 	/* Range check */
 
index 96a6737a95c..8c38d02d011 100644
 
 	/* Range check */
 
-- 
2.34.1

From 5b4d86921d852efda26e3483c85e92ca77581f9c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 9 Aug 2025 22:26:13 +0800
Subject: [PATCH v6 07/18] error safe for casting float8 to other types per
 pg_cast

and castsource::regtype = 'float8'::regtype

    castsource    | casttarget | castfunc | castcontext | castmethod |     prosrc     | proname
------------------+------------+----------+-------------+------------+----------------+---------
 double precision | bigint     |      483 | a           | f          | dtoi8          | int8
 double precision | smallint   |      237 | a           | f          | dtoi2          | int2
 double precision | integer    |      317 | a           | f          | dtoi4          | int4
 double precision | real       |      312 | a           | f          | dtof           | float4
 double precision | numeric    |     1743 | a           | f          | float8_numeric | numeric
(5 rows)
---
 src/backend/utils/adt/float.c | 12 ++++++++----
 src/backend/utils/adt/int8.c  |  2 +-
 2 files changed, 9 insertions(+), 5 deletions(-)

index ab5c38d723d..6461e9c94b1 100644
 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */
 
index 8c38d02d011..5bddcccf378 100644
 
 	/* Range check */
 
-- 
2.34.1

From 3738b36237e21a9341abea8a599e7ed7fa899307 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 9 Aug 2025 21:33:56 +0800
Subject: [PATCH v6 05/18] error safe for casting numeric to other types per
 pg_cast

and castsource::regtype = 'numeric'::regtype

castsource |    casttarget    | castfunc | castcontext | castmethod |     prosrc     | proname
------------+------------------+----------+-------------+------------+----------------+---------
 numeric    | bigint           |     1779 | a           | f          | numeric_int8   | int8
 numeric    | smallint         |     1783 | a           | f          | numeric_int2   | int2
 numeric    | integer          |     1744 | a           | f          | numeric_int4   | int4
 numeric    | real             |     1745 | i           | f          | numeric_float4 | float4
 numeric    | double precision |     1746 | i           | f          | numeric_float8 | float8
 numeric    | money            |     3824 | a           | f          | numeric_cash   | money
 numeric    | numeric          |     1703 | i           | f          | numeric        | numeric
(7 rows)

current safe cast for money data type is not supported, so no realted function
refactoring.
---
 src/backend/utils/adt/numeric.c | 69 ++++++++++++++++++++++++++-------
 1 file changed, 56 insertions(+), 13 deletions(-)

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 122f2efab8b..2ab26d59e41 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1259,7 +1259,8 @@ numeric		(PG_FUNCTION_ARGS)
 	 */
 	if (NUMERIC_IS_SPECIAL(num))
 	{
-		(void) apply_typmod_special(num, typmod, NULL);
+		if (!apply_typmod_special(num, typmod, fcinfo->context))
+			PG_RETURN_NULL();
 		PG_RETURN_NUMERIC(duplicate_numeric(num));
 	}
 
@@ -1310,7 +1311,8 @@ numeric		(PG_FUNCTION_ARGS)
 	init_var(&var);
 
 	set_var_from_num(num, &var);
-	(void) apply_typmod(&var, typmod, NULL);
+	if (!apply_typmod(&var, typmod, fcinfo->context))
+		PG_RETURN_NULL();
 	new = make_result(&var);
 
 	free_var(&var);
@@ -4550,7 +4552,22 @@ numeric_int4(PG_FUNCTION_ARGS)
 {
 	Numeric		num = PG_GETARG_NUMERIC(0);
 
+	if (likely(!fcinfo->context))
 
 /*
@@ -4638,7 +4655,22 @@ numeric_int8(PG_FUNCTION_ARGS)
 {
 	Numeric		num = PG_GETARG_NUMERIC(0);
 
+	if (likely(!fcinfo->context))

@@ -4662,11 +4694,11 @@ numeric_int2(PG_FUNCTION_ARGS)
 	if (NUMERIC_IS_SPECIAL(num))
 	{
 		if (NUMERIC_IS_NAN(num))
-			ereport(ERROR,
+			ereturn(fcinfo->context, (Datum) 0,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert NaN to %s", "smallint")));
 		else
-			ereport(ERROR,
+			ereturn(fcinfo->context, (Datum) 0,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert infinity to %s", "smallint")));
 	}
@@ -4675,12 +4707,12 @@ numeric_int2(PG_FUNCTION_ARGS)
 	init_var_from_num(num, &x);

 
@@ -4745,10 +4777,14 @@ numeric_float8(PG_FUNCTION_ARGS)

 	PG_RETURN_DATUM(result);
 }
@@ -4840,7 +4876,14 @@ numeric_float4(PG_FUNCTION_ARGS)
 	tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
 											  NumericGetDatum(num)));

 	pfree(tmp);
 
-- 
2.34.1

From 9ca7cb44e80f642f1e602ce55012bbbb6cca80f4 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 9 Aug 2025 21:25:38 +0800
Subject: [PATCH v6 04/18] error safe for casting bigint to other types per
 pg_cast

and castsource::regtype = 'bigint'::regtype

 castsource |    casttarget    | castfunc | castcontext | castmethod |    prosrc    | proname
------------+------------------+----------+-------------+------------+--------------+---------
 bigint     | smallint         |      714 | a           | f          | int82        | int2
 bigint     | integer          |      480 | a           | f          | int84        | int4
 bigint     | real             |      652 | i           | f          | i8tof        | float4
 bigint     | double precision |      482 | i           | f          | i8tod        | float8
 bigint     | numeric          |     1781 | i           | f          | int8_numeric | numeric
 bigint     | money            |     3812 | a           | f          | int8_cash    | money
 bigint     | oid              |     1287 | i           | f          | i8tooid      | oid
 bigint     | regproc          |     1287 | i           | f          | i8tooid      | oid
 bigint     | regprocedure     |     1287 | i           | f          | i8tooid      | oid
 bigint     | regoper          |     1287 | i           | f          | i8tooid      | oid
 bigint     | regoperator      |     1287 | i           | f          | i8tooid      | oid
 bigint     | regclass         |     1287 | i           | f          | i8tooid      | oid
 bigint     | regcollation     |     1287 | i           | f          | i8tooid      | oid
 bigint     | regtype          |     1287 | i           | f          | i8tooid      | oid
 bigint     | regconfig        |     1287 | i           | f          | i8tooid      | oid
 bigint     | regdictionary    |     1287 | i           | f          | i8tooid      | oid
 bigint     | regrole          |     1287 | i           | f          | i8tooid      | oid
 bigint     | regnamespace     |     1287 | i           | f          | i8tooid      | oid
 bigint     | regdatabase      |     1287 | i           | f          | i8tooid      | oid
 bigint     | bytea            |     6369 | e           | f          | int8_bytea   | bytea
 bigint     | bit              |     2075 | e           | f          | bitfromint8  | bit
(21 rows)

already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
---
 src/backend/utils/adt/int8.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 9dd5889f34c..96a6737a95c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1251,7 +1251,7 @@ int84(PG_FUNCTION_ARGS)
 	int64		arg = PG_GETARG_INT64(0);
 
 	if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("integer out of range")));
 
@@ -1272,7 +1272,7 @@ int82(PG_FUNCTION_ARGS)
 	int64		arg = PG_GETARG_INT64(0);
 
 	if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
@@ -1355,7 +1355,7 @@ i8tooid(PG_FUNCTION_ARGS)
 	int64		arg = PG_GETARG_INT64(0);
 
 	if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("OID out of range")));
 
-- 
2.34.1

From bb6577b6491b0cd78760c068fc26521437662513 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 9 Aug 2025 21:20:32 +0800
Subject: [PATCH v6 03/18] error safe for casting integer to other types per
 pg_cast

and castsource::regtype = 'integer'::regtype

 castsource |    casttarget    | castfunc | castcontext | castmethod |    prosrc    | proname
------------+------------------+----------+-------------+------------+--------------+---------
 integer    | bigint           |      481 | i           | f          | int48        | int8
 integer    | smallint         |      314 | a           | f          | i4toi2       | int2
 integer    | real             |      318 | i           | f          | i4tof        | float4
 integer    | double precision |      316 | i           | f          | i4tod        | float8
 integer    | numeric          |     1740 | i           | f          | int4_numeric | numeric
 integer    | money            |     3811 | a           | f          | int4_cash    | money
 integer    | boolean          |     2557 | e           | f          | int4_bool    | bool
 integer    | bytea            |     6368 | e           | f          | int4_bytea   | bytea
 integer    | "char"           |       78 | e           | f          | i4tochar     | char
 integer    | bit              |     1683 | e           | f          | bitfromint4  | bit
(10 rows)

only int4_cash, i4toi2, i4tochar need take care of error handling.  but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
---
 src/backend/utils/adt/char.c | 2 +-
 src/backend/utils/adt/int.c  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..e90844a29f0 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
 	int32		arg1 = PG_GETARG_INT32(0);
 
 	if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("\"char\" out of range")));
 
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..b45599d402d 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
 	int32		arg1 = PG_GETARG_INT32(0);
 
 	if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
-- 
2.34.1

From efc37e956c0af2d0a255b8f047f68d8b67784761 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 4 Aug 2025 20:11:53 +0800
Subject: [PATCH v6 01/18] error safe for casting bytea to other types per
 pg_cast

select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;

 castsource | casttarget | castfunc | castcontext | castmethod |   prosrc   | proname
------------+------------+----------+-------------+------------+------------+---------
 bytea      | smallint   |     6370 | e           | f          | bytea_int2 | int2
 bytea      | integer    |     6371 | e           | f          | bytea_int4 | int4
 bytea      | bigint     |     6372 | e           | f          | bytea_int8 | int8
(3 rows)
---
 src/backend/utils/adt/bytea.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..f43ad6b4aff 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,7 +1027,7 @@ bytea_int2(PG_FUNCTION_ARGS)
 
 	/* Check that the byte array is not too long */
 	if (len > sizeof(result))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				errmsg("smallint out of range"));
 
@@ -1052,7 +1052,7 @@ bytea_int4(PG_FUNCTION_ARGS)
 
 	/* Check that the byte array is not too long */
 	if (len > sizeof(result))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				errmsg("integer out of range"));
 
@@ -1077,7 +1077,7 @@ bytea_int8(PG_FUNCTION_ARGS)
 
 	/* Check that the byte array is not too long */
 	if (len > sizeof(result))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				errmsg("bigint out of range"));
 
-- 
2.34.1

From 4d0fc95b1fc9690b7a631b98d3d0f392a2a293bd Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 9 Aug 2025 17:49:04 +0800
Subject: [PATCH v6 02/18] error safe for casting character to other types per
 pg_cast

and castsource::regtype ='character'::regtype

 castsource |    casttarget     | castfunc | castcontext | castmethod |   prosrc    | proname
------------+-------------------+----------+-------------+------------+-------------+---------
 character  | text              |      401 | i           | f          | rtrim1      | text
 character  | character varying |      401 | i           | f          | rtrim1      | text
 character  | "char"            |      944 | a           | f          | text_char   | char
 character  | name              |      409 | i           | f          | bpchar_name | name
 character  | xml               |     2896 | e           | f          | texttoxml   | xml
 character  | character         |      668 | i           | f          | bpchar      | bpchar
(6 rows)

only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other already error safe.
---
 src/backend/executor/execExprInterp.c |  2 +-
 src/backend/utils/adt/varchar.c       |  2 +-
 src/backend/utils/adt/xml.c           | 12 ++++++++----
 src/include/utils/xml.h               |  2 +-
 4 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..67f4e00eac4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
 
 				*op->resvalue = PointerGetDatum(xmlparse(data,
 														 xexpr->xmloption,
-														 preserve_whitespace));
+														 preserve_whitespace, NULL));
 				*op->resnull = false;
 			}
 			break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..5cb5c8c46f9 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
 		{
 			for (i = maxmblen; i < len; i++)
 				if (s[i] != ' ')
-					ereport(ERROR,
+					ereturn(fcinfo->context, (Datum) 0,
 							(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
 							 errmsg("value too long for type character(%d)",
 									maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 182e8f75db7..1d423e0f9a3 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -660,7 +660,7 @@ texttoxml(PG_FUNCTION_ARGS)
 {
 	text	   *data = PG_GETARG_TEXT_PP(0);
 
-	PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+	PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
 }

@@ -1029,14 +1029,18 @@ xmlelement(XmlExpr *xexpr,

 xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
 {
 #ifdef USE_LIBXML
 	xmlDocPtr	doc;
 
 	doc = xml_parse(data, xmloption_arg, preserve_whitespace,
-					GetDatabaseEncoding(), NULL, NULL, NULL);
-	xmlFreeDoc(doc);
+					GetDatabaseEncoding(), NULL, NULL, escontext);
+	if (doc)
+		xmlFreeDoc(doc);
+
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return NULL;
 
 	return (xmltype *) data;
 #else
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 0d7a816b9f9..b3d3f11fac4 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
 extern xmltype *xmlelement(XmlExpr *xexpr,
 						   Datum *named_argvalue, bool *named_argnull,
 						   Datum *argvalue, bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
 extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
 extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
 extern bool xml_is_document(xmltype *arg);
-- 
2.34.1

---

From: jian he <[email-masked]>
Date: Fri, 10 Oct 2025 20:23:07 +0800

hi.

First of all, rebase v6.

select distinct castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0 and pt.typnamespace =
'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
order by castsource::regtype, casttarget::regtype;

The above query will list all cast functions in PG_CATALOG schema that
need to be
refactored to be error-safe.  looking around, I found that the below source data
type, the cast function is already error safe:

boolean
name
"char"  ("char" to integer was handled in integer cast part)
regprocedure
regoper
regoperator
regclass
regtype
regconfig
regdictionary
regproc
oid
regnamespace
regrole
regcollation
regdatabase
int4range
numrange
tsrange
tstzrange
daterange
int8range
xid8
time without time zone
time with time zone

After applying the attached v7 patch patchset,
the below are cast functions that I didn't refactor to error safe yet.

select pc.castsource::regtype,
       pc.casttarget::regtype, castfunc::regproc, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace and not pc.casterrorsafe;

 castsource | casttarget |       castfunc       |    prosrc
------------+------------+----------------------+--------------
 bigint     | money      | pg_catalog.money     | int8_cash
 integer    | money      | pg_catalog.money     | int4_cash
 numeric    | money      | pg_catalog.money     | numeric_cash
 money      | numeric    | pg_catalog."numeric" | cash_numeric
(4 rows)

The reason I don't refactor these cast functions related to money data
type  is because
1. I am not sure if the PGLC_localeconv() function is error safe or not.
2. refactoring such ``DirectFunctionCall1(numeric_int8, amount));``
requires more effort.

please check the attached v7 patch set:
01-error-safe-for-casting-bytea-to-other-types
02-error-safe-for-casting-bit-varbit-to-other-types
03-error-safe-for-casting-character-to-other-types
04-error-safe-for-casting-text-to-other-types
05-error-safe-for-casting-character-varying-to-other-types
06-error-safe-for-casting-inet-to-other-types
07-error-safe-for-casting-macaddr8-to-other-types
08-error-safe-for-casting-integer-to-other-types
09-error-safe-for-casting-bigint-to-other-types
10-error-safe-for-casting-numeric-to-other-types
11-error-safe-for-casting-float4-to-other-types
12-error-safe-for-casting-float8-to-other-types
13-error-safe-for-casting-date-to-other-types
14-error-safe-for-casting-interval-to-other-types
15-error-safe-for-casting-timestamptz-to-other-types
16-error-safe-for-casting-timestamp-to-other-types
17-error-safe-for-casting-jsonb-to-other-types
18-error-safe-for-casting-geometry-data-types
19-invent_some_error_safe_function.patch
20-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patch

Each patch includes a commit message explaining which function is being
refactored to be error-safe.

I also made some changes for
"20-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patch"

If the source expression is Const, it can be unknown type or other type.
* for UNKNOWN type, example:
SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON
CONVERSION ERROR);
We use CoerceUnknownConstSafe to verify earlier that such Const can be
coerced to target data type or not.

* for other data type, example:
SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON
CONVERSION ERROR);
we use evaluate_expr_safe to evaluate it error safe way earlier in
transformTypeSafeCast.
similar to transformPartitionBoundValue call evaluate_expr

The geometry cast functions ``(poly_circle(PG_FUNCTION_ARGS)`` have
problems making it error safe,
working on it.
From a70d65ce8183eef768eba2e340b208743539ddd5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 10 Oct 2025 18:57:21 +0800
Subject: [PATCH v7 19/20] invent some error safe functions

stringTypeDatumSafe: error safe version of stringTypeDatum
evaluate_expr_safe: error safe version of evaluate_expr
ExecInitExprSafe: soft error variant of ExecInitExpr
OidInputFunctionCallSafe: soft error variant of OidInputFunctionCall

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/executor/execExpr.c      | 41 +++++++++++++++++
 src/backend/optimizer/util/clauses.c | 69 ++++++++++++++++++++++++++++
 src/backend/parser/parse_type.c      | 14 ++++++
 src/backend/utils/fmgr/fmgr.c        | 13 ++++++
 src/include/executor/executor.h      |  1 +
 src/include/fmgr.h                   |  3 ++
 src/include/optimizer/optimizer.h    |  2 +
 src/include/parser/parse_type.h      |  2 +
 8 files changed, 145 insertions(+)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..b302be36f73 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -170,6 +170,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
 	return state;
 }
 
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+	ExprState  *state;
+	ExprEvalStep scratch = {0};
+
+	/* Special case: NULL expression produces a NULL ExprState pointer */
+	if (node == NULL)
+		return NULL;
+
+	/* Initialize ExprState with empty step list */
+	state = makeNode(ExprState);
+	state->expr = node;
+	state->parent = parent;
+	state->ext_params = NULL;
+	state->escontext = makeNode(ErrorSaveContext);
+	state->escontext->type = T_ErrorSaveContext;
+	state->escontext->error_occurred = false;
+	state->escontext->details_wanted = false;
+
+	/* Insert setup steps as needed */
+	ExecCreateExprSetupSteps(state, (Node *) node);
+
+	/* Compile the expression proper */
+	ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+	/* Finally, append a DONE step */
+	scratch.opcode = EEOP_DONE_RETURN;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecReadyExpr(state);
+
+	return state;
+}
+
 /*
  * ExecInitExprWithParams: prepare a standalone expression tree for execution
  *
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 81d768ff2a2..3f9ff455ba6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -5147,6 +5147,75 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 							  resultTypByVal);
 }
 
+/*
+ * evaluate_expr_safe: error safe version of evaluate_expr
+ *
+ * We use the executor's routine ExecEvalExpr() to avoid duplication of
+ * code and ensure we get the same result as the executor would get.
+ *
+ * return NULL when evaulation failed. Ensure the expression expr can be evaulated
+ * in a soft error way.
+ *
+ * See comments on evaluate_expr too.
+ */
+Expr *
+evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+				   Oid result_collation)
+{
+	EState	   *estate;
+	ExprState  *exprstate;
+	MemoryContext oldcontext;
+	Datum		const_val;
+	bool		const_is_null;
+	int16		resultTypLen;
+	bool		resultTypByVal;
+
+	estate = CreateExecutorState();
+
+	/* We can use the estate's working context to avoid memory leaks. */
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	/* Make sure any opfuncids are filled in. */
+	fix_opfuncids((Node *) expr);
+
+	/*
+	 * Prepare expr for execution.  (Note: we can't use ExecPrepareExpr
+	 * because it'd result in recursively invoking eval_const_expressions.)
+	 */
+	exprstate = ExecInitExprSafe(expr, NULL);
+
+	const_val = ExecEvalExprSwitchContext(exprstate,
+										  GetPerTupleExprContext(estate),
+										  &const_is_null);
+
+	/* Get info needed about result datatype */
+	get_typlenbyval(result_type, &resultTypLen, &resultTypByVal);
+
+	/* Get back to outer memory context */
+	MemoryContextSwitchTo(oldcontext);
+
+	if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+	{
+		FreeExecutorState(estate);
+		return NULL;
+	}
+
+	if (!const_is_null)
+	{
+		if (resultTypLen == -1)
+			const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(const_val));
+		else
+			const_val = datumCopy(const_val, resultTypByVal, resultTypLen);
+	}
+
+	FreeExecutorState(estate);
+
+	return (Expr *) makeConst(result_type, result_typmod, result_collation,
+							  resultTypLen,
+							  const_val, const_is_null,
+							  resultTypByVal);
+}
+
 
 /*
  * inline_set_returning_function
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..d260aeec5dc 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "utils/array.h"
@@ -660,6 +661,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
 	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
 }
 
+/* error safe version of stringTypeDatum */
+bool
+									(Node *) &escontext, result);
index 0fe63c6bb83..aaa4a42b1ea 100644
 
+bool
+OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+						 int32 typmod, Node *escontext,
+						 Datum *result)
+{
+	FmgrInfo			flinfo;
+
+	fmgr_info(functionId, &flinfo);
+
+	return InputFunctionCallSafe(&flinfo, str, typioparam, typmod,
+								 escontext, result);
+}
+
 char *
 OidOutputFunctionCall(Oid functionId, Datum val)
 {
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3248e78cd28..ff9997e1cc1 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -321,6 +321,7 @@ ExecProcNode(PlanState *node)
  * prototypes from functions in execExpr.c
  */
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
 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/fmgr.h b/src/include/fmgr.h
index 74fe3ea0575..991e14034d3 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -750,6 +750,9 @@ extern bool DirectInputFunctionCallSafe(PGFunction func, char *str,
 										Datum *result);
 extern Datum OidInputFunctionCall(Oid functionId, char *str,
 								  Oid typioparam, int32 typmod);
+extern bool OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+									 int32 typmod, Node *escontext,
+									 Datum *result);
 extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
 extern char *OidOutputFunctionCall(Oid functionId, Datum val);
 extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index a34113903c0..f1b6381d084 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -146,6 +146,8 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
 extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 						   Oid result_collation);
 
+extern Expr *evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+								Oid result_collation);
 extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
 
 extern List *expand_function_arguments(List *args, bool include_out_arguments,

From 3e350ca387117f05388e340e048c14a95ce724e1 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 10 Oct 2025 19:06:14 +0800
Subject: [PATCH v7 20/20] CAST(expr AS newtype DEFAULT ON ERROR)

demo:

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

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 .../pg_stat_statements/expected/select.out    |  23 +-
 contrib/pg_stat_statements/sql/select.sql     |   5 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/syntax.sgml                      |  16 +
 src/backend/catalog/pg_cast.c                 |   1 +
 src/backend/executor/execExpr.c               |  76 +-
 src/backend/executor/execExprInterp.c         |  37 +
 src/backend/jit/llvm/llvmjit_expr.c           |  48 ++
 src/backend/nodes/nodeFuncs.c                 |  67 ++
 src/backend/optimizer/util/clauses.c          |  24 +
 src/backend/parser/gram.y                     |  24 +-
 src/backend/parser/parse_agg.c                |   9 +
 src/backend/parser/parse_expr.c               | 383 +++++++++-
 src/backend/parser/parse_func.c               |   3 +
 src/backend/parser/parse_target.c             |  14 +
 src/backend/utils/adt/arrayfuncs.c            |  13 +
 src/backend/utils/adt/ruleutils.c             |  16 +
 src/include/catalog/pg_cast.dat               | 330 ++++-----
 src/include/catalog/pg_cast.h                 |   4 +
 src/include/executor/execExpr.h               |   8 +
 src/include/nodes/execnodes.h                 |  30 +
 src/include/nodes/parsenodes.h                |   6 +
 src/include/nodes/primnodes.h                 |  33 +
 src/include/parser/parse_node.h               |   2 +
 src/test/regress/expected/cast.out            | 681 ++++++++++++++++++
 src/test/regress/expected/create_cast.out     |   5 +
 src/test/regress/expected/opr_sanity.out      |  24 +-
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/cast.sql                 | 301 ++++++++
 src/test/regress/sql/create_cast.sql          |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 31 files changed, 2011 insertions(+), 190 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
 -----
 (0 rows)
 
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4 
+------
+    2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4 
+------
+   11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric 
+---------
+      12
+(1 row)
+
 -- DISTINCT and ORDER BY patterns
 -- Try some query permutations which once produced identical query IDs
 SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      2 |    2 | SELECT $1 AS "int" ORDER BY 1
      1 |    2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
      1 |    1 | SELECT $1 || $2
+     2 |    2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+     1 |    1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
      2 |    2 | SELECT DISTINCT $1 AS "int"
      0 |    0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
      1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
        |      | )                                                                           +
        |      |   SELECT f FROM t ORDER BY f
      1 |    1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
 
 SELECT pg_stat_statements_reset() IS NOT NULL AS t;
  t 
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
 SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
 SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
 
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
 -- DISTINCT and ORDER BY patterns
 -- Try some query permutations which once produced identical query IDs
 SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9b3aae8603b..957041add7b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>casterrorsafe</structfield> <type>bool</type>
+      </para>
+      <para>
+       This indicates whether the <structfield>castfunc</structfield> function is error safe.
+       If the <structfield>castfunc</structfield> function is error safe, it can be used in
+         For further details see <xref linkend="sql-syntax-type-casts"/>.
+      </para></entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 237d7306fe8..9acb7d69645 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,22 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
     The <literal>CAST</literal> syntax conforms to SQL; the syntax with
     <literal>::</literal> is historical <productname>PostgreSQL</productname>
     usage.
+    It can also be written as:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
+   </para>
+
+   <para>
+Type cast can also be error safe.
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>defexpression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+    For example, the following query will evaluate the default expression and return 42.
+    TODO: more explanation
+<programlisting>
+SELECT CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR);
+</programlisting>
    </para>
 
    <para>

index b302be36f73..88a647e44fa 100644
@@ -1742,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,

 				ExecInitExprRec(acoerce->elemexpr, elemstate,
 								&elemstate->resvalue, &elemstate->resnull);
@@ -2218,6 +2222,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
@@ -2784,7 +2796,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4782,6 +4794,68 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+ * expressions.
+	 * If coercion to the target type fails, fallback to the DEFAULT expression
+		ExprEvalStep *as;
+		int				jumps_to_end;
+		state->escontext = &stcstate->escontext;
+		jumps_to_end = state->steps_len;
+		as = &state->steps[jumps_to_end];
index 67f4e00eac4..49da2905810 100644
@@ -1926,6 +1927,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+			/* too complex for an inline implementation */
@@ -3644,6 +3651,12 @@ ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 
 /*
@@ -5183,6 +5196,30 @@ GetJsonBehaviorValueString(JsonBehavior *behavior)
 	return pstrdup(behavior_names[behavior->btype]);
 }
 
+int
index 712b35df7e5..ebea7c4445f 100644
@@ -2256,6 +2256,54 @@ llvm_compile_expr(ExprState *state)
 
index ede838cd40c..533e21120a6 100644
@@ -1550,6 +1561,15 @@ exprLocation(const Node *expr)
@@ -2321,6 +2341,18 @@ expression_tree_walker_impl(Node *node,
@@ -3330,6 +3362,19 @@ expression_tree_mutator_impl(Node *node,
@@ -4464,6 +4509,28 @@ raw_expression_tree_walker_impl(Node *node,
+				if (WALK(sc->def_expr))
index 3f9ff455ba6..14e0df3bee2 100644
@@ -2945,6 +2945,30 @@ eval_const_expressions_mutator(Node *node,
 
+				 * We must not reduce any recognizably constant subexpressions
+				 * in cast_expr here, since we don’t want it to fail
+				 * prematurely.
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 21caf2d43bf..c62e9ac8edf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -649,6 +649,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partboundspec> PartitionBoundSpec
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
+%type <node>	cast_on_error_clause_opt
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -15982,8 +15983,20 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_CURRENT_SCHEMA, -1, @1);
 				}
-			| CAST '(' a_expr AS Typename ')'
-				{ $$ = makeTypeCast($3, $5, @1); }
+			| CAST '(' a_expr AS Typename cast_on_error_clause_opt ')'
+				{
+					TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+					if ($6 == NULL)
+						$$ = (Node *) cast;
+					else
+					{
+						SafeTypeCast *safecast = makeNode(SafeTypeCast);
+						safecast->cast = (Node *) cast;
+						safecast->def_expr = $6;
+
+						$$ = (Node *) safecast;
+					}
+				}
 			| EXTRACT '(' extract_list ')'
 				{
 					$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -16370,6 +16383,13 @@ func_expr_common_subexpr:
 			;

+cast_on_error_clause_opt:
+			DEFAULT a_expr ON CONVERSION_P ERROR_P 			{ $$ = $2; }
+			| ERROR_P ON CONVERSION_P ERROR_P 				{ $$ = NULL; }
+			| NULL_P ON CONVERSION_P ERROR_P 				{ $$ = makeNullAConst(-1); }
+			| /* EMPTY */ 								 	{ $$ = NULL; }
+		;
+
 /*
  * SQL/XML support
  */
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3254c83cc6c..6573e2a93ea 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in check constraints");
 
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+			else
+				err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 
@@ -956,6 +962,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("window functions are not allowed in check constraints");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("window functions are not allowed in CAST DEFAULT expressions");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 12119f147fc..ac60b74dceb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,8 @@
 
 #include "access/htup_details.h"
 
@@ -60,7 +63,8 @@ static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
@@ -76,6 +80,7 @@ static Node *transformWholeRowRef(ParseState *pstate,
@@ -107,6 +112,8 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
+static bool CoerceUnknownConstSafe(ParseState *pstate, Node *node,

 /*
@@ -164,13 +171,17 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
+			result = transformTypeSafeCast(pstate, (SafeTypeCast *) expr);
+			break;
+
 		case T_CollateClause:
 			result = transformCollateClause(pstate, (CollateClause *) expr);
 			break;
@@ -576,6 +587,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_COPY_WHERE:
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_CAST_DEFAULT:
 			/* okay */
 			break;
 
@@ -1824,6 +1836,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("cannot use subquery in check constraint");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
@@ -2005,16 +2018,73 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 	return result;
 }
 
+/*
+ * Return true if successfuly coerced a Unknown Const to targetType
+*/
+static bool
+CoerceUnknownConstSafe(ParseState *pstate, Node *node,
+					   Oid targetType, int32 targetTypeMod)
+{
+	Oid			baseTypeId;
+	int32		baseTypeMod;
+	int32		inputTypeMod;
+	Type		baseType;
+	char		*string;
+	Datum		datum;
+	bool		converted;
+	Const	   *con;
+
+	Assert(IsA(node, Const));
+	Assert(exprType(node) == UNKNOWNOID);
+
+	con = (Const *) node;
+	baseTypeMod = targetTypeMod;
+	baseTypeId = getBaseTypeAndTypmod(targetType, &baseTypeMod);
+
+	if (baseTypeId == INTERVALOID)
+		inputTypeMod = baseTypeMod;
+	else
+		inputTypeMod = -1;
+
+	baseType = typeidType(baseTypeId);
+
+	/*
+	 * We assume here that UNKNOWN's internal representation is the same as
+	 * CSTRING.
+	*/
+	if (!con->constisnull)
+		string = DatumGetCString(con->constvalue);
+	else
+		string = NULL;
+
+	converted = stringTypeDatumSafe(baseType,
+									string,
+									inputTypeMod,
+									&datum);
+
+	ReleaseSysCache(baseType);
+
+	return converted;
+}
+
+
 /*
  * transformArrayExpr
  *
  * If the caller specifies the target type, the resulting array will
  * be of exactly that type.  Otherwise we try to infer a common type
  * for the elements using select_common_type().
+ *
+ * Most of the time, the caller will pass can_coerce as NULL.
+ * can_coerce is not NULL only when performing parse analysis for expressions
+ * like CAST(DEFAULT ... ON CONVERSION ERROR).
+ * When can_coerce is not NULL, it should be assumed to default to true.  If
+ * coerce array elements to the target type fails, can_coerce will be set to
+ * false.
  */
 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 *can_coerce)
 {
 	ArrayExpr  *newa = makeNode(ArrayExpr);
 	List	   *newelems = NIL;
@@ -2022,6 +2092,7 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	ListCell   *element;
 	Oid			coerce_type;
 	bool		coerce_hard;
+	bool		expr_is_const = false;
 
 	/*
 	 * Transform the element expressions
@@ -2045,9 +2116,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  can_coerce);
 			/* we certainly have an array here */
-			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			Assert(can_coerce || array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
 		}
 		else
@@ -2088,6 +2160,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	}
 	else
 	{
+		/* We obviously know the target type when performing type-safe cast */
+		Assert(can_coerce == NULL);
+
 		/* Can't handle an empty array without a target type */
 		if (newelems == NIL)
 			ereport(ERROR,
@@ -2140,7 +2215,36 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		Node	   *e = (Node *) lfirst(element);
 		Node	   *newe;
 
-		if (coerce_hard)
+		/*
+		 * For error-safe type casting, we need to safely check whether the
+		 * UNKNOWN constant can be coerced to the target type. If it cannot, set
+		 * can_coerce to false and coerce source element to TEXT data type.
+		 * see coerce_type function also.
+		*/
+		if (can_coerce != NULL && IsA(e, Const))
+		{
+			if (exprType(e) == UNKNOWNOID)
+			{
+				if (!CoerceUnknownConstSafe(pstate, e, coerce_type, typmod))
+				{
+					*can_coerce = false;
+
+					/* see resolveTargetListUnknowns also */
+					e =  coerce_to_target_type(pstate, e,
+											   UNKNOWNOID,
+											   TEXTOID,
+											   -1,
+											   COERCION_IMPLICIT,
+											   COERCE_IMPLICIT_CAST,
+											   -1);
+				}
+			}
+			expr_is_const = true;
+		}
+
+		if (can_coerce != NULL && (!*can_coerce))
+			newe = e;
+		else if (coerce_hard)
 		{
 			newe = coerce_to_target_type(pstate, e,
 										 exprType(e),
@@ -2149,13 +2253,51 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 										 COERCION_EXPLICIT,
 										 COERCE_EXPLICIT_CAST,
 										 -1);
-			if (newe == NULL)
+			if (newe == NULL && can_coerce == NULL)
 				ereport(ERROR,
 						(errcode(ERRCODE_CANNOT_COERCE),
 						 errmsg("cannot cast type %s to %s",
 								format_type_be(exprType(e)),
 								format_type_be(coerce_type)),
 						 parser_errposition(pstate, exprLocation(e))));
+
+			if (can_coerce != NULL)
+			{
+				if (newe == NULL)
+				{
+					newe = e;
+					*can_coerce = false;
+				}
+				else if (expr_is_const && !IsA(newe, Const))
+				{
+					/*
+					 * Use evaluate_expr_safe to pre-evaluate simple constant
+					 * cast expressions early, in a way that tolter errors .
+					 *
+					 * Rationale:
+					 * 1. When deparsing safe cast expressions (or in other
+					 *    cases), eval_const_expressions might be invoked, but
+					 *    it cannot handle errors gracefully.
+					 * 2. If the cast expression involves only simple constants,
+					 *    we can safely evaluate it ahead of time. If the
+					 *    evaluation fails, it indicates that such a cast is not
+					 *    possible, and we can then fall back to the CAST
+					 *    DEFAULT expression.
+					*/
+					Expr *newe_copy = (Expr *) copyObject(newe);
+
+					newe = (Node *) evaluate_expr_safe(newe_copy,
+													   coerce_type,
+													   typmod,
+													   exprCollation(newe));
+					if (newe == NULL)
+					{
+						newe = e;
+						*can_coerce = false;
+					}
+				}
+				expr_is_const = false;
+			}
 		}
 		else
 			newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2885,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 									  (A_ArrayExpr *) arg,
 									  targetBaseType,
 									  elementType,
-									  targetBaseTypmod);
+									  targetBaseTypmod,
+									  NULL);
 		}
 		else
 			expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2923,228 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	return result;
 }
 
+/*
+ * Handle an explicit CAST(... DEFAULT ... ON CONVERSION ERROR) construct.
+ *
+ * Transform SafeTypeCast node, look up the type name, and apply any necessary
+ * coercion function(s).
+ */
+static Node *
+transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc)
+{
+	SafeTypeCastExpr   *result;
+	TypeCast   *tcast = (TypeCast *) tc->cast;
+	Node	   *def_expr = NULL;
+	Node	   *cast_expr = NULL;
+	Node	   *source_expr = NULL;
+	Oid			inputType = InvalidOid;
+	Oid			targetType;
+	Oid			targetBaseType;
+	Oid			typeColl;
+	int32		targetTypmod;
+	int32		targetBaseTypmod;
+	bool		can_coerce = true;
+	bool		expr_is_const = false;
+	int			location;
+
+	/* Look up the type name first */
+	typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+	targetBaseTypmod = targetTypmod;
+
+	typeColl = get_typcollation(targetType);
+	targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+	/* now looking at cast default expression */
+	def_expr = transformExpr(pstate, tc->def_expr, EXPR_KIND_CAST_DEFAULT);
+
+	def_expr = coerce_to_target_type(pstate, def_expr, exprType(def_expr),
+									 targetType, targetTypmod,
+									 COERCION_EXPLICIT,
+									 COERCE_EXPLICIT_CAST,
+									 exprLocation(def_expr));
+
+	assign_expr_collations(pstate, def_expr);
+
+	if (def_expr == 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->def_expr), def_expr));
+
+	/*
+	 * The collation of DEFAULT expression must match the collation of the
+	 * target type.
+	*/
+	if (OidIsValid(typeColl))
+	{
+		Oid			defColl;
+
+		defColl = exprCollation(def_expr);
+
+		if (OidIsValid(defColl) && typeColl != defColl)
+			ereport(ERROR,
+					errcode(ERRCODE_DATATYPE_MISMATCH),
+					errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+					errdetail("\"%s\" versus \"%s\"",
+							  get_collation_name(typeColl),
+							  get_collation_name(defColl)),
+					parser_errposition(pstate, exprLocation(def_expr)));
+	}
+
+	/*
+	 * If the type cast 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.  Otherwise, just transform the argument normally.
+	 */
+	if (IsA(tcast->arg, A_ArrayExpr))
+	{
+		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.
+		 */
+		elementType = get_element_type(targetBaseType);
+
+		if (OidIsValid(elementType))
+		{
+			source_expr = transformArrayExpr(pstate,
+											 (A_ArrayExpr *) tcast->arg,
+											 targetBaseType,
+											 elementType,
+											 targetBaseTypmod,
+											 &can_coerce);
+		}
+		else
+			source_expr = transformExprRecurse(pstate, tcast->arg);
+	}
+	else
+		source_expr = transformExprRecurse(pstate, tcast->arg);
+
+	inputType = exprType(source_expr);
+	if (inputType == InvalidOid)
+		return (Node *) NULL;			/* return NULL if NULL input */
+
+	if (can_coerce && IsA(source_expr, Const))
+	{
+		if (exprType(source_expr) == UNKNOWNOID)
+			can_coerce = CoerceUnknownConstSafe(pstate,
+												source_expr,
+												targetType,
+												targetTypmod);
+		expr_is_const = true;
+	}
+
+	/*
+	 * Location of the coercion is preferentially the location of CAST symbol,
+	 * but if there is none then use the location of the type name (this can
+	 * happen in TypeName 'string' syntax, for instance).
+	 */
+	location = tcast->location;
+	if (location < 0)
+		location = tcast->typeName->location;
+
+	if (can_coerce)
+	{
+		Node	   *origexpr;
+
+		cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+										  targetType, targetTypmod,
+										  COERCION_EXPLICIT,
+										  COERCE_EXPLICIT_CAST,
+										  location);
+		origexpr = cast_expr;
+
+		while (cast_expr && IsA(cast_expr, CollateExpr))
+			cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+
+		if (cast_expr != NULL && IsA(cast_expr, FuncExpr))
+		{
+			HeapTuple	tuple;
+			ListCell   *lc;
+			Node	   *sexpr;
+			FuncExpr   *fexpr = (FuncExpr *) cast_expr;
+
+			lc = list_head(fexpr->args);
+			sexpr = (Node *) lfirst(lc);
+
+			/* Look in pg_cast */
+			tuple = SearchSysCache2(CASTSOURCETARGET,
+									ObjectIdGetDatum(exprType(sexpr)),
+									ObjectIdGetDatum(targetType));
+
+			if (HeapTupleIsValid(tuple))
+			{
+				Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+				if (!castForm->casterrorsafe)
+					ereport(ERROR,
+							errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+								   format_type_be(inputType),
+								   format_type_be(targetType),
+								   "DEFAULT",
+								   "CAST ... ON CONVERSION ERROR"),
+							errhint("Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast"),
+							parser_errposition(pstate, exprLocation(source_expr)));
+			}
+			else
+				elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+							format_type_be(inputType),
+							format_type_be(targetType));
+			ReleaseSysCache(tuple);
+
+			if (expr_is_const)
+			{
+				/*
+				 * Use evaluate_expr_safe to pre-evaluate simple constant cast
+				 * expressions early, in a way that tolter errors .
+				 *
+				 * Rationale:
+				 * 1. When deparsing safe cast expressions (or in other cases),
+				 *    eval_const_expressions might be invoked, but it cannot
+				 *    handle errors gracefully.
+				 * 2. If the cast expression involves only simple constants, we
+				 *    can safely evaluate it ahead of time. If the evaluation
+				 *    fails, it indicates that such a cast is not possible, and
+				 *    we can then fall back to the CAST DEFAULT expression.
+				 */
+				FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+				assign_expr_collations(pstate, (Node *) newexpr);
+
+				cast_expr = (Node *) evaluate_expr_safe((Expr *) newexpr,
+														targetType,
+														targetTypmod,
+														typeColl);
+			}
+		}
+
+		/*
+		 * set cast_expr back as is, cast_expr can be NULL after
+		 * evaluate_expr_safe
+		 */
+		if (cast_expr != NULL)
+			cast_expr = origexpr;
+	}
+
+	Assert(can_coerce || cast_expr == NULL);
+
+	result = makeNode(SafeTypeCastExpr);
+	result->source_expr = source_expr;
+	result->cast_expr = cast_expr;
+	result->default_expr = def_expr;
+	result->resulttype = targetType;
+	result->resulttypmod = targetTypmod;
+	result->resultcollid = typeColl;
+
+	return (Node *) result;
+}
+
 /*
  * Handle an explicit COLLATE clause.
  *
@@ -3193,6 +3558,8 @@ ParseExprKindName(ParseExprKind exprKind)
 		case EXPR_KIND_CHECK_CONSTRAINT:
 		case EXPR_KIND_DOMAIN_CHECK:
 			return "CHECK";
+		case EXPR_KIND_CAST_DEFAULT:
+			return "CAST DEFAULT";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("set-returning functions are not allowed in check constraints");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..d67e5f8a5b7 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,20 @@ FigureColnameInternal(Node *node, char **name)
 				}
 			}
 			break;
+		case T_SafeTypeCast:
+			strength = FigureColnameInternal(((SafeTypeCast *) node)->cast,
+											 name);
+			if (strength <= 1)
+			{
+				TypeCast *node_cast;
+				node_cast = (TypeCast *)((SafeTypeCast *) node)->cast;
+				if (node_cast->typeName != NULL)
+				{
+					*name = strVal(llast(node_cast->typeName->names));
+					return 1;
+				}
+			}
+			break;
 		case T_CollateClause:
 			return FigureColnameInternal(((CollateClause *) node)->arg, name);
 		case T_GroupingFunc:
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index a8951f55b93..31cb36a56e6 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,19 @@ array_map(Datum arrayd,
 		/* Apply the given expression to source element */
 		values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
 
+		if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+		{
+			pfree(values);
+			pfree(nulls);
+			return (Datum) 0;
+		}
+
+		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 050eef97a4c..4278e44b4a9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10558,6 +10558,22 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+								 format_type_with_typemod(stcexpr->resulttype,
+														  stcexpr->resulttypmod));
index fbfd669587f..1ef59a982af 100644

 
 
@@ -302,57 +302,57 @@

 
@@ -378,73 +378,73 @@
 
 # Datetime category
 
 # Geometric category
 { castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'polygon', casttarget => 'path', castfunc => 'path',
-  castcontext => 'a', castmethod => 'f' },
+  castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'polygon', casttarget => 'circle',
-  castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+  castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'circle', casttarget => 'polygon',
-  castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+  castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 /* ----------------
index 75366203706..34a884e3196 100644
 
+		/* for EEOP_SAFETYPE_CAST */
+		struct
+		{
+			struct SafeTypeCastState *stcstate;	/* original expression node */
+		}			stcexpr;
+
 		/* for EEOP_JSONEXPR_PATH */
 		struct
 		{
@@ -892,6 +899,7 @@ extern int	ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op,
 								 ExprContext *econtext);
 extern void ExecEvalJsonCoercion(ExprState *state, ExprEvalStep *op,
 								 ExprContext *econtext);
+extern int ExecEvalSafeTypeCast(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalMergeSupportFunc(ExprState *state, ExprEvalStep *op,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a36653c37f9..015cd9f47e7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1053,6 +1053,36 @@ typedef struct DomainConstraintState
 	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;
 
index dc09d1a3f03..83549151bfb 100644
@@ -400,6 +400,12 @@ typedef struct TypeCast
 
+typedef struct SafeTypeCast
+{
+	NodeTag		type;
+	Node		*cast;
+	Node	   	*def_expr;		/* default expr */
+} SafeTypeCast;
 /*
  * CollateClause - a COLLATE expression
  */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..70b72ed2f40 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,39 @@ typedef enum CoercionForm
 	COERCE_SQL_SYNTAX,			/* display with SQL-mandated special syntax */
 } CoercionForm;
 
+/*
+ * SafeTypeCastExpr -
+ *		Transformed representation of
+ * CAST(expr AS typename DEFAULT expr2 ON ERROR)
+ * CAST(expr AS typename NULL ON ERROR)
+ */
+typedef struct SafeTypeCastExpr
+{
+	Expr		xpr;
+
+	/* transformed expression being casted */
+	Node	   *source_expr;
+
+	/*
+	 * The transformed cast expression; It will NULL if can not cast source type
+	 * to target type
+	 */
+	Node	   *cast_expr 	pg_node_attr(query_jumble_ignore);
+
+	/* Fall back to the default expression if cast evaluation fails */
+	Node	   *default_expr;
+
+	/* cast result data type */
+	Oid			resulttype;
+
+	/* cast result data type typmod (usually -1) */
+	int32		resulttypmod;
+
+	/* cast result data type collation */
+	Oid			resultcollid;
+
+} SafeTypeCastExpr;
+
 /*
  * FuncExpr - expression node for a function call
  *
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_VALUES_SINGLE,	/* single-row VALUES (in INSERT only) */
 	EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
 	EXPR_KIND_DOMAIN_CHECK,		/* CHECK constraint for a domain */
+	EXPR_KIND_CAST_DEFAULT,		/* default expression in
+								   CAST DEFAULT ON CONVERSION ERROR */
 	EXPR_KIND_COLUMN_DEFAULT,	/* default value for a table column */
 	EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
 	EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..6614ba3370c
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,681 @@
+SET extra_float_digits = 0;
+-- 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));
+                     ^
+VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR));
+ column1 
+---------
+        
+(1 row)
+
+VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR));
+ column1 
+---------
+      42
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char 
+------
+ 
+(1 row)
+
+CREATE OR REPLACE 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 array coerce
+SELECT CAST(array[11.12] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ {11}
+(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[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS integer[] 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)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array 
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+       array       
+-------------------
+ {{1,2},{three,a}}
+(1 row)
+
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
+$$
+BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY, c text default '1');
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error
+ERROR:  set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+                                       ^
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR:  aggregate functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR);
+                                       ^
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error
+ERROR:  window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+                                       ^
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+ERROR:  invalid input syntax for type integer: "b"
+LINE 1: SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR);
+                                       ^
+SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t;
+     t     
+-----------
+ {21,22,1}
+ {21,22,2}
+ {21,22,3}
+ {21,22,4}
+(4 rows)
+
+SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t;
+     a     
+-----------
+ {12}
+ {21,22,2}
+ {21,22,3}
+ {13}
+(4 rows)
+
+-- test with domain
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+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); --ok
+ comp2 
+-------
+ (123)
+(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(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+ d_int42 
+---------
+      42
+(1 row)
+
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  domain d_int42 does not allow null values
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+ d_int42 
+---------
+      42
+(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('(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)'...
+                                                             ^
+-----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);
+ERROR:  cannot convert circle with radius zero to polygon
+CONTEXT:  SQL function "polygon" statement 1
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+                    ^
+HINT:  Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+/*
+the following are safe cast test with type
+{
+ bytea, bit character, text, character-varying,
+ inet, macaddr8
+ int2, integer, bigint, numeric, float4, float8,
+ date, interval, timestamptz, timestamp
+ jsonb
+}
+*/
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8 
+------
+     
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4 
+------
+     
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2 
+------
+     
+(1 row)
+
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+ int4 
+------
+     
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+ int8 
+------
+     
+(1 row)
+
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass 
+----------
+ 
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+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,
+       CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+       text        | to_regclass | expect_true | to_char |      to_name      | to_xml 
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value |             | t           | <       | <value>one</value | 
+(1 row)
+
+-----safe cast from other type to inet/cidr
+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 range type to other type
+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)
+
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[],
+                        col5 int2 default 32767,
+                        col6 int4 default 32768,
+                        col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO safecast VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO safecast 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 safecast;
+ 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 safecast;
+ 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 safecast;
+    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 safecast;
+       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 safecast;
+          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 safecast;
+  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 safecast;
+  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)
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 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 safecast1;
+   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 safecast1;
+ 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 safecast1;
+ 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 safecast1;
+ 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) 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 safecast1;
+    time     |  to_times   |   to_timetz    |          to_interval          |       to_interval_scale       
+-------------+-------------+----------------+-------------------------------+-------------------------------
+ 15:36:39    | 15:36:39    | 15:36:39-07    | @ 15 hours 36 mins 39 secs    | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | @ 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 safecast1;
+     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)
+
+CREATE TEMP TABLE test_safecast1(col0 jsonb);
+INSERT INTO test_safecast1(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+       CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+       CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+       CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast1;
+ jsonb  | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint 
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" |            |            |         |           |           |         |            
+(1 row)
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+       CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as safecast,
+       CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview
+CREATE OR REPLACE VIEW public.safecastview AS
+ SELECT CAST('1234' AS character(3) DEFAULT '-1111'::integer::character(3) ON CONVERSION ERROR) AS bpchar,
+    CAST(1 AS date DEFAULT now()::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS safecast,
+    CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+    CAST(ARRAY[ARRAY['1'::text, '2'::text], ARRAY['three'::text, 'a'::text]] AS text[] DEFAULT '{21,22}'::text[] ON CONVERSION ERROR) AS cast2
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\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 '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+    CAST(ARRAY[ARRAY[1, 2], ARRAY['three'::text, 'a'::text]] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 |  cast2  
+-------+---------
+ {1,2} | {21,22}
+(1 row)
+
+CREATE INDEX cast_error_idx  ON tcast((cast(c as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
+ERROR:  functions in index expression must be marked IMMUTABLE
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..38eab453344 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
  bar1234
 (1 row)
 
+ERROR:  cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
index a357e1d0c0e..81ea244859f 100644

 
 SELECT c.*

 SELECT c.*
 
index f9450cdc477..573a6699a63 100644

 # ----------
 # Run these alone so they don't run out of parallel workers
diff --git a/src/test/regress/sql/cast.sql b/src/test/regress/sql/cast.sql
new file mode 100644
index 00000000000..eb68ec21859
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,301 @@
+SET extra_float_digits = 0;
+
+-- CAST DEFAULT ON CONVERSION ERROR
+VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); --error
+VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR));
+VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR));
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE OR REPLACE 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 array coerce
+SELECT CAST(array[11.12] AS int[] 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[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON CONVERSION ERROR);
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
+$$
+BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY, c text default '1');
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+
+SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t;
+SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t;
+
+-- test with domain
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok
+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('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' 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 from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+
+/*
+the following are safe cast test with type
+{
+ bytea, bit character, text, character-varying,
+ inet, macaddr8
+ int2, integer, bigint, numeric, float4, float8,
+ date, interval, timestamptz, timestamp
+ jsonb
+}
+*/
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+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,
+       CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+-----safe cast from other type to inet/cidr
+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 range type to other type
+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);
+
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[],
+                        col5 int2 default 32767,
+                        col6 int4 default 32768,
+                        col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO safecast VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO safecast VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO safecast 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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+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 safecast;
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 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 safecast1;
+
+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 safecast1;
+
+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 safecast1;
+
+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 safecast1;
+
+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) 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 safecast1;
+
+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 safecast1;
+
+CREATE TEMP TABLE test_safecast1(col0 jsonb);
+INSERT INTO test_safecast1(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+       CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+       CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+       CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast1;
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+       CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as safecast,
+       CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+CREATE INDEX cast_error_idx  ON tcast((cast(c as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;

 -- check dependencies generated for that
 SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5290b91e83e..7996854b3a5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2660,6 +2660,9 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

From 2f2168688b59e2d3d0568636f32190afe67e969e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v7 16/20] error safe for casting timestamp to other types per
 pg_cast

 

 
index 116e3ef28fc..7a57ac3eaf6 100644
 
 	result = timestamp;

 	PG_RETURN_TIMESTAMP(result);
 }
@@ -6430,7 +6431,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 {
 	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);

 /*
-- 
2.34.1

From a402051d73e3b3ed86f2972639bd3910ee7b7cff Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:49:46 +0800
Subject: [PATCH v7 04/20] error safe for casting text to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/catalog/namespace.c | 167 ++++++++++++++++++++++++++++++++
 src/backend/utils/adt/regproc.c |  33 ++++++-
 src/backend/utils/adt/varlena.c |  42 ++++++++
 src/include/catalog/namespace.h |   6 ++
 src/include/utils/varlena.h     |   1 +
 5 files changed, 246 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..a04d179c7fb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -640,6 +640,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	return relId;
 }
 
+/* safe version of RangeVarGetRelidExtended */
+Oid
@@ -3650,6 +3781,42 @@ makeRangeVarFromNameList(const List *names)
 	return rel;
 }
 
+/*
index e5c2246f2c9..059d0154335 100644
@@ -1902,10 +1902,37 @@ text_regclass(PG_FUNCTION_ARGS)
 	Oid			result;
 	RangeVar   *rv;

-	/* We might not even have permissions on this relation; don't lock it. */
-	result = RangeVarGetRelid(rv, NoLock, false);
+		/*
+		 * We might not even have permissions on this relation; don't lock it.
+		 */
+		result = RangeVarGetRelid(rv, NoLock, false);
+	}
+	else
+	{
+		List   		*rvnames;
+
+		rvnames = textToQualifiedNameListSafe(relname, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			PG_RETURN_NULL();
+
+		rv = makeRangeVarFromNameListSafe(rvnames, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			PG_RETURN_NULL();
+
+		result = RangeVarGetRelidExtendedSafe(rv,
+											  NoLock,
+											  0,
+											  NULL,
+											  NULL,
+											  fcinfo->context);
+
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			PG_RETURN_NULL();
+	}
 
 	PG_RETURN_OID(result);
 }
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 2c398cd9e5c..1117939430a 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2717,6 +2717,48 @@ textToQualifiedNameList(text *textval)
 	return result;
 }
 
+/* error safe version of textToQualifiedNameList */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
+{
+	char	   *rawname;
+	List	   *result = NIL;
+	List	   *namelist;
+	ListCell   *l;
+
+	/* Convert to C string (handles possible detoasting). */
+	/* Note we rely on being able to modify rawname below. */
+	rawname = text_to_cstring(textval);
+
+	if (!SplitIdentifierString(rawname, '.', &namelist))
+	{
+		errsave(escontext,
+				errcode(ERRCODE_INVALID_NAME),
+				errmsg("invalid name syntax"));
+		return NIL;
+	}
+
+	if (namelist == NIL)
+	{
+		errsave(escontext,
+				errcode(ERRCODE_INVALID_NAME),
+				errmsg("invalid name syntax"));
+		return NIL;
+	}
+
+	foreach(l, namelist)
+	{
+		char	   *curname = (char *) lfirst(l);
+
+		result = lappend(result, makeString(pstrdup(curname)));
+	}
+
+	pfree(rawname);
+	list_free(namelist);
+
+	return result;
+}
+
 /*
  * SplitIdentifierString --- parse a string containing identifiers
  *
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid	RangeVarGetRelidExtended(const RangeVar *relation,
 									 LOCKMODE lockmode, uint32 flags,
 									 RangeVarGetRelidCallback callback,
 									 void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+										LOCKMODE lockmode, uint32 flags,
+										RangeVarGetRelidCallback callback,
+										void *callback_arg,
+										Node *escontext);
 extern Oid	RangeVarGetCreationNamespace(const RangeVar *newRelation);
 extern Oid	RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
 												 LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid	LookupCreationNamespace(const char *nspname);
 extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
 extern Oid	QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
 extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
 extern char *NameListToString(const List *names);
 extern char *NameListToQuotedString(const List *names);

From 8f462532bbcc356181f212eeb9f8f1a070b88efe Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:58:40 +0800
Subject: [PATCH v7 17/20] error safe for casting jsonb to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/jsonb.c | 91 +++++++++++++++++++++++++++++------
 1 file changed, 75 insertions(+), 16 deletions(-)

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index da94d424d61..1ff00f72f78 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2005,7 +2005,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
  * Emit correct, translatable cast error message
  */
 static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
 {
 	static const struct
 	{
@@ -2026,9 +2026,12 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)

 	/* should be unreachable */
 	elog(ERROR, "unknown jsonb type: %d", (int) type);
@@ -2041,7 +2044,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	JsonbValue	v;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "boolean");
+	{
+		cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	if (v.type == jbvNull)
 	{
@@ -2050,7 +2057,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvBool)
-		cannotCastJsonbValue(v.type, "boolean");
+	{
+		cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	PG_FREE_IF_COPY(in, 0);
 
@@ -2065,7 +2076,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	Numeric		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "numeric");
+	{
+		cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	if (v.type == jbvNull)
 	{
@@ -2074,7 +2089,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "numeric");
+	{
+		cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	/*
 	 * v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2095,7 +2114,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "smallint");
+	{
+		cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	if (v.type == jbvNull)
 	{
@@ -2104,7 +2127,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "smallint");
+	{
+		cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	retValue = DirectFunctionCall1(numeric_int2,
 								   NumericGetDatum(v.val.numeric));
@@ -2122,7 +2149,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "integer");
+	{
+		cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	if (v.type == jbvNull)
 	{
@@ -2131,7 +2162,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "integer");
+	{
+		cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	retValue = DirectFunctionCall1(numeric_int4,
 								   NumericGetDatum(v.val.numeric));
@@ -2149,7 +2184,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "bigint");
+	{
+		cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	if (v.type == jbvNull)
 	{
@@ -2158,7 +2197,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "bigint");
+	{
+		cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	retValue = DirectFunctionCall1(numeric_int8,
 								   NumericGetDatum(v.val.numeric));
@@ -2176,7 +2219,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "real");
+	{
+		cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	if (v.type == jbvNull)
 	{
@@ -2185,7 +2232,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "real");
+	{
+		cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	retValue = DirectFunctionCall1(numeric_float4,
 								   NumericGetDatum(v.val.numeric));
@@ -2203,7 +2254,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	Datum		retValue;
 
 	if (!JsonbExtractScalar(&in->root, &v))
-		cannotCastJsonbValue(v.type, "double precision");
+	{
+		cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}
 
 	if (v.type == jbvNull)
 	{
@@ -2212,7 +2267,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "double precision");
+	{
+		cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+		PG_RETURN_NULL();
+	}

From d5c577a6ce3e69417940dbba049e2abde9bd934e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v7 13/20] error safe for casting date to other types per
 pg_cast

 

 

From 14337c6ba6e656d85bcf95aa4c18c078fbbfb740 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v7 14/20] error safe for casting interval to other types per
 pg_cast

 
 
index 156a4830ffd..7b565cc6d66 100644

From e56515b37e3a5601d7bd315ba8816a7d569c7824 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v7 15/20] error safe for casting timestamptz to other types
 per pg_cast

 

 

index 7b565cc6d66..116e3ef28fc 100644
 
 	result = timestamp;

 	PG_RETURN_TIMESTAMPTZ(result);
 }
@@ -6507,7 +6508,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
 {
 	TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);

 /*
-- 
2.34.1

From 5558c75ac6876a83e739a4a219f4f166f3fa8a06 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:41:55 +0800
Subject: [PATCH v7 12/20] error safe for casting float8 to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/float.c   | 29 +++++++++++++++++++++++++----
 src/backend/utils/adt/int8.c    |  6 +++++-
 src/backend/utils/adt/numeric.c |  3 ++-
 3 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index dd8a2ff378b..6747544b679 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,22 @@ dtof(PG_FUNCTION_ARGS)
 
 	result = (float4) num;
+		errsave(fcinfo->context,
+		errsave(fcinfo->context,
 
 	PG_RETURN_FLOAT4(result);
 }
@@ -1224,10 +1237,14 @@ dtoi4(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num)))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("integer out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_INT32((int32) num);
 }
 
@@ -1249,10 +1266,14 @@ dtoi2(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num)))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_INT16((int16) num);
 }
 
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bcdb020a91c..437dbbccd4a 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1268,10 +1268,14 @@ dtoi8(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num)))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("bigint out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_INT64((int64) num);
 }
 
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 8839b095f60..76cd9800c2a 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4570,7 +4570,8 @@ float8_numeric(PG_FUNCTION_ARGS)
 	init_var(&result);
 
 	/* Assume we need not worry about leading/trailing spaces */
-	(void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+	if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+		PG_RETURN_NULL();
 
 	res = make_result(&result);
 
-- 
2.34.1

From ab4382ec5ad71348279f6fd1be3d04d66bb3399e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:33:57 +0800
Subject: [PATCH v7 11/20] error safe for casting float4 to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/float.c   | 12 ++++++++++--
 src/backend/utils/adt/int8.c    |  6 +++++-
 src/backend/utils/adt/numeric.c |  3 ++-
 3 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..dd8a2ff378b 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,10 +1298,14 @@ ftoi4(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num)))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("integer out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_INT32((int32) num);
 }
 
@@ -1323,10 +1327,14 @@ ftoi2(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num)))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_INT16((int16) num);
 }
 
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 3b2d100be92..bcdb020a91c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1303,10 +1303,14 @@ ftoi8(PG_FUNCTION_ARGS)
 
 	/* Range check */
 	if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num)))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("bigint out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_INT64((int64) num);
 }
 
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index ce5f71109e7..8839b095f60 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4668,7 +4668,8 @@ float4_numeric(PG_FUNCTION_ARGS)
 	init_var(&result);
 
 	/* Assume we need not worry about leading/trailing spaces */
-	(void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+	if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+		PG_RETURN_NULL();
 
 	res = make_result(&result);
 
-- 
2.34.1

From f9af62970b23dfee506ee8fd46fdbee80170e6fd Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 21:57:31 +0800
Subject: [PATCH v7 10/20] error safe for casting numeric to other types per
 pg_cast

discussion: https://postgr.es/m/CACJufxHCMzrHOW=[email-masked]
---
 src/backend/utils/adt/numeric.c | 68 +++++++++++++++++++++++++--------
 1 file changed, 53 insertions(+), 15 deletions(-)

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2501007d981..ce5f71109e7 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,8 @@ numeric		(PG_FUNCTION_ARGS)
 	 */
 	if (NUMERIC_IS_SPECIAL(num))
 	{
-		(void) apply_typmod_special(num, typmod, NULL);
+		if (!apply_typmod_special(num, typmod, fcinfo->context))
+			PG_RETURN_NULL();
 		PG_RETURN_NUMERIC(duplicate_numeric(num));
 	}
 
@@ -1295,8 +1296,9 @@ numeric		(PG_FUNCTION_ARGS)
 	init_var(&var);
 
 	set_var_from_num(num, &var);
-	(void) apply_typmod(&var, typmod, NULL);
-	new = make_result(&var);
+	if (!apply_typmod(&var, typmod, fcinfo->context))
+		PG_RETURN_NULL();
+	new = make_result_safe(&var, fcinfo->context);
 
 	free_var(&var);
 
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
 	Numeric		num2 = PG_GETARG_NUMERIC(1);
 	Numeric		res;
 
-	res = numeric_mul_safe(num1, num2, NULL);
+	res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+		PG_RETURN_NULL();
 
 	PG_RETURN_NUMERIC(res);
 }
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
 Datum
 numeric_int4(PG_FUNCTION_ARGS)
 {
+	int32		result;
 	Numeric		num = PG_GETARG_NUMERIC(0);
 
-	PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+	result = numeric_int4_safe(num, fcinfo->context);
+
+	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+		PG_RETURN_NULL();
+
+	PG_RETURN_INT32(result);
 }
 
 /*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
 Datum
 numeric_int8(PG_FUNCTION_ARGS)
 {
+	int64		result;
 	Numeric		num = PG_GETARG_NUMERIC(0);
 
-	PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+	result = numeric_int8_safe(num, fcinfo->context);
+
+	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+		PG_RETURN_NULL();
+
+	PG_RETURN_INT64(result);
 }

@@ -4489,28 +4506,38 @@ numeric_int2(PG_FUNCTION_ARGS)
 	if (NUMERIC_IS_SPECIAL(num))
 	{
 		if (NUMERIC_IS_NAN(num))
-			ereport(ERROR,
+			errsave(fcinfo->context,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert NaN to %s", "smallint")));
 		else
-			ereport(ERROR,
+			errsave(fcinfo->context,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert infinity to %s", "smallint")));
+
+		PG_RETURN_NULL();
 	}
 
 	/* Convert to variable format and thence to int8 */
 	init_var_from_num(num, &x);
 
 	if (!numericvar_to_int64(&x, &val))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	/* Down-convert to int2 */
 	result = (int16) val;
 
@@ -4572,10 +4599,14 @@ numeric_float8(PG_FUNCTION_ARGS)

 	PG_RETURN_DATUM(result);
 }
@@ -4667,7 +4698,14 @@ numeric_float4(PG_FUNCTION_ARGS)
 	tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
 											  NumericGetDatum(num)));

 	pfree(tmp);
 
-- 
2.34.1

From afa97fc4300711baf573799a7f363644fee03b4c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 16:04:07 +0800
Subject: [PATCH v7 08/20] error safe for casting integer to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/char.c | 6 +++++-
 src/backend/utils/adt/int.c  | 5 ++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..7cf09d954e7 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,10 +192,14 @@ i4tochar(PG_FUNCTION_ARGS)
 	int32		arg1 = PG_GETARG_INT32(0);
 
 	if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("\"char\" out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_CHAR((int8) arg1);
 }
 
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..4d3ce23a4af 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,10 +350,13 @@ i4toi2(PG_FUNCTION_ARGS)
 	int32		arg1 = PG_GETARG_INT32(0);
 
 	if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
+		PG_RETURN_NULL();
+	}
 	PG_RETURN_INT16((int16) arg1);
 }
 
-- 
2.34.1

From ecce9850b5793174fb67699b95106c363820888c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 16:38:37 +0800
Subject: [PATCH v7 09/20] error safe for casting bigint to other types per
 pg_cast

already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/int8.c | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bdea490202a..3b2d100be92 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1204,10 +1204,14 @@ int84(PG_FUNCTION_ARGS)
 	int64		arg = PG_GETARG_INT64(0);
 
 	if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("integer out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_INT32((int32) arg);
 }
 
@@ -1225,10 +1229,14 @@ int82(PG_FUNCTION_ARGS)
 	int64		arg = PG_GETARG_INT64(0);
 
 	if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_INT16((int16) arg);
 }
 
@@ -1308,10 +1316,14 @@ i8tooid(PG_FUNCTION_ARGS)
 	int64		arg = PG_GETARG_INT64(0);
 
 	if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("OID out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	PG_RETURN_OID((Oid) arg);
 }
 
-- 
2.34.1

From 101651e9a37fa394113006f3f5ba30218d1fb60a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:55:55 +0800
Subject: [PATCH v7 06/20] error safe for casting inet to other types per
 pg_cast

inet_to_cidr is already error safe.

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/network.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..f34228763da 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,10 +1137,14 @@ network_show(PG_FUNCTION_ARGS)
 
+		errsave(fcinfo->context,
 
+		PG_RETURN_NULL();
+	}
+
 	/* Add /n if not present (which it won't be) */
 	if (strchr(tmp, '/') == NULL)
 	{
-- 
2.34.1

From 7d5152e88cb8506811f61f83ff2bb9a7f3cfacbe Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:12:12 +0800
Subject: [PATCH v7 07/20] error safe for casting macaddr8 to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/mac8.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..e2a7a90e42c 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,8 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
 	result = (macaddr *) palloc0(sizeof(macaddr));
 
 	if ((addr->d != 0xFF) || (addr->e != 0xFE))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("macaddr8 data out of range to convert to macaddr"),
 				 errhint("Only addresses that have FF and FE as values in the "
@@ -558,6 +559,9 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
 						 "xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted "
 						 "from macaddr8 to macaddr.")));
 
+		PG_RETURN_NULL();
+	}
+
 	result->a = addr->a;
 	result->b = addr->b;
 	result->c = addr->c;
-- 
2.34.1

From a6c47c4c351f2d69e5a6be2e77c5701a73e6a24e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:46:16 +0800
Subject: [PATCH v7 03/20] error safe for casting character to other types per
 pg_cast

only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/executor/execExprInterp.c |  2 +-
 src/backend/utils/adt/varchar.c       |  6 +++++-
 src/backend/utils/adt/xml.c           | 12 ++++++++----
 src/include/utils/xml.h               |  2 +-
 4 files changed, 15 insertions(+), 7 deletions(-)

 				*op->resvalue = PointerGetDatum(xmlparse(data,
 														 xexpr->xmloption,
-														 preserve_whitespace));
+														 preserve_whitespace, NULL));
 				*op->resnull = false;
 			}
 			break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..a95e4bd094e 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,10 +307,14 @@ bpchar(PG_FUNCTION_ARGS)
 		{
 			for (i = maxmblen; i < len; i++)
 				if (s[i] != ' ')
-					ereport(ERROR,
+				{
+					errsave(fcinfo->context,
 							(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
 							 errmsg("value too long for type character(%d)",
 									maxlen)));
+
+					PG_RETURN_NULL();
+				}
 		}
 
 		len = maxmblen;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 66b44183695..e6a5be8d9cc 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
 {
 	text	   *data = PG_GETARG_TEXT_PP(0);

@@ -1028,14 +1028,18 @@ xmlelement(XmlExpr *xexpr,

 xmltype *

 	return (xmltype *) data;
 #else

From 0849ffe6c62fccf18ce03cbd8ef593a601daf50f Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:17:00 +0800
Subject: [PATCH v7 05/20] error safe for casting character varying to other
 types per pg_cast

texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/varchar.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index a95e4bd094e..fdbef272c39 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -638,10 +638,14 @@ varchar(PG_FUNCTION_ARGS)
 	{
 		for (i = maxmblen; i < len; i++)
 			if (s_data[i] != ' ')
-				ereport(ERROR,
+			{
+				ereturn(fcinfo->context, (Datum) 0,
 						(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
 						 errmsg("value too long for type character varying(%d)",
 								maxlen)));
+
+				PG_RETURN_NULL();
+			}
 	}
 
 	PG_RETURN_VARCHAR_P((VarChar *) cstring_to_text_with_len(s_data,
-- 
2.34.1

From fed4f75d0a2a77c99eebcd6351ff099d385587bf Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:09:53 +0800
Subject: [PATCH v7 02/20] error safe for casting bit/varbit to other types per
 pg_cast

select castsource::regtype, casttarget::regtype, castfunc,
       castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;

 castsource  | casttarget  | castfunc | castcontext | castmethod |  prosrc   | proname
-------------+-------------+----------+-------------+------------+-----------+---------
 bit         | bigint      |     2076 | e           | f          | bittoint8 | int8
 bit         | integer     |     1684 | e           | f          | bittoint4 | int4
 bit         | bit         |     1685 | i           | f          | bit       | bit
 bit varying | bit varying |     1687 | i           | f          | varbit    | varbit
(4 rows)

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/varbit.c | 24 ++++++++++++++++++++----
 1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..bfcea18f4b2 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,11 +401,15 @@ bit(PG_FUNCTION_ARGS)
 		PG_RETURN_VARBIT_P(arg);
 
 	if (!isExplicit)
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
 				 errmsg("bit string length %d does not match type bit(%d)",
 						VARBITLEN(arg), len)));
 
+		PG_RETURN_NULL();
+	}
+
 	rlen = VARBITTOTALLEN(len);
 	/* set to 0 so that string is zero-padded */
 	result = (VarBit *) palloc0(rlen);
@@ -752,11 +756,15 @@ varbit(PG_FUNCTION_ARGS)
 		PG_RETURN_VARBIT_P(arg);
 
 	if (!isExplicit)
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
 				 errmsg("bit string too long for type bit varying(%d)",
 						len)));
 
+		PG_RETURN_NULL();
+	}
+
 	rlen = VARBITTOTALLEN(len);
 	result = (VarBit *) palloc(rlen);
 	SET_VARSIZE(result, rlen);
@@ -1591,10 +1599,14 @@ bittoint4(PG_FUNCTION_ARGS)
 
 	/* Check that the bit string is not too long */
 	if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("integer out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	result = 0;
 	for (r = VARBITS(arg); r < VARBITEND(arg); r++)
 	{
@@ -1671,10 +1683,14 @@ bittoint8(PG_FUNCTION_ARGS)
 
 	/* Check that the bit string is not too long */
 	if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("bigint out of range")));
 
+		PG_RETURN_NULL();
+	}
+
 	result = 0;
 	for (r = VARBITS(arg); r < VARBITEND(arg); r++)
 	{
-- 
2.34.1

From cedd16bb3bc7df4870f6689cb90e137e83dcc66a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:19:04 +0800
Subject: [PATCH v7 18/20] error safe for casting geometry data type

select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and  pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory  = 'G' or pt1.typcategory  = 'G')
order by castsource::regtype, casttarget::regtype;

 castsource | casttarget |    prosrc
------------+------------+---------------
 point      | box        | point_box
 lseg       | point      | lseg_center
 path       | polygon    | path_poly
 box        | point      | box_center
 box        | lseg       | box_diagonal
 box        | polygon    | box_poly
 box        | circle     | box_circle
 polygon    | point      | poly_center
 polygon    | path       | poly_path
 polygon    | box        | poly_box
 polygon    | circle     | poly_circle
 circle     | point      | circle_center
 circle     | box        | circle_box
 circle     | polygon    |
(14 rows)

already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly

discussion: https://postgr.es/m/CACJufxHCMzrHOW=[email-masked]
---
 src/backend/access/spgist/spgproc.c |   2 +-
 src/backend/utils/adt/float.c       |   8 +-
 src/backend/utils/adt/geo_ops.c     | 282 ++++++++++++++++++++++++----
 src/backend/utils/adt/geo_spgist.c  |   2 +-
 src/include/utils/float.h           | 107 +++++++++++
 src/include/utils/geo_decls.h       |   4 +-
 6 files changed, 358 insertions(+), 47 deletions(-)

diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c
index 660009291da..b3e0fbc59ba 100644
--- a/src/backend/access/spgist/spgproc.c
+++ b/src/backend/access/spgist/spgproc.c
@@ -51,7 +51,7 @@ point_box_distance(Point *point, BOX *box)
 	else
 		dy = 0.0;
 
-	return HYPOT(dx, dy);
+	return HYPOT(dx, dy, NULL);
 }
 
 /*
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 6747544b679..06b2b6ae295 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -772,7 +772,7 @@ float8pl(PG_FUNCTION_ARGS)
 	float8		arg1 = PG_GETARG_FLOAT8(0);
 	float8		arg2 = PG_GETARG_FLOAT8(1);
 
-	PG_RETURN_FLOAT8(float8_pl(arg1, arg2));
+	PG_RETURN_FLOAT8(float8_pl_safe(arg1, arg2, fcinfo->context));
 }
 
 Datum
@@ -781,7 +781,7 @@ float8mi(PG_FUNCTION_ARGS)
 	float8		arg1 = PG_GETARG_FLOAT8(0);
 	float8		arg2 = PG_GETARG_FLOAT8(1);
 
-	PG_RETURN_FLOAT8(float8_mi(arg1, arg2));
+	PG_RETURN_FLOAT8(float8_mi_safe(arg1, arg2, fcinfo->context));
 }
 
 Datum
@@ -790,7 +790,7 @@ float8mul(PG_FUNCTION_ARGS)
 	float8		arg1 = PG_GETARG_FLOAT8(0);
 	float8		arg2 = PG_GETARG_FLOAT8(1);
 
-	PG_RETURN_FLOAT8(float8_mul(arg1, arg2));
+	PG_RETURN_FLOAT8(float8_mul_safe(arg1, arg2, fcinfo->context));
 }
 
 Datum
@@ -799,7 +799,7 @@ float8div(PG_FUNCTION_ARGS)
 	float8		arg1 = PG_GETARG_FLOAT8(0);
 	float8		arg2 = PG_GETARG_FLOAT8(1);
 
-	PG_RETURN_FLOAT8(float8_div(arg1, arg2));
+	PG_RETURN_FLOAT8(float8_div_safe(arg1, arg2, fcinfo->context));
 }

diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 377a1b3f3ad..a5f2537da25 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -78,11 +78,14 @@ enum path_delim
 /* Routines for points */
 static inline void point_construct(Point *result, float8 x, float8 y);
 static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline bool point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+										Node *escontext);
 static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
 static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
 static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
 static inline bool point_eq_point(Point *pt1, Point *pt2);
 static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline bool point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result);
 static inline float8 point_sl(Point *pt1, Point *pt2);
 static int	point_inside(Point *p, int npts, Point *plist);
 
@@ -109,6 +112,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
 /* Routines for boxes */
 static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
 static void box_cn(Point *center, BOX *box);
+static bool box_cn_safe(Point *center, BOX *box, Node* escontext);
 static bool box_ov(BOX *box1, BOX *box2);
 static float8 box_ar(BOX *box);
 static float8 box_ht(BOX *box);
@@ -125,7 +129,7 @@ static float8 circle_ar(CIRCLE *circle);
 
 /* Routines for polygons */
 static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext);
 static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
 static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
 static bool plist_same(int npts, Point *p1, Point *p2);
@@ -851,7 +855,8 @@ box_center(PG_FUNCTION_ARGS)
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	Point	   *result = (Point *) palloc(sizeof(Point));
 
-	box_cn(result, box);
+	if (!box_cn_safe(result, box, fcinfo->context))
+		PG_RETURN_NULL();
 
 	PG_RETURN_POINT_P(result);
 }
@@ -875,6 +880,31 @@ box_cn(Point *center, BOX *box)
 	center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
 }
 
+/* safe version of box_cn */
+static bool
+box_cn_safe(Point *center, BOX *box, Node* escontext)
+{
+	float8		x;
+	float8		y;
+
+	x = float8_pl_safe(box->high.x, box->low.x, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	center->x = float8_div_safe(x, 2.0, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	y = float8_pl_safe(box->high.y, box->low.y, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	center->y = float8_div_safe(y, 2.0, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	return true;
+}
 
 /*		box_wd	-		returns the width (length) of the box
  *								  (horizontal magnitude).
@@ -1276,7 +1306,7 @@ line_distance(PG_FUNCTION_ARGS)
 
 	PG_RETURN_FLOAT8(float8_div(fabs(float8_mi(l1->C,
 											   float8_mul(ratio, l2->C))),
-								HYPOT(l1->A, l1->B)));
+								HYPOT(l1->A, l1->B, NULL)));
 }
 
 /* line_interpt()
@@ -2001,9 +2031,34 @@ point_distance(PG_FUNCTION_ARGS)
 static inline float8
 point_dt(Point *pt1, Point *pt2)
 {
-	return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+	return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y), NULL);
 }
 
+/* errror safe version of point_dt */
+static inline bool
+point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result)
+{
+	float8		x;
+	float8		y;
+
+	Assert(result != NULL);
+
+	x = float8_mi_safe(pt1->x, pt2->x, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	y  = float8_mi_safe(pt1->y, pt2->y, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	*result = HYPOT(x, y, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	return true;
+}
+
+
 Datum
 point_slope(PG_FUNCTION_ARGS)
 {
@@ -2317,13 +2372,31 @@ lseg_center(PG_FUNCTION_ARGS)
 {
 	LSEG	   *lseg = PG_GETARG_LSEG_P(0);
 	Point	   *result;
+	float8		x;
+	float8		y;
 
 	result = (Point *) palloc(sizeof(Point));
 
-	result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
-	result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+	x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
+
+	result->x = float8_div_safe(x, 2.0, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
+
+	y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
+
+	result->y = float8_div_safe(y, 2.0, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
 
 	PG_RETURN_POINT_P(result);
+
+fail:
+	PG_RETURN_NULL();
 }

@@ -4115,6 +4188,27 @@ point_add_point(Point *result, Point *pt1, Point *pt2)
 					float8_pl(pt1->y, pt2->y));
 }
 
+/* error safe version of point_add_point */
+static inline bool
+point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+					 Node *escontext)
+{
+	float8		x;
+	float8		y;
+
+	x = float8_pl_safe(pt1->x, pt2->x, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	y = float8_pl_safe(pt1->y, pt2->y, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	point_construct(result, x, y);
+
+	return true;
+}
+
 Datum
 point_add(PG_FUNCTION_ARGS)
 {
@@ -4458,10 +4552,14 @@ path_poly(PG_FUNCTION_ARGS)
 
 	/* This is not very consistent --- other similar cases return NULL ... */
 	if (!path->closed)
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("open path cannot be converted to polygon")));
 
+		PG_RETURN_NULL();
+	}
+
 	/*
 	 * Never overflows: the old size fit in MaxAllocSize, and the new size is
 	 * just a small constant larger.
@@ -4508,7 +4606,9 @@ poly_center(PG_FUNCTION_ARGS)
 
 	result = (Point *) palloc(sizeof(Point));
 
-	poly_to_circle(&circle, poly);
+	if (!poly_to_circle_safe(&circle, poly, fcinfo->context))
+		PG_RETURN_NULL();
+
 	*result = circle.center;
 
 	PG_RETURN_POINT_P(result);
@@ -5005,7 +5105,7 @@ circle_mul_pt(PG_FUNCTION_ARGS)
 	result = (CIRCLE *) palloc(sizeof(CIRCLE));
 
 	point_mul_point(&result->center, &circle->center, point);
-	result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y));
+	result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y, NULL));
 
 	PG_RETURN_CIRCLE_P(result);
 }
@@ -5020,7 +5120,7 @@ circle_div_pt(PG_FUNCTION_ARGS)
 	result = (CIRCLE *) palloc(sizeof(CIRCLE));
 
 	point_div_point(&result->center, &circle->center, point);
-	result->radius = float8_div(circle->radius, HYPOT(point->x, point->y));
+	result->radius = float8_div(circle->radius, HYPOT(point->x, point->y, NULL));
 
 	PG_RETURN_CIRCLE_P(result);
 }
@@ -5191,14 +5291,30 @@ circle_box(PG_FUNCTION_ARGS)
 
 	box = (BOX *) palloc(sizeof(BOX));
 
-	delta = float8_div(circle->radius, sqrt(2.0));
+	delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
 
-	box->high.x = float8_pl(circle->center.x, delta);
-	box->low.x = float8_mi(circle->center.x, delta);
-	box->high.y = float8_pl(circle->center.y, delta);
-	box->low.y = float8_mi(circle->center.y, delta);
+	box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
+
+	box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
+
+	box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
+
+	box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()
@@ -5209,15 +5325,35 @@ box_circle(PG_FUNCTION_ARGS)
 {
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	CIRCLE	   *circle;
+	float8		x;
+	float8		y;
 
 	circle = (CIRCLE *) palloc(sizeof(CIRCLE));
 
-	circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
-	circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+	x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
 
-	circle->radius = point_dt(&circle->center, &box->high);
+	circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
+
+	y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
+
+	circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
+
+	if (!point_dt_safe(&circle->center, &box->high, fcinfo->context,
+					   &circle->radius))
+		goto fail;
 
 	PG_RETURN_CIRCLE_P(circle);
+
+fail:
+	PG_RETURN_NULL();
 }

@@ -5230,28 +5366,38 @@ circle_poly(PG_FUNCTION_ARGS)
 	int			base_size,
 				size;
 	int			i;
+	float8		x;
+	float8		y;
 	float8		angle;
 	float8		anglestep;
 
 	if (FPzero(circle->radius))
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot convert circle with radius zero to polygon")));
+		goto fail;
+	}
 
 	if (npts < 2)
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("must request at least 2 points")));
+		goto fail;
+	}
 
 	base_size = sizeof(poly->p[0]) * npts;
 	size = offsetof(POLYGON, p) + base_size;
 
 	/* Check for integer overflow */
 	if (base_size / npts != sizeof(poly->p[0]) || size <= base_size)
-		ereport(ERROR,
+	{
+		errsave(fcinfo->context,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("too many points requested")));
-
+		goto fail;
+	}
 	poly = (POLYGON *) palloc0(size);	/* zero any holes */
 	SET_VARSIZE(poly, size);
 	poly->npts = npts;
@@ -5260,17 +5406,33 @@ circle_poly(PG_FUNCTION_ARGS)
 
 	for (i = 0; i < npts; i++)
 	{
-		angle = float8_mul(anglestep, i);
+		angle = float8_mul_safe(anglestep, i, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			goto fail;
 
-		poly->p[i].x = float8_mi(circle->center.x,
-								 float8_mul(circle->radius, cos(angle)));
-		poly->p[i].y = float8_pl(circle->center.y,
-								 float8_mul(circle->radius, sin(angle)));
+		x = float8_mul_safe(circle->radius, cos(angle), fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			goto fail;
+
+		poly->p[i].x = float8_mi_safe(circle->center.x, x, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			goto fail;
+
+		y = float8_mul_safe(circle->radius, sin(angle),fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			goto fail;
+
+		poly->p[i].y = float8_pl_safe(circle->center.y, y, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			goto fail;
 	}
 
 	make_bound_box(poly);
 
 	PG_RETURN_POLYGON_P(poly);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /*
@@ -5281,10 +5443,11 @@ circle_poly(PG_FUNCTION_ARGS)
  * XXX This algorithm should use weighted means of line segments
  *	rather than straight average values of points - tgl 97/01/21.
  */
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext)
 {
 	int			i;
+	float8		x;
 
 	Assert(poly->npts > 0);
 
@@ -5293,14 +5456,41 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
 	result->radius = 0;
 
 	for (i = 0; i < poly->npts; i++)
-		point_add_point(&result->center, &result->center, &poly->p[i]);
-	result->center.x = float8_div(result->center.x, poly->npts);
-	result->center.y = float8_div(result->center.y, poly->npts);
+	{
+		if (!point_add_point_safe(&result->center,
+								  &result->center,
+								  &poly->p[i],
+								  escontext))
+			return false;
+	}
+
+	result->center.x = float8_div_safe(result->center.x,
+									   poly->npts,
+									   escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	result->center.y = float8_div_safe(result->center.y,
+									   poly->npts,
+									   escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
 
 	for (i = 0; i < poly->npts; i++)
-		result->radius = float8_pl(result->radius,
-								   point_dt(&poly->p[i], &result->center));
-	result->radius = float8_div(result->radius, poly->npts);
+	{
+		if (!point_dt_safe(&poly->p[i], &result->center, escontext, &x))
+			return false;
+
+		result->radius = float8_pl_safe(result->radius, x, escontext);
+		if (SOFT_ERROR_OCCURRED(escontext))
+			return false;
+	}
+
+	result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	return true;
 }
 
 Datum
@@ -5311,7 +5501,8 @@ poly_circle(PG_FUNCTION_ARGS)
 
 	result = (CIRCLE *) palloc(sizeof(CIRCLE));
 
-	poly_to_circle(result, poly);
+	if (!poly_to_circle_safe(result, poly, fcinfo->context))
+		PG_RETURN_NULL();
 
 	PG_RETURN_CIRCLE_P(result);
 }
@@ -5516,7 +5707,7 @@ plist_same(int npts, Point *p1, Point *p2)
  *-----------------------------------------------------------------------
  */
 float8
-pg_hypot(float8 x, float8 y)
+pg_hypot(float8 x, float8 y, Node *escontext)
 {
 	float8		yx,
 				result;
@@ -5554,9 +5745,22 @@ pg_hypot(float8 x, float8 y)
 	result = x * sqrt(1.0 + (yx * yx));
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+	{
+		errsave(escontext,
+			errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+			errmsg("value out of range: overflow"));
+
+		result = 0.0;
+	}
+
 	if (unlikely(result == 0.0))
-		float_underflow_error();
+	{
+		errsave(escontext,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: underflow"));
+
+		result = 0.0;
+	}
 
 	return result;
 }
diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c
index fec33e95372..ffffd3cd2de 100644
--- a/src/backend/utils/adt/geo_spgist.c
+++ b/src/backend/utils/adt/geo_spgist.c
@@ -390,7 +390,7 @@ pointToRectBoxDistance(Point *point, RectBox *rect_box)
 	else
 		dy = 0;
 
-	return HYPOT(dx, dy);
+	return HYPOT(dx, dy, NULL);
 }

diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..5a7033f2a60 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,6 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
 	return result;
 }
 
+/* error safe version of float8_pl */
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	result = val1 + val2;
+	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+	{
+		errsave(escontext,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: overflow"));
+		result = 0.0f;
+	}
+
+	return result;
+}
+
 static inline float8
 float8_pl(const float8 val1, const float8 val2)
 {
@@ -133,6 +151,24 @@ float4_mi(const float4 val1, const float4 val2)
 	return result;
 }
 
+/* error safe version of float8_mi */
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	result = val1 - val2;
+	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+	{
+		errsave(escontext,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: overflow"));
+		result  = 0.0f;
+	}
+
+	return result;
+}
+
 static inline float8
 float8_mi(const float8 val1, const float8 val2)
 {
@@ -159,6 +195,36 @@ float4_mul(const float4 val1, const float4 val2)
 	return result;
 }
 
+/* error safe version of float8_mul */
+static inline float8
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	result = val1 * val2;
+	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+	{
+		errsave(escontext,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: overflow"));
+
+		result = 0.0;
+		return result;
+	}
+
+	if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
+	{
+		errsave(escontext,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: underflow"));
+
+		result = 0.0;
+		return result;
+	}
+
+	return result;
+}
+
 static inline float8
 float8_mul(const float8 val1, const float8 val2)
 {
@@ -189,6 +255,47 @@ float4_div(const float4 val1, const float4 val2)
 	return result;
 }
 
+/* error safe version of float8_div */
+static inline float8
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	if (unlikely(val2 == 0.0) && !isnan(val1))
+	{
+		errsave(escontext,
+				errcode(ERRCODE_DIVISION_BY_ZERO),
+				errmsg("division by zero"));
+
+		result = 0.0;
+		return result;
+	}
+
+	result = val1 / val2;
+
+	if (unlikely(isinf(result)) && !isinf(val1))
+	{
+		errsave(escontext,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: overflow"));
+
+		result = 0.0;
+		return result;
+	}
+
+	if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
+	{
+		errsave(escontext,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: underflow"));
+
+		result = 0.0;
+		return result;
+	}
+
+	return result;
+}
+
 static inline float8
 float8_div(const float8 val1, const float8 val2)
 {
diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h
index 8a9df75c93c..0056a26639f 100644
--- a/src/include/utils/geo_decls.h
+++ b/src/include/utils/geo_decls.h
@@ -88,7 +88,7 @@ FPge(double A, double B)
 #define FPge(A,B)				((A) >= (B))
 #endif
 
-#define HYPOT(A, B)				pg_hypot(A, B)
+#define HYPOT(A, B, escontext)				pg_hypot(A, B, escontext)
 
 /*---------------------------------------------------------------------
  * Point - (x,y)
@@ -280,6 +280,6 @@ CirclePGetDatum(const CIRCLE *X)
  * in geo_ops.c
  */
 
-extern float8 pg_hypot(float8 x, float8 y);
+extern float8 pg_hypot(float8 x, float8 y, Node *escontext);
 
 #endif							/* GEO_DECLS_H */
-- 
2.34.1

From 1338cc5c2f867ffad457fb5624eb9d0e0627e67c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:36:35 +0800
Subject: [PATCH v7 01/20] error safe for casting bytea to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/bytea.c | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..4a8adcb8204 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,10 +1027,14 @@ bytea_int2(PG_FUNCTION_ARGS)
 
+		errsave(fcinfo->context,
 
+		PG_RETURN_NULL();
+	}
+
 	/* Convert it to an integer; most significant bytes come first */
 	result = 0;
 	for (int i = 0; i < len; i++)
@@ -1052,10 +1056,14 @@ bytea_int4(PG_FUNCTION_ARGS)
 
+		errsave(fcinfo->context,
 
+		PG_RETURN_NULL();
+	}
+
 	/* Convert it to an integer; most significant bytes come first */
 	result = 0;
 	for (int i = 0; i < len; i++)
@@ -1077,10 +1085,14 @@ bytea_int8(PG_FUNCTION_ARGS)
 
+		errsave(fcinfo->context,
 
+		PG_RETURN_NULL();
+	}
+
 	/* Convert it to an integer; most significant bytes come first */
 	result = 0;
 	for (int i = 0; i < len; i++)
-- 
2.34.1

---

From: jian he <[email-masked]>
Date: Tue, 14 Oct 2025 10:00:26 +0800

>  castsource | casttarget |       castfunc       |    prosrc

Summary of what this patch set is doing:
To implement the syntax:
CAST(source_expr AS target_type DEFAULT def_expr ON CONVERSION ERROR)

we need to ensure that the transformed cast expression is error-safe.
A transformed cast expression can be either a CoerceViaIO or a FuncExpr.
Since CoerceViaIO is already error-safe but FuncExpr cannot be
rewritten as CoerceViaIO
one of the example: SELECT ('11.1'::numeric::int)

So we need to refactor how pg_cast.castfunc works to make it error-safe.
We also need to query pg_cast to determine whether a given pg_cast.castfunc is
error-safe.  For this, a new field casterrorsafe is added to pg_cast.

Patches 01–18: Refactor all pg_cast.castfunc entries (except those with the
money data type) to be error-safe.
Patches 19–20: Implement support for CAST(... AS ... DEFAULT def_expr ON
CONVERSION ERROR)

Please check the attached v8.

It includes minor changes compared to v7:
mainly a small mistake fix in src/backend/jit/llvm/llvmjit_expr.c.
Additionally, one occurrence of NO_XML_SUPPORT(); was replaced with
errsave(escontext, …).
From e015c06c2b620864c4203393d388a6b1091cbbf3 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:58:40 +0800
Subject: [PATCH v8 17/20] error safe for casting jsonb to other types per
 pg_cast

 

 
 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From f020e840b5cf648f3ef8ac726ad733ff8b4983c6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 10 Oct 2025 18:57:21 +0800
Subject: [PATCH v8 19/20] invent some error safe functions

 
+/*
 
+/*
 
 /*

+bool

 

From 7e302066156bfbb9fe2159139259ddd2027aaeca Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 11 Oct 2025 09:35:33 +0800
Subject: [PATCH v8 20/20] CAST(expr AS newtype DEFAULT ON ERROR)

demo:

 31 files changed, 2012 insertions(+), 190 deletions(-)

+--error safe type cast

+--error safe type cast
 
    <para>

 

 

+/*

 /*
 
+int
index 712b35df7e5..f54fe563543 100644

 

 
 			break;

 

 /*
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:

+/*
 
 	/*
 
-		if (coerce_hard)
+		/*
 
+/*

 

 
@@ -302,57 +302,57 @@

 
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 /* ----------------

 
 
+/*
+ * SafeTypeCastExpr -

 
 
 SELECT c.*

 SELECT c.*

 
 # ----------

From ed53de0d64021185bc51ec0efd0d566ce3563a7e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:19:04 +0800
Subject: [PATCH v8 18/20] error safe for casting geometry data type

 
-	return HYPOT(dx, dy);
 
 /*

 Datum

 Datum

 Datum

 

-	box_cn(result, box);

 

 /* line_interpt()

 

 

+		PG_RETURN_NULL();
+	}
+
 	/*

 

 

 

 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()

 

 
 
 	if (npts < 2)
-		ereport(ERROR,
+	{

 

 	make_bound_box(poly);

 /*
 
 	Assert(poly->npts > 0);

 
 
 Datum

 

 	return result;
 }
 
-	return HYPOT(dx, dy);

 

 

 
 #endif							/* GEO_DECLS_H */
-- 
2.34.1

From a16e038803cc64f835ed37174e06652f17c26088 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v8 16/20] error safe for casting timestamp to other types per
 pg_cast

 

 
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 8230876c38463a7ff0778a58da8208593e2f6f56 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v8 15/20] error safe for casting timestamptz to other types
 per pg_cast

 

 

 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 717fdef46a8aa9628dea19f23684a4a6ea2f8659 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v8 13/20] error safe for casting date to other types per
 pg_cast

 

 

From 488fab8fb6d4f3be25b347137d743631cff3a7b7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v8 14/20] error safe for casting interval to other types per
 pg_cast

 

 

From 0fcd9f5fac1a1d418d35fe642775b2db6932d3f8 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:33:57 +0800
Subject: [PATCH v8 11/20] error safe for casting float4 to other types per
 pg_cast

 
 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 
 
-- 
2.34.1

From c09d71d0be3e4b35dda2b83f3fdeedd318df76b9 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:41:55 +0800
Subject: [PATCH v8 12/20] error safe for casting float8 to other types per
 pg_cast

 
 	result = (float4) num;

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 
 
-- 
2.34.1

From c1893169d93940053735303d0093632b030ff8c2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 21:57:31 +0800
Subject: [PATCH v8 10/20] error safe for casting numeric to other types per
 pg_cast

 

 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

+		PG_RETURN_NULL();
+	}
+
 
+		PG_RETURN_NULL();
+	}
+

 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 8fe607708c8291649e6eab1dbaa4159bac06b4b7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 16:04:07 +0800
Subject: [PATCH v8 08/20] error safe for casting integer to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

 
+		PG_RETURN_NULL();
+	}
 
-- 
2.34.1

From 51e40d5a0ab1e41df990d86575a1734e9954f94f Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 16:38:37 +0800
Subject: [PATCH v8 09/20] error safe for casting bigint to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

 
+		PG_RETURN_NULL();
+	}
+

 
+		PG_RETURN_NULL();
+	}
+
 
-- 
2.34.1

From 63feaffbf971d110e57276e09e7a28dc5cbb6a57 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:12:12 +0800
Subject: [PATCH v8 07/20] error safe for casting macaddr8 to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+
 	result->a = addr->a;
 	result->b = addr->b;
 	result->c = addr->c;
-- 
2.34.1

From dc5cfd3891d76de2aa7eb5d0d694a0eeeb9af9f5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:17:00 +0800
Subject: [PATCH v8 05/20] error safe for casting character varying to other
 types per pg_cast

 

From ef691060bbb07a0c8e3d995a70437676bd9dc5f2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:55:55 +0800
Subject: [PATCH v8 06/20] error safe for casting inet to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

From e17c43deafe11573da59f2c9022de0412d1eecc6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 11 Oct 2025 10:11:37 +0800
Subject: [PATCH v8 03/20] error safe for casting character to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/executor/execExprInterp.c |  2 +-
 src/backend/utils/adt/varchar.c       |  6 +++++-
 src/backend/utils/adt/xml.c           | 17 ++++++++++++-----
 src/include/utils/xml.h               |  2 +-
 4 files changed, 19 insertions(+), 8 deletions(-)

 
 		len = maxmblen;
index 66b44183695..ba58c354e50 100644

@@ -1028,18 +1028,25 @@ xmlelement(XmlExpr *xexpr,

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
+	errsave(escontext,
+			errmsg("unsupported XML feature"),
+			errdetail("This functionality requires the server to be built with libxml support."));
 	return NULL;

From 2a5c73509c8030b55906d254f3457cee89c37639 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:49:46 +0800
Subject: [PATCH v8 04/20] error safe for casting text to other types per
 pg_cast

 
 
+/*

 
 	PG_RETURN_OID(result);
 }

From eb4a6ec310f50ceefaeef98fc31914cf8e48f683 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:09:53 +0800
Subject: [PATCH v8 02/20] error safe for casting bit/varbit to other types per
 pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
+	{
 
+		PG_RETURN_NULL();
+	}
+
 
 	if (!isExplicit)
-		ereport(ERROR,
+	{
 
+		PG_RETURN_NULL();
+	}
+

+		PG_RETURN_NULL();
+	}
+
 	result = 0;

+		PG_RETURN_NULL();
+	}
+
 	result = 0;

From 6765d6091bc3f9272833ef382cd41deb48f3b01d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:36:35 +0800
Subject: [PATCH v8 01/20] error safe for casting bytea to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

+		PG_RETURN_NULL();
+	}
+

+		PG_RETURN_NULL();
+	}
+

---

From: jian he <[email-masked]>
Date: Tue, 4 Nov 2025 14:02:14 +0800

hi.

please see the attached V9 patchset.
v9-0001 to v9-0018: refactor pg_cast.castfunc entries, make it error safe.
v9-0019:  CAST(... AS ... DEFAULT def_expr ON CONVERSION ERROR)

select pc.castsource::regtype,pc.casttarget::regtype,
castfunc::regproc, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace and not pc.casterrorsafe;

 circle     | polygon    | pg_catalog.polygon   |

The above result shows type casts using functions which cannot be error safe.
Money type related casts still can not be error safe.

Cast from circle to polygon cannot be error safe because the associated cast
function (pg_cast.castfunc) is written in SQL
(see src/backend/catalog/system_functions.sql LINE 112).
It appears impossible to make SQL language functions error safe, because
fmgr_sql ignores fcinfo->context.

eval_const_expressions cannot be error safe, so we need to handle
source_expr as an UNKNOWN constant in an error safe beforehand.
For example, we need handle ('1' AS DATE) in an error safe way
for
SELECT CAST('1' AS date  DEFAULT '2011-01-01' ON ERROR);

Since we must handle the source_expr when it is an UNKNOWN constant in an
error safe way, we can apply the same handling when source_expr is a
Const whose type is not UNKNOWN.
For example:
SELECT CAST('[(1,2),(3,4)]'::path AS polygon DEFAULT NULL ON CONVERSION ERROR);

If source_expr is a Const and the cast expression is a FuncExpr, we can be
certain that all arguments (FuncExpr->args) are also Const; see the function
chain coerce_to_target_type → coerce_type → build_coercion_expression.

We don’t need to worry about deparsing the expression because struct
SafeTypeCastExpr includes source_expr, cast_expr, and default_expr.  Even if
cast_expr is NULL, we can still use source_expr to reconstruct the original CAST
expression.

so I introduced:
evaluate_expr_safe: error safe version of evaluate_expr
CoerceUnknownConstSafe: tests whether an UNKNOWN Const can be coerced to the
target type.
From 7936c6a941547fd03972e16923fefa27358b9368 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:58:40 +0800
Subject: [PATCH v9 17/19] error safe for casting jsonb to other types per
 pg_cast

 

 
 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From e53a8808bd501f08a9660920830559b5785745a8 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v9 16/19] error safe for casting timestamp to other types per
 pg_cast

 

 
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 6b58a093f3937d96dcd404bec314064e20d3ea77 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 4 Nov 2025 13:45:37 +0800
Subject: [PATCH v9 19/19] CAST(expr AS newtype DEFAULT ON ERROR)

demo:

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 .../pg_stat_statements/expected/select.out    |  23 +-
 contrib/pg_stat_statements/sql/select.sql     |   5 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/syntax.sgml                      |  29 +
 src/backend/catalog/pg_cast.c                 |   1 +
 src/backend/executor/execExpr.c               | 117 ++-
 src/backend/executor/execExprInterp.c         |  37 +
 src/backend/jit/llvm/llvmjit_expr.c           |  49 ++
 src/backend/nodes/nodeFuncs.c                 |  67 ++
 src/backend/optimizer/util/clauses.c          |  93 +++
 src/backend/parser/gram.y                     |  22 +
 src/backend/parser/parse_agg.c                |   9 +
 src/backend/parser/parse_expr.c               | 434 ++++++++++-
 src/backend/parser/parse_func.c               |   3 +
 src/backend/parser/parse_target.c             |  14 +
 src/backend/parser/parse_type.c               |  14 +
 src/backend/utils/adt/arrayfuncs.c            |   8 +
 src/backend/utils/adt/ruleutils.c             |  22 +
 src/backend/utils/fmgr/fmgr.c                 |  13 +
 src/include/catalog/pg_cast.dat               | 330 ++++----
 src/include/catalog/pg_cast.h                 |   4 +
 src/include/executor/execExpr.h               |   8 +
 src/include/executor/executor.h               |   1 +
 src/include/fmgr.h                            |   3 +
 src/include/nodes/execnodes.h                 |  30 +
 src/include/nodes/parsenodes.h                |   6 +
 src/include/nodes/primnodes.h                 |  33 +
 src/include/optimizer/optimizer.h             |   2 +
 src/include/parser/parse_node.h               |   2 +
 src/include/parser/parse_type.h               |   2 +
 src/test/regress/expected/cast.out            | 730 ++++++++++++++++++
 src/test/regress/expected/create_cast.out     |   5 +
 src/test/regress/expected/opr_sanity.out      |  24 +-
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/cast.sql                 | 320 ++++++++
 src/test/regress/sql/create_cast.sql          |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 37 files changed, 2290 insertions(+), 188 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

+--error safe type cast

+--error safe type cast
index 6c8a0f173c9..492c99e37cd 100644
+       If the <structfield>castfunc</structfield> function is error safe, it can be used in error safe type cast.
+       For further details see <xref linkend="sql-syntax-type-casts-safe"/>.
index 237d7306fe8..d31aaef2875 100644
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
 
    <para>
@@ -2160,6 +2164,31 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
      <xref linkend="sql-createcast"/>.
     </para>
    </note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+  <title>Safe Type Cast</title>
+   <para>
+Sometimes a type cast may fail; to handle such cases, an <literal>ON ERROR</literal> clause can be
+specified to avoid potential errors. The syntax is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+    If cast the source expression to target type fails, it falls back to
+    evaluating the optionally supplied default <replaceable>expression</replaceable>
+    specified in ON <literal>ON ERROR</literal> clause.
+    Currently, this only support built-in system type casts,
+    casts created using <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+  </para>
+
+    <para>
+     For example, the following query attempts to cast a <type>text</type> type value to <type>integer</type> type,
+     but when the conversion fails, it falls back to evaluate the supplied default expression.
+<programlisting>
+SELECT CAST(TEXT 'error' AS integer DEFAULT NULL ON CONVERSION ERROR) IS NULL;  <lineannotation>true</lineannotation>
+</programlisting>
+    </para>
+  </sect3>
+
   </sect2>
 
   <sect2 id="sql-syntax-collate-exprs">

index f1569879b52..5a4b9e8b53d 100644
@@ -170,6 +173,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
 	return state;
 }
 
+/*
@@ -1701,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,

 				ExecInitExprRec(acoerce->elemexpr, elemstate,
 								&elemstate->resvalue, &elemstate->resnull);
@@ -2177,6 +2222,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
@@ -2743,7 +2796,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4741,6 +4794,68 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+	 * If we can not coerce to the target type, fallback to the DEFAULT
+	 * expression specified in the ON CONVERSION ERROR clause, and we are done.
index 67f4e00eac4..bd8b1048c5f 100644

 /*
 
+int
+		 * expression.
 
index 81d768ff2a2..07b8298dd9d 100644

@@ -5147,6 +5171,75 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 							  resultTypByVal);
 }
 
+/*
 
 /*
  * inline_set_returning_function
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a4b29c822e8..8d1dd0f278c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -15993,6 +15993,28 @@ 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 NULL_P ON CONVERSION_P ERROR_P ')'
+				{
+					TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+					SafeTypeCast *safecast = makeNode(SafeTypeCast);
+					safecast->cast = (Node *) cast;
+					safecast->def_expr = makeNullAConst(-1);;
+
+					$$ = (Node *) safecast;
+				}
+			| CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+				{
+					TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+					SafeTypeCast *safecast = makeNode(SafeTypeCast);
+					safecast->cast = (Node *) cast;
+					safecast->def_expr = $7;
+
+					$$ = (Node *) safecast;
+				}
 			| EXTRACT '(' extract_list ')'
 				{
 					$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3254c83cc6c..6573e2a93ea 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in check constraints");
 
 			break;
 
index 12119f147fc..596aac69b08 100644

 /*
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
 
@@ -2005,22 +2018,80 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 	return result;
 }
 
+/*
 	List	   *newcoercedelems = NIL;
 	ListCell   *element;
 	Oid			coerce_type;
+	Oid			coerce_type_coll;
 	bool		coerce_hard;
 
 	/*
@@ -2125,6 +2200,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		coerce_hard = false;
 	}
 
+	coerce_type_coll = get_typcollation(coerce_type);
+
 	/*
 	 * Coerce elements to target type
 	 *
@@ -2134,13 +2211,43 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	 * If the array's type was merely derived from the common type of its
 	 * elements, then the elements are implicitly coerced to the common type.
 	 * This is consistent with other uses of select_common_type().
+	 *
+	 * If can_coerce is not NULL, we need to safely test whether each element
+	 * can be coerced to the target type, similar to what is done in
+	 * transformTypeSafeCast.
 	 */
 	foreach(element, newelems)
 	{
 		Node	   *e = (Node *) lfirst(element);
+		bool		expr_is_const = false;
 		Node	   *newe;
 
-		if (coerce_hard)
+		/*
+		 * If an UNKNOWN constant cannot be coerced to the target type, set
+		 * can_coerce to false and coerce it to the TEXT data type instead.
+		 */
+		if (can_coerce != NULL && IsA(e, Const))
+		{
+			expr_is_const = true;
+
+			if (exprType(e) == UNKNOWNOID &&
+				!CoerceUnknownConstSafe(pstate, e, coerce_type, typmod))
+			{
+				*can_coerce = false;
+
+				e =  coerce_to_target_type(pstate, e,
+										   UNKNOWNOID,
+										   TEXTOID,
+										   -1,
+										   COERCION_IMPLICIT,
+										   COERCE_IMPLICIT_CAST,
+										   -1);
+			}
+		}
+
+		if (can_coerce != NULL && (!*can_coerce))
+			newe = e;
+		else if (coerce_hard)
 		{
 			newe = coerce_to_target_type(pstate, e,
 										 exprType(e),
@@ -2149,13 +2256,68 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 										 COERCION_EXPLICIT,
 										 COERCE_EXPLICIT_CAST,
 										 -1);
-			if (newe == NULL)
+			if (newe == NULL && can_coerce == NULL)
 				ereport(ERROR,
 						(errcode(ERRCODE_CANNOT_COERCE),
 						 errmsg("cannot cast type %s to %s",
 								format_type_be(exprType(e)),
 								format_type_be(coerce_type)),
 						 parser_errposition(pstate, exprLocation(e))));
+
+			if (newe == NULL && can_coerce != NULL)
+			{
+				newe = e;
+				*can_coerce = false;
+			}
+
+			if (newe != NULL && can_coerce != NULL && expr_is_const)
+			{
+				Node	   *origexpr = newe;
+				Node	   *result;
+				bool		have_collate_expr = false;
+
+				if (IsA(newe, CollateExpr))
+					have_collate_expr = true;
+
+				while (newe && IsA(newe, CollateExpr))
+					newe = (Node *) ((CollateExpr *) newe)->arg;
+
+				if (!IsA(newe, FuncExpr))
+					newe = origexpr;
+				else
+				{
+					/*
+					 * Use evaluate_expr_safe to pre-evaluate simple constant
+					 * cast expressions early, in a way that tolter errors.
+					 */
+					FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+					assign_expr_collations(pstate, (Node *) newexpr);
+
+					result = (Node *) evaluate_expr_safe((Expr *) newexpr,
+														 coerce_type,
+														 typmod,
+														 coerce_type_coll);
+					if (result == NULL)
+					{
+						newe = e;
+						*can_coerce = false;
+					}
+					else if (have_collate_expr)
+					{
+						/* Reinstall top CollateExpr */
+						CollateExpr *coll = (CollateExpr *) origexpr;
+						CollateExpr *newcoll = makeNode(CollateExpr);
+
+						newcoll->arg = (Expr *) result;
+						newcoll->collOid = coll->collOid;
+						newcoll->location = coll->location;
+						newe = (Node *) newcoll;
+					}
+					else
+						newe = result;
+				}
+			}
 		}
 		else
 			newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2905,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 									  (A_ArrayExpr *) arg,
 									  targetBaseType,
 									  elementType,
-									  targetBaseTypmod);
+									  targetBaseTypmod,
+									  NULL);
 		}
 		else
 			expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2943,259 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	return result;
 }
 
+/*
+	/* now looking at DEFAULT expression */
+	/* Test coerce UNKNOWN constant to the target type */
+		if (cast_expr == NULL)
+		bool	have_collate_expr = false;
+		Node	   *origexpr = cast_expr;
+		if (IsA(cast_expr, CollateExpr))
+			have_collate_expr = true;
+		if (IsA(cast_expr, FuncExpr))
+			Node	   *result;
+			if (!expr_is_const)
+				 * expressions early, in a way that tolter errors.
+				 * 3. Even if the function has three arguments, the second and
+				 *    third arguments will also be constants per
+				 *    coerce_to_target_type.
+				result = (Node *) evaluate_expr_safe((Expr *) newexpr,
+				if (result == NULL)
+					cast_expr = NULL;
+				else if (have_collate_expr)
+					/* Reinstall top CollateExpr */
+					CollateExpr *coll = (CollateExpr *) origexpr;
+					CollateExpr *newcoll = makeNode(CollateExpr);
+					newcoll->arg = (Expr *) result;
+					newcoll->collOid = coll->collOid;
+					newcoll->location = coll->location;
+					cast_expr = (Node *) newcoll;
+					cast_expr = result;
+			/* Nothing to do, restore cast_expr to its original value */
@@ -3193,6 +3609,8 @@ ParseExprKindName(ParseExprKind exprKind)
index 905c975d83b..dc03cf4ce74 100644
 
index a464349ee33..c1a7031a96c 100644
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
 
+		/* Exit early if the evaluation fails */
+		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 79ec136231b..5cc2f7e0cf2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10558,6 +10558,28 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+				 * Here, we cannot simply deparse cast_expr, as it may have been
+				 * optimized into a plain constant by transformTypeSafeCast.
+				 * However, in this context we need the original CAST
+				 * expression.
 
+bool
index fbfd669587f..ca52cfcd086 100644

 
 
@@ -302,57 +302,57 @@

 
 
 # Datetime category
 
 # Geometric category
+  castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 /* ----------------
 
index fa2b657fb2f..f99fc26eb1f 100644
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
index 18ae8f0d4bb..f59494c8c6f 100644
@@ -1059,6 +1059,36 @@ typedef struct DomainConstraintState
 
index ecbddd12e1b..7140093c55d 100644

+/*
+ * SafeTypeCastExpr -
index d0aa8ab0c1c..964ec024de7 100644
@@ -145,6 +145,8 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);

 
index 00000000000..6a0d32b4120
@@ -0,0 +1,730 @@
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS TEXT DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR:  the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS TEXT DEFAULT '1' COLLATE "C" ON CONV...
+DETAIL:  "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3'  ON CONVERSION ERROR));
+ 1 3
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}'  ON CONVERSION ERROR));
+ {"1 3"}
+-- test 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"}
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+            AS INT[]
+            DEFAULT NULL ON CONVERSION ERROR);
+CREATE OR REPLACE FUNCTION ret_int8() RETURNS BIGINT AS
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+ERROR:  cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+-----safe cast from bytea type to other data types
+-----safe cast from bit type to other data types
+-----safe cast from text type to other data types
+-----safe cast betweeen range data types
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+  col1 float4, col2 float8, col3 numeric, col4 numeric[],
+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[]);
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+  col0 date, col1 timestamp, col2 timestamptz,
+  col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+FROM test_safecast2;
+FROM test_safecast2;
+FROM test_safecast2;
+FROM test_safecast2;
+       CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+FROM test_safecast2;
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 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
+FROM test_safecast2;
+CREATE TEMP TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+FROM test_safecast3;
+       CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as test_safecast1,
+    CAST(1 AS date DEFAULT now()::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS test_safecast1,
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION 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 test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+                                                              pg_get_indexdef                                                              
+-------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON pg_temp.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;

 
 
 SELECT c.*

 SELECT c.*
 
 -- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a0f5fab0f5d..c6936f64a87 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
-test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252
+test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252 cast
 
 # ----------
index 00000000000..9233b88309d
@@ -0,0 +1,320 @@
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS TEXT DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector DEFAULT '1 3'  ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}'  ON CONVERSION ERROR));
+-- test 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 OR REPLACE FUNCTION ret_int8() RETURNS BIGINT AS
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+-----safe cast from bytea type to other data types
+-----safe cast from bit type to other data types
+-----safe cast from text type to other data types
+-----safe cast betweeen range data types
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+  col1 float4, col2 float8, col3 numeric, col4 numeric[],
+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[]);
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+  col0 date, col1 timestamp, col2 timestamptz,
+  col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+FROM test_safecast2;
+FROM test_safecast2;
+FROM test_safecast2;
+FROM test_safecast2;
+       CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+FROM test_safecast2;
+FROM test_safecast2;
+CREATE TEMP TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+FROM test_safecast3;
+       CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as test_safecast1,
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;

 -- check dependencies generated for that
 SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 018b5919cf6..c3e6432ec35 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2662,6 +2662,9 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

From 4266d9902fbb4f7b11fa9e882b7e9a7f5083ef6d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v9 15/19] error safe for casting timestamptz to other types
 per pg_cast

 

 

 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From c784c0b1ef9b11a72c6e372ca3bda750b817ace6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 31 Oct 2025 08:32:32 +0800
Subject: [PATCH v9 18/19] error safe for casting geometry data type

already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
can not error safe: cast circle to polygon, because it's a SQL function

discussion: https://postgr.es/m/CACJufxHCMzrHOW=[email-masked]
---
 src/backend/access/spgist/spgproc.c |   2 +-
 src/backend/utils/adt/float.c       |   8 +-
 src/backend/utils/adt/geo_ops.c     | 238 ++++++++++++++++++++++++----
 src/backend/utils/adt/geo_spgist.c  |   2 +-
 src/include/utils/float.h           | 107 +++++++++++++
 src/include/utils/geo_decls.h       |   4 +-
 6 files changed, 323 insertions(+), 38 deletions(-)

-	return HYPOT(dx, dy);
 
 /*

 Datum

 Datum

 Datum

index 377a1b3f3ad..bd031805858 100644

 
-	box_cn(result, box);

 

 /* line_interpt()

 

 

+		PG_RETURN_NULL();
+	}
+
 	/*

 

 

 

 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()

 

@@ -5281,10 +5417,11 @@ circle_poly(PG_FUNCTION_ARGS)
 
 	Assert(poly->npts > 0);
 
@@ -5293,14 +5430,41 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
 	result->radius = 0;

 
 Datum
@@ -5311,7 +5475,8 @@ poly_circle(PG_FUNCTION_ARGS)

 
 	PG_RETURN_CIRCLE_P(result);
 }
@@ -5516,7 +5681,7 @@ plist_same(int npts, Point *p1, Point *p2)
  *-----------------------------------------------------------------------
  */
 float8
-pg_hypot(float8 x, float8 y)
+pg_hypot(float8 x, float8 y, Node *escontext)
 {
 	float8		yx,
 				result;
@@ -5554,9 +5719,22 @@ pg_hypot(float8 x, float8 y)
 	result = x * sqrt(1.0 + (yx * yx));

 	return result;
 }
 
-	return HYPOT(dx, dy);

 

 

 
 #endif							/* GEO_DECLS_H */
-- 
2.34.1

From efe7ec0bc44e16399f80a0fa457c168f3a109368 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v9 14/19] error safe for casting interval to other types per
 pg_cast

 

 

From 65929bff29519aee2935a16a393bba4df7c74d50 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:41:55 +0800
Subject: [PATCH v9 12/19] error safe for casting float8 to other types per
 pg_cast

 
 	result = (float4) num;

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 
 
-- 
2.34.1

From 52145f4ed22021d5eab95886b246076425bd0382 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v9 13/19] error safe for casting date to other types per
 pg_cast

 

 

From dbb44df103a821bf9769549447ee98313803a1f6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:33:57 +0800
Subject: [PATCH v9 11/19] error safe for casting float4 to other types per
 pg_cast

 
 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 
 
-- 
2.34.1

From 8229b22d674b51e4e42c9de0abb59ba123857595 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 21:57:31 +0800
Subject: [PATCH v9 10/19] error safe for casting numeric to other types per
 pg_cast

 

 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

+		PG_RETURN_NULL();
+	}
+
 
+		PG_RETURN_NULL();
+	}
+

 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 325a86c72c4535614aa91f74d8cda542e0b6c3be Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 16:38:37 +0800
Subject: [PATCH v9 09/19] error safe for casting bigint to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

 
+		PG_RETURN_NULL();
+	}
+

 
+		PG_RETURN_NULL();
+	}
+
 
-- 
2.34.1

From 6db7ba9916baf975d2a1c895cbdb89ac918bbc8e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 16:04:07 +0800
Subject: [PATCH v9 08/19] error safe for casting integer to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

 
+		PG_RETURN_NULL();
+	}
 
-- 
2.34.1

From aabf3932a2ae4a7b6646ab5de19b98522303dd9b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:17:00 +0800
Subject: [PATCH v9 05/19] error safe for casting character varying to other
 types per pg_cast

 

From ce1908874819044361f16c8976e1d883232fc265 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:12:12 +0800
Subject: [PATCH v9 07/19] error safe for casting macaddr8 to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+
 	result->a = addr->a;
 	result->b = addr->b;
 	result->c = addr->c;
-- 
2.34.1

From 67c06609b4d565e8ebb35c855db04462d4536a50 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:55:55 +0800
Subject: [PATCH v9 06/19] error safe for casting inet to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

From 15886c5fa6b9ef57e155774f8179f541fed9843c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:49:46 +0800
Subject: [PATCH v9 04/19] error safe for casting text to other types per
 pg_cast

 
 
+/*

 
 	PG_RETURN_OID(result);
 }
index 8d735786e51..5e2676f7180 100644

From 5f7fa88b9e44b7dc684a4989471c27d0017190a7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:09:53 +0800
Subject: [PATCH v9 02/19] error safe for casting bit/varbit to other types per
 pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
+	{
 
+		PG_RETURN_NULL();
+	}
+
 
 	if (!isExplicit)
-		ereport(ERROR,
+	{
 
+		PG_RETURN_NULL();
+	}
+

+		PG_RETURN_NULL();
+	}
+
 	result = 0;

+		PG_RETURN_NULL();
+	}
+
 	result = 0;

From ad556d145cf33a3e7a9e6cbb5572b2228b1938be Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 3 Nov 2025 11:58:46 +0800
Subject: [PATCH v9 03/19] error safe for casting character to other types per
 pg_cast

 
 
 		len = maxmblen;
index 35c915573a1..4723d13928f 100644

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
+	errsave(escontext,
index 732dac47bc4..b15168c430e 100644
 						   const Datum *named_argvalue, const bool *named_argnull,
 						   const Datum *argvalue, const bool *argnull);

From 346525569c67eb8fb9d17057a46746e2a612fc94 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:36:35 +0800
Subject: [PATCH v9 01/19] error safe for casting bytea to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

+		PG_RETURN_NULL();
+	}
+

+		PG_RETURN_NULL();
+	}
+

---

From: Corey Huinker <[email-masked]>
Date: Wed, 5 Nov 2025 17:00:01 -0500

> fmgr_sql ignores fcinfo->context.

It is inevitable that some typecast functions are not type safe, either
because they cannot easily be made so, or because they come from an
extension that has not yet made them so.

> SELECT CAST('[(1,2),(3,4)]'::path AS polygon DEFAULT NULL ON CONVERSION

In the case you've presented here, the cast to type "path" doesn't need to
be safe, but the cast path->polygon does. Or rather, if it isn't safe we
should raise an error.

> CoerceUnknownConstSafe: tests whether an UNKNOWN Const can be coerced to

Good. That's the missing bit I knew we needed to add. Sorry I wasn't able
to find time.

Issue 1:
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS
<replaceable>type</replaceable>
     The <literal>CAST</literal> syntax conforms to SQL; the syntax with
     <literal>::</literal> is historical
<productname>PostgreSQL</productname>
     usage.
+    It can also be written as:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS
<replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>

The wording implies that this syntax is a new shortcut to a previously
established ON CONVERSION ERROR syntax, when it was the only way to do it
until this patch.

Issue 2:
s/is historical/is the original/
s/It can be also be written as:/The equivalent ON CONVERSION ERROR behavior
is:/

+ /*

typo on tolter? This happens in 2 places.

Issue 3:
+ errhint("Currently CAST ... DEFAULT ON CONVERSION ERROR does not support
this cast"),

Suggest: errhint("Explicit cast is defined but definition is not declared
as safe")

This hint helps distinguish the fact that it's the cast function getting in
the way of this cast-on-default working. If the cast had been defined as IO
then we wouldn't have had a problem.

It is true that we presently have no means of declaring a cast safe aside
from making it so in pg_proc.dat, but that's coming shortly.

Issue 4:

catversion.h is not updated. Which isn't a bad thing because that brings me
to...

Issue 5:

I think 0019 is a bit big for a committer to digest all in one sitting.
Currently it:

- introduces the type-safe cast node
- introduces the cast on default syntax
- redefines
- adds in test cases for all safe functions defined in patches 1-18.

As tedious as it might be, I think we want to move this patch to the front,
move all pg_cast.dat changes to their respective patches that introduce
that datatype's safe typecast function, as well as the test cases that are
made possible by that new safe typecast. That will make it easier to ensure
that each new cast has test coverage.

Yes, that's going to be a lot of catversion bumps, requiring somebody to
fudge the dates as we presently only allow for 10 catversion bumps in a
day, but the committer will either combine a few of the patches or spread
the work out over a few days.

Overall:

I like where this patchset is going. The introduction of error contexts has
eliminated a lot of the issues I was having carrying the error state
forward into the next eval step.

---

From: jian he <[email-masked]>
Date: Tue, 11 Nov 2025 11:06:11 +0800

On Thu, Nov 6, 2025 at 6:00 AM Corey Huinker <[email-masked]> wrote:
>
> Issue 5:
>
> I think 0019 is a bit big for a committer to digest all in one sitting. Currently it:
>
> As tedious as it might be, I think we want to move this patch to the front, move all pg_cast.dat changes to their respective patches that introduce that datatype's safe typecast function, as well as the test cases that are made possible by that new safe typecast. That will make it easier to ensure that each new cast has test coverage.
>
> Yes, that's going to be a lot of catversion bumps, requiring somebody to fudge the dates as we presently only allow for 10 catversion bumps in a day, but the committer will either combine a few of the patches or spread the work out over a few days.
>

Currently, patches v9-0001 through v9-0018 focus solely on refactoring
pg_cast.castfunc.  This refactoring is already valuable on its own because it
establishes the infrastructure needed for error safe type casts.
As mentioned before, to make
CAST(source_expr AS target_type DEFAULT expr ON CONVERSION ERROR);
work,
we cannot just simply replace casting FuncExpr nodes with CoerceViaIO, since
type modifiers behave differently in these two Nodes.
(e.g., casting numeric 1.11 to integer is not equivalent to casting the literal
"1.11" to integer).

Also, are we settled on this new pg_cast column name (pg_cast.casterrorsafe)?
Overall, I think it's better not to touch pg_cast.dat in each of these
refactoring patches.

Without first refactoring pg_cast.castfunc (01 to 18), making CAST ...
DEFAULT as the first patch (0001)
won't work, since pg_cast.castfunc itself is not error safe yet, and we
have no way to test the CAST DEFAULT syntax.

So we have to *first* refactor pg_cast.castfunc, make it error safe
then implement CAST DEFAULT.
--------------------------------------------------
The CAST DEFAULT patch is large, I tried to split some newly created function
into a seperate patch.
see v10-0019-invent-some-error-safe-functions.patch.

SELECT CAST('five' AS INTEGER DEFAULT 6 ON CONVERSION ERROR);
is not hard to make it error safe. ('five' is a simple Cons node)

However, I found out, to make
SELECT CAST('five'::INTEGER AS INTEGER DEFAULT 6 ON CONVERSION ERROR);
error safe is hard.

('five'::INTEGER) is a TypeCast node, normally it will error out in
transformTypeCast->
coerce_to_target_type->coerce_type ```(if (inputTypeId == UNKNOWNOID
&& IsA(node, Const)))```
If we do not error out, then we need a Node to represent the failed cast, mainly
for deparse purposes.

that means for CAST(source_expr .... DEFAULT defexpr ON CONVERSION ERROR);
The only corner case we handle is when source_expr is a simple Const node.
In all other cases, source_expr is processed through transformExpr,
which does all
the normal parse analysis for a node.

--
jian
https://www.enterprisedb.com/
From e53a8808bd501f08a9660920830559b5785745a8 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v10 16/20] error safe for casting timestamp to other types per
 pg_cast

 

 
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From c784c0b1ef9b11a72c6e372ca3bda750b817ace6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 31 Oct 2025 08:32:32 +0800
Subject: [PATCH v10 18/20] error safe for casting geometry data type

 
-	return HYPOT(dx, dy);
 
 /*

 Datum

 Datum

 Datum

 

-	box_cn(result, box);

 

 /* line_interpt()

 

 

+		PG_RETURN_NULL();
+	}
+
 	/*

 

 

 

 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()

 

 
 	Assert(poly->npts > 0);

 
 
 Datum

 

 	return result;
 }
 
-	return HYPOT(dx, dy);

 

 

 
 #endif							/* GEO_DECLS_H */
-- 
2.34.1

From 873c03536869c1c1aae9dbd600d0921c4d3fe2d5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 8 Nov 2025 13:05:39 +0800
Subject: [PATCH v10 19/20] invent some error safe functions

stringTypeDatumSafe: error safe version of stringTypeDatum
evaluate_expr_safe: error safe version of evaluate_expr
ExecInitExprSafe: soft error variant of ExecInitExpr
OidInputFunctionCallSafe: soft error variant of OidInputFunctionCall
CoerceUnknownConstSafe: attempts to coerce an UNKNOWN Const to the target type.
Returns NULL if the coercion is not possible.

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/executor/execExpr.c      | 41 +++++++++++++
 src/backend/optimizer/util/clauses.c | 69 +++++++++++++++++++++
 src/backend/parser/parse_coerce.c    | 92 ++++++++++++++++++++++++++++
 src/backend/parser/parse_type.c      | 14 +++++
 src/backend/utils/fmgr/fmgr.c        | 13 ++++
 src/include/executor/executor.h      |  1 +
 src/include/fmgr.h                   |  3 +
 src/include/optimizer/optimizer.h    |  2 +
 src/include/parser/parse_coerce.h    |  4 ++
 src/include/parser/parse_type.h      |  2 +
 10 files changed, 241 insertions(+)

+/*
 
+/*
 
 /*
  * inline_set_returning_function
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..cdcdd44a799 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -657,6 +657,98 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
 	return true;
 }
 
+/*
+ * Create an expression tree to represent coercion a UNKNOWN Const node.
+ *
+ * 'node': the input expression
+ * 'baseTypeId': base type of domain
+ * 'baseTypeMod': base type typmod of domain
+ * 'targetType': target type to coerce to
+ * 'targetTypeMod': target type typmod
+ * 'ccontext': context indicator to control coercions
+ * 'cformat': coercion display format
+ * 'location': coercion request location
+ * 'hideInputCoercion': if true, hide the input coercion under this one.
+ */
+Node *
+CoerceUnknownConstSafe(ParseState *pstate, Node *node, Oid targetType, int32 targetTypeMod,
+					   CoercionContext ccontext, CoercionForm cformat, int location,
+					   bool hideInputCoercion)
+{
+	Oid			baseTypeId;
+	int32		baseTypeMod;
+	int32		inputTypeMod;
+	Type		baseType;
+	char		*string;
+	Datum		datum;
+	Const	   *newcon;
+	Node	   *result = NULL;
+	Const	   *con = (Const *) node;
+
+	Assert(IsA(node, Const));
+	Assert(exprType(node) == UNKNOWNOID);
+
+	baseTypeMod = targetTypeMod;
+	baseTypeId = getBaseTypeAndTypmod(targetType, &baseTypeMod);
+
+	if (baseTypeId == INTERVALOID)
+		inputTypeMod = baseTypeMod;
+	else
+		inputTypeMod = -1;
+
+	baseType = typeidType(baseTypeId);
+
+	/*
+	 * We assume here that UNKNOWN's internal representation is the same as
+	 * CSTRING.
+	 */
+	if (!con->constisnull)
+		string = DatumGetCString(con->constvalue);
+	else
+		string = NULL;
+
+	if (!stringTypeDatumSafe(baseType,
+							 string,
+							 inputTypeMod,
+							 &datum))
+	{
+		ReleaseSysCache(baseType);
+		return NULL;
+	}
+
+	newcon = makeNode(Const);
+	newcon->consttype = baseTypeId;
+	newcon->consttypmod = inputTypeMod;
+	newcon->constcollid = typeTypeCollation(baseType);
+	newcon->constlen = typeLen(baseType);
+	newcon->constbyval = typeByVal(baseType);
+	newcon->constisnull = con->constisnull;
+	newcon->constvalue = datum;
+
+	/*
+	 * If it's a varlena value, force it to be in non-expanded
+	 * (non-toasted) format; this avoids any possible dependency on
+	 * external values and improves consistency of representation.
+	 */
+	if (!con->constisnull && newcon->constlen == -1)
+		newcon->constvalue =
+			PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
+
+	result = (Node *) newcon;
+
+	/* If target is a domain, apply constraints. */
+	if (baseTypeId != targetType)
+		result = coerce_to_domain(result,
+								  baseTypeId, baseTypeMod,
+								  targetType,
+								  ccontext, cformat, location,
+								  false);
+
+	ReleaseSysCache(baseType);
+
+	return result;
+}
+
 
 /*
  * Create an expression tree to represent coercion to a domain type.

+bool

 extern List *expand_function_arguments(List *args, bool include_out_arguments,
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 8d775c72c59..ad16cdd7022 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -48,6 +48,10 @@ 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 *CoerceUnknownConstSafe(ParseState *pstate, Node *node,
+									Oid targetType, int32 targetTypeMod,
+									CoercionContext ccontext, CoercionForm cformat,
+									int location, bool hideInputCoercion);
 extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
 							  Oid typeId,
 							  CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..12381aed64c 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, char *string, int32 atttypmod);
+extern bool stringTypeDatumSafe(Type tp, char *string, int32 atttypmod,
+								Datum *result);

From 7936c6a941547fd03972e16923fefa27358b9368 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:58:40 +0800
Subject: [PATCH v10 17/20] error safe for casting jsonb to other types per
 pg_cast

 

 
 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From 636f9467c9c6a82604b847129a55411674fe8a23 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 10 Nov 2025 14:08:49 +0800
Subject: [PATCH v10 20/20] CAST(expr AS newtype DEFAULT ON ERROR)

demo:

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 .../pg_stat_statements/expected/select.out    |  23 +-
 contrib/pg_stat_statements/sql/select.sql     |   5 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/syntax.sgml                      |  29 +
 src/backend/catalog/pg_cast.c                 |   1 +
 src/backend/executor/execExpr.c               |  76 +-
 src/backend/executor/execExprInterp.c         |  37 +
 src/backend/jit/llvm/llvmjit_expr.c           |  49 ++
 src/backend/nodes/nodeFuncs.c                 |  61 ++
 src/backend/optimizer/util/clauses.c          |  25 +
 src/backend/parser/gram.y                     |  22 +
 src/backend/parser/parse_agg.c                |   9 +
 src/backend/parser/parse_expr.c               | 424 +++++++++-
 src/backend/parser/parse_func.c               |   3 +
 src/backend/parser/parse_target.c             |  14 +
 src/backend/utils/adt/arrayfuncs.c            |   8 +
 src/backend/utils/adt/ruleutils.c             |  21 +
 src/include/catalog/pg_cast.dat               | 330 ++++----
 src/include/catalog/pg_cast.h                 |   4 +
 src/include/executor/execExpr.h               |   8 +
 src/include/nodes/execnodes.h                 |  30 +
 src/include/nodes/parsenodes.h                |  11 +
 src/include/nodes/primnodes.h                 |  36 +
 src/include/parser/parse_node.h               |   2 +
 src/test/regress/expected/cast.out            | 767 ++++++++++++++++++
 src/test/regress/expected/create_cast.out     |   5 +
 src/test/regress/expected/opr_sanity.out      |  24 +-
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/cast.sql                 | 335 ++++++++
 src/test/regress/sql/create_cast.sql          |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 31 files changed, 2183 insertions(+), 194 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

+--error safe type cast

+--error safe type cast
index 237d7306fe8..3dc7baf08b9 100644
+    The equivalent ON CONVERSION ERROR behavior is:
 
    <para>

 
index b302be36f73..5a4b9e8b53d 100644

 

 
+/*

 /*
 
+int
 
index ede838cd40c..fe3e6b11a08 100644
@@ -1706,6 +1717,9 @@ exprLocation(const Node *expr)
 				loc = leftmostLoc(loc, tc->location);
+			loc = ((const SafeTypeCastExpr *) expr)->location;
 			/* just use argument's location */
 			loc = exprLocation(((const CollateClause *) expr)->arg);
@@ -2321,6 +2335,18 @@ expression_tree_walker_impl(Node *node,
@@ -3330,6 +3356,19 @@ expression_tree_mutator_impl(Node *node,
@@ -4464,6 +4503,28 @@ raw_expression_tree_walker_impl(Node *node,
+				if (WALK(sc->raw_default))
index 3f9ff455ba6..56649162845 100644
@@ -2945,6 +2945,31 @@ eval_const_expressions_mutator(Node *node,
 
+				if (stc->cast_expr != NULL)
 
index a4b29c822e8..a8c75f53363 100644
+					safecast->raw_default = makeNullAConst(-1);;
+					safecast->raw_default = $7;
 
 			break;
 
index 12119f147fc..8fc8ee47aea 100644

@@ -164,13 +169,17 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
+			result = transformTypeSafeCast(pstate, (SafeTypeCast *) expr);
+			break;
+
 		case T_CollateClause:
 			result = transformCollateClause(pstate, (CollateClause *) expr);
 			break;
@@ -564,6 +573,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_VALUES_SINGLE:
 		case EXPR_KIND_CHECK_CONSTRAINT:
 		case EXPR_KIND_DOMAIN_CHECK:
+		case EXPR_KIND_CAST_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 		case EXPR_KIND_INDEX_EXPRESSION:
 		case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1834,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("cannot use subquery in check constraint");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("cannot use subquery in CAST DEFAULT expression");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
@@ -2011,16 +2024,23 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
  * If the caller specifies the target type, the resulting array will
  * be of exactly that type.  Otherwise we try to infer a common type
  * for the elements using select_common_type().
+ *
+ * Most of the time, can_coerce will be NULL.
+ * can_coerce is not NULL only when performing parse analysis for expressions
+ * like CAST(DEFAULT ... ON CONVERSION ERROR). can_coerce should be assumed to
+ * default to true. If coerce array elements to the target type fails,
+ * can_coerce will be set to false.
  */
 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 *can_coerce)
 {
 	ArrayExpr  *newa = makeNode(ArrayExpr);
 	List	   *newelems = NIL;
 	List	   *newcoercedelems = NIL;
 	ListCell   *element;
 	Oid			coerce_type;
+	Oid			coerce_type_coll;
 	bool		coerce_hard;
 
 	/*
@@ -2045,9 +2065,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  can_coerce);
 			/* we certainly have an array here */
-			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			Assert(can_coerce || array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
 		}
 		else
@@ -2088,6 +2109,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	}
 	else
 	{
+		/* The target type is valid when performing type-safe cast */
+		Assert(can_coerce == NULL);
+
 		/* Can't handle an empty array without a target type */
 		if (newelems == NIL)
 			ereport(ERROR,
@@ -2125,6 +2149,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		coerce_hard = false;
 	}
 
@@ -2134,13 +2160,53 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
-		Node	   *newe;
+		Node	   *newe = NULL;
 
-		if (coerce_hard)
+		if (can_coerce != NULL && IsA(e, Const))
+		{
+			expr_is_const = true;
+
+			/* coerce UNKNOWN Const to the target type */
+			if (*can_coerce && exprType(e) == UNKNOWNOID)
+			{
+				newe = CoerceUnknownConstSafe(pstate, e, coerce_type, typmod,
+											  COERCION_IMPLICIT,
+											  COERCE_IMPLICIT_CAST,
+											  -1,
+											  false);
+				if (newe == NULL)
+				{
+					newe = e;
+					*can_coerce = false;
+				}
+				newcoercedelems = lappend(newcoercedelems, newe);
+
+				/*
+				 * Done handling this UNKNOWN Const element; move on to the
+				 * next.
+				 */
+				continue;
+			}
+		}
+
+		if (can_coerce != NULL && (!*can_coerce))
+		{
+			/*
+			 * Can not coerce source expression to the target type, just append
+			 * the transformed element expression to the list.
+			 */
+			newe = e;
+		}
+		else if (coerce_hard)
 		{
 			newe = coerce_to_target_type(pstate, e,
 										 exprType(e),
@@ -2150,12 +2216,72 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 										 COERCE_EXPLICIT_CAST,
 										 -1);
 			if (newe == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_CANNOT_COERCE),
-						 errmsg("cannot cast type %s to %s",
-								format_type_be(exprType(e)),
-								format_type_be(coerce_type)),
-						 parser_errposition(pstate, exprLocation(e))));
+			{
+				if (can_coerce == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_CANNOT_COERCE),
+							errmsg("cannot cast type %s to %s",
+									format_type_be(exprType(e)),
+									format_type_be(coerce_type)),
+							parser_errposition(pstate, exprLocation(e))));
+				else
+				{
+					/*
+					 * Can not coerce source expression to the target type, just
+					 * append the transformed element expression to the list.
+					 */
+					newe = e;
+					*can_coerce = false;
+				}
+			}
+			else if (can_coerce != NULL && expr_is_const)
+			{
+				Node	   *origexpr = newe;
+				Node	   *result;
+				bool		have_collate_expr = false;
+
+				if (IsA(newe, CollateExpr))
+					have_collate_expr = true;
+
+				while (newe && IsA(newe, CollateExpr))
+					newe = (Node *) ((CollateExpr *) newe)->arg;
+
+				if (!IsA(newe, FuncExpr))
+					newe = origexpr;
+				else
+				{
+					/*
+					 * Use evaluate_expr_safe to pre-evaluate simple constant
+					 * cast expressions early, in a way that tolerate errors.
+					 */
+					FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+					assign_expr_collations(pstate, (Node *) newexpr);
+
+					result = (Node *) evaluate_expr_safe((Expr *) newexpr,
+														 coerce_type,
+														 typmod,
+														 coerce_type_coll);
+					if (result == NULL)
+					{
+						newe = e;
+						*can_coerce = false;
+					}
+					else if (have_collate_expr)
+					{
+						/* Reinstall top CollateExpr */
+						CollateExpr *coll = (CollateExpr *) origexpr;
+						CollateExpr *newcoll = makeNode(CollateExpr);
+
+						newcoll->arg = (Expr *) result;
+						newcoll->collOid = coll->collOid;
+						newcoll->location = coll->location;
+						newe = (Node *) newcoll;
+					}
+					else
+						newe = result;
+				}
+			}
 		}
 		else
 			newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2869,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 									  (A_ArrayExpr *) arg,
 									  targetBaseType,
 									  elementType,
-									  targetBaseTypmod);
+									  targetBaseTypmod,
+									  NULL);
 		}
 		else
 			expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2907,273 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	return result;
 }
 
+/*
+	ParseLoc	location;
+	def_expr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+				parser_coercion_errposition(pstate, exprLocation(tc->raw_default), def_expr));
+		/* coerce UNKNOWN Const to the target type */
+			cast_expr = CoerceUnknownConstSafe(pstate, source_expr,
+											   location, false);
+	if (can_coerce && cast_expr == NULL)
+		Assert(cast_expr != NULL);
+							errhint("Explicit cast is defined but definition is not declared as safe"),
+				 * expressions early, in a way that tolerate errors.
+				 * 3. Even if FuncExpr (for castfunc) node has three arguments,
+				 *    the second and third arguments will also be constants per
+				 *    coerce_to_target_type. Evaluating a FuncExpr whose
+				 *    arguments are all Const is safe.
+					 * It has been pre-evaluated that the source expression can
+					 * not coerce to the target type.
+	result->location = location;
@@ -3193,6 +3587,8 @@ ParseExprKindName(ParseExprKind exprKind)
 
+		/* Exit early if the evaluation fails */
+		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 79ec136231b..e3dd86a6520 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10558,6 +10558,27 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+				 * We cannot deparsing cast_expr directly, since
+				 * transformTypeSafeCast may have folded it into a simple
+				 * constant or NULL. Instead, we use source_expr and
+				 * default_expr to reconstruct the CAST DEFAULT clause.

 
 
@@ -302,57 +302,57 @@

 
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 /* ----------------

index ecbddd12e1b..c47d412b560 100644
@@ -400,6 +400,17 @@ typedef struct TypeCast
 
+/*
+ * SafeTypeCast - a CAST(source_expr AS target_type) DEFAULT ON CONVERSION ERROR
+ * construct
+ */
+typedef struct SafeTypeCast
+{
+	NodeTag		type;
+	Node		*cast;			/* TypeCast expression */
+	Node	   	*raw_default;	/* DEFAULT expression */
+} SafeTypeCast;
+
 /*
  * CollateClause - a COLLATE expression
  */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..c3bd722cb2f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ typedef enum CoercionForm
 	COERCE_SQL_SYNTAX,			/* display with SQL-mandated special syntax */
 } CoercionForm;
 
+/*
+ * SafeTypeCastExpr -
+	 * The transformed cast expression; NULL if we can not cocerce source
+	 * expression to the target type
+	/* Original SafeTypeCastExpr's location */
+	ParseLoc	location;
index 00000000000..9fbd6310ea6
@@ -0,0 +1,767 @@
+HINT:  Explicit cast is defined but definition is not declared as safe
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' 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); --ok
+ {21,22}
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR:  invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR:  invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+CREATE FUNCTION ret_setint() RETURNS SETOF integer AS
+SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+ERROR:  cannot cast type date to json
+LINE 1: SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION...
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR:  invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR:  cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION E...
+HINT:  Explicit cast is defined but definition is not declared as safe
+HINT:  Explicit cast is defined but definition is not declared as safe
+HINT:  Explicit cast is defined but definition is not declared as safe
+HINT:  Explicit cast is defined but definition is not declared as safe
+HINT:  Explicit cast is defined but definition is not declared as safe
+CREATE TABLE test_safecast3(col0 jsonb);
+       CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+       CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+    CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+    CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+    CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+    CAST(ARRAY[ARRAY[1, 2], ARRAY['three', 'a']] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
index 0e69644bca2..f4035c338aa 100644
 
+HINT:  Explicit cast is defined but definition is not declared as safe

 
 SELECT c.*

 SELECT c.*

 # ----------
index 00000000000..f93ddaa1bb2
@@ -0,0 +1,335 @@
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' 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); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+CREATE FUNCTION ret_setint() RETURNS SETOF integer AS
+SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+CREATE TABLE test_safecast3(col0 jsonb);
+       CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+       CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;

From 4266d9902fbb4f7b11fa9e882b7e9a7f5083ef6d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v10 15/20] error safe for casting timestamptz to other types
 per pg_cast

 

 

 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From efe7ec0bc44e16399f80a0fa457c168f3a109368 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v10 14/20] error safe for casting interval to other types per
 pg_cast

 

 

From 65929bff29519aee2935a16a393bba4df7c74d50 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:41:55 +0800
Subject: [PATCH v10 12/20] error safe for casting float8 to other types per
 pg_cast

 
 	result = (float4) num;

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 
 
-- 
2.34.1

From 52145f4ed22021d5eab95886b246076425bd0382 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v10 13/20] error safe for casting date to other types per
 pg_cast

 

 

From dbb44df103a821bf9769549447ee98313803a1f6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:33:57 +0800
Subject: [PATCH v10 11/20] error safe for casting float4 to other types per
 pg_cast

 
 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 	/* Range check */
 
+		PG_RETURN_NULL();
+	}
+

 
 
-- 
2.34.1

From 325a86c72c4535614aa91f74d8cda542e0b6c3be Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 16:38:37 +0800
Subject: [PATCH v10 09/20] error safe for casting bigint to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

 
+		PG_RETURN_NULL();
+	}
+

 
+		PG_RETURN_NULL();
+	}
+
 
-- 
2.34.1

From ce1908874819044361f16c8976e1d883232fc265 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:12:12 +0800
Subject: [PATCH v10 07/20] error safe for casting macaddr8 to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+
 	result->a = addr->a;
 	result->b = addr->b;
 	result->c = addr->c;
-- 
2.34.1

From 67c06609b4d565e8ebb35c855db04462d4536a50 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:55:55 +0800
Subject: [PATCH v10 06/20] error safe for casting inet to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

From 8229b22d674b51e4e42c9de0abb59ba123857595 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 21:57:31 +0800
Subject: [PATCH v10 10/20] error safe for casting numeric to other types per
 pg_cast

 

 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

+		PG_RETURN_NULL();
+	}
+
 
+		PG_RETURN_NULL();
+	}
+

 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 6db7ba9916baf975d2a1c895cbdb89ac918bbc8e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 16:04:07 +0800
Subject: [PATCH v10 08/20] error safe for casting integer to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

 
+		PG_RETURN_NULL();
+	}
 
-- 
2.34.1

From 15886c5fa6b9ef57e155774f8179f541fed9843c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:49:46 +0800
Subject: [PATCH v10 04/20] error safe for casting text to other types per
 pg_cast

 
 
+/*

 
 	PG_RETURN_OID(result);
 }

From aabf3932a2ae4a7b6646ab5de19b98522303dd9b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:17:00 +0800
Subject: [PATCH v10 05/20] error safe for casting character varying to other
 types per pg_cast

 

From 5f7fa88b9e44b7dc684a4989471c27d0017190a7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 19:09:53 +0800
Subject: [PATCH v10 02/20] error safe for casting bit/varbit to other types
 per pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
+	{
 
+		PG_RETURN_NULL();
+	}
+
 
 	if (!isExplicit)
-		ereport(ERROR,
+	{
 
+		PG_RETURN_NULL();
+	}
+

+		PG_RETURN_NULL();
+	}
+
 	result = 0;

+		PG_RETURN_NULL();
+	}
+
 	result = 0;

From ad556d145cf33a3e7a9e6cbb5572b2228b1938be Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 3 Nov 2025 11:58:46 +0800
Subject: [PATCH v10 03/20] error safe for casting character to other types per
 pg_cast

 
 
 		len = maxmblen;

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
+	errsave(escontext,

From 346525569c67eb8fb9d17057a46746e2a612fc94 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 15:36:35 +0800
Subject: [PATCH v10 01/20] error safe for casting bytea to other types per
 pg_cast

 
 
+		PG_RETURN_NULL();
+	}
+

+		PG_RETURN_NULL();
+	}
+

+		PG_RETURN_NULL();
+	}
+

---

From: Corey Huinker <[email-masked]>
Date: Mon, 10 Nov 2025 23:36:49 -0500

> (e.g., casting numeric 1.11 to integer is not equivalent to casting the

I wasn't suggesting that. I was suggesting that we move tests that use a
given type's custom cast function into the same patch that makes that cast
safe. This would mean that the initial syntax+nodes+executor patch starts
out only with test cases known to not have custom functions.

> Overall, I think it's better not to touch pg_cast.dat in each of these

I'm fine with it. I can see having 'f' and 's' both mean cast functions,
but 's' means safe, but the extra boolean works too and we'll be fine with
either method.

> then implement CAST DEFAULT.

Makes sense.

> SELECT CAST('five'::INTEGER AS INTEGER DEFAULT 6 ON CONVERSION ERROR);

That's no problem at all. The CAST () function can't do anything about an
input that's already an error before the CAST node executes. Nor should it.
It should only concern itself with errors related to the actual casting of
a value to the specified type.

> If we do not error out, then we need a Node to represent the failed cast,

We _must_ error out in that case, before the CAST executes.

> the normal parse analysis for a node.

I'll get to reviewing this patchset soon.

---

From: Amul Sul <[email-masked]>
Date: Mon, 17 Nov 2025 19:13:15 +0530

On Tue, Nov 11, 2025 at 8:37 AM jian he <[email-masked]> wrote:
> > [...]
> pg_cast.castfunc.

I had a quick look but haven't finished the full review due to lack of
time today. Here are a few initial comments:

v10-0003:

-   NO_XML_SUPPORT();
+   errsave(escontext,
+           errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+           errmsg("unsupported XML feature"),
+           errdetail("This functionality requires the server to be
built with libxml support."));
    return NULL;

Can use ereturn() instead of errsave() followed by return NULL.
--

10-0004:

If I am not mistaken, it looks like an exact copy of
textToQualifiedNameList(). I think you can simply keep only
textToQualifiedNameListSafe() and call that from
textToQualifiedNameList() with a NULL value for escontext. This way,
all errsave() or ereturn() calls will behave like ereport().

The same logic applies to RangeVarGetRelidExtendedSafe() and
makeRangeVarFromNameListSafe. These can be called from
RangeVarGetRelidExtended() and makeRangeVarFromNameList(),
respectively.
--

+   {
+       errsave(escontext,

I prefer ereturn() instead of errsave + return.
--

v10-0017

Here, I think, you can use ereturn() to return void.
--

v10-0018

Here, you can use ereturn() to return 0.0. Similar changes are needed
at other places in the same patch.

Regards,
Amul

---

From: Corey Huinker <[email-masked]>
Date: Fri, 21 Nov 2025 02:32:45 -0500

> out only with test cases known to not have custom functions.

With a second look at the patches, I think the organization is fine as-is.

> but 's' means safe, but the extra boolean works too and we'll be fine with

I can work on this part if you don't have time.

> I'll get to reviewing this patchset soon.

Not as soon as I would have liked, but the patchset still applies without
error, and survives all of my attempts to break it, including user defined
functions that generate errors deterministically as well as
non-deterministically, hoping to see it incorrectly use the default rather
than reporting the error.

---

From: jian he <[email-masked]>
Date: Fri, 21 Nov 2025 16:41:05 +0800

hi.

List *
textToQualifiedNameList(text *textval)
{
    List       *namelist;
    rawname = text_to_cstring(textval);
    if (!SplitIdentifierString(rawname, '.', &namelist))
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_NAME),
                 errmsg("invalid name syntax")));
}

I don’t see any way to pass the escontext (ErrorSaveContext) without changing
the textToQualifiedNameList function signature.

There are around 30 occurrences of textToQualifiedNameList.
changing the function textToQualifiedNameList signature is invasive,
so I tend to avoid it.
I think it's easier to just duplicate textToQualifiedNameList
than changing the function textToQualifiedNameList signature.

Am I missing something?

--
jian
https://www.enterprisedb.com

---

From: Corey Huinker <[email-masked]>
Date: Fri, 21 Nov 2025 17:18:59 -0500

> ...

I think we need to keep these separate. The execution paths that don't care
about capturing errors shouldn't be slowed down by minority of paths that
do. That may change in the future, but if it does, we'll be getting rid of
a lot of internal functions with this type of difference.

---

From: Amul Sul <[email-masked]>
Date: Mon, 24 Nov 2025 09:08:08 +0530

> than changing the function textToQualifiedNameList signature.

The change I was suggesting will be as below:

--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2684,6 +2684,13 @@ name_text(PG_FUNCTION_ARGS)
  */
 List *
 textToQualifiedNameList(text *textval)
+{
+   textToQualifiedNameListSafe(textval, NULL);
+}
+
+/* error safe version of textToQualifiedNameList */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
 {
    char       *rawname;
    List       *result = NIL;

We must try to avoid duplication whenever possible, as any bug fixes
or enhancements would need to be copied to multiple places, which is
often overlooked.

Regards,
Amul

---

From: jian he <[email-masked]>
Date: Tue, 25 Nov 2025 10:59:42 +0800

> or enhancements would need to be copied to multiple places, which is
hi.
great idea!

I incorporated all of your ideas into v11.
I replaced all errsave to ereturn.
I aslo simplified T_SafeTypeCastExpr expression initialization, evaluation logic
within execExpr.c, execExprInterp.c.

but one thing I didn't touch: float8_div.

+static inline float8

but we can change float8_div to:

static inline float8
float8_div(const float8 val1, const float8 val2)
{
   return float8_div_safe(val1, val2, NULL);
}
I am worried that entering another function would cause a minor performance
degradation.  And since these simple functions are so simple, keeping them
separated should not be a big problem.  also I placed float8_div,
float8_div_safe together.

--
jian
https://www.enterprisedb.com/
From 78f39bc072a0e228ea225b512d4a7940f9b74e63 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 16:38:23 +0800
Subject: [PATCH v11 18/20] error safe for casting geometry data type

discussion: https://postgr.es/m/CACJufxHCMzrHOW=[email-masked]
---
 src/backend/access/spgist/spgproc.c |   2 +-
 src/backend/utils/adt/geo_ops.c     | 225 +++++++++++++++++++++++-----
 src/backend/utils/adt/geo_spgist.c  |   2 +-
 src/include/utils/float.h           |  77 ++++++++++
 src/include/utils/geo_decls.h       |   4 +-
 5 files changed, 271 insertions(+), 39 deletions(-)

-	return HYPOT(dx, dy);
 
 /*
index 377a1b3f3ad..bae8730c347 100644
+static inline float8 point_dt_safe(Point *pt1, Point *pt2, Node *escontext);

 
-	box_cn(result, box);
 
 	PG_RETURN_POINT_P(result);
 }
@@ -871,10 +876,33 @@ box_ar(BOX *box)
 static void
 box_cn(Point *center, BOX *box)
 {
-	center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
-	center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+	(void) box_cn_safe(center, box, NULL);
 }
 
+static bool
 
 /*		box_wd	-		returns the width (length) of the box
  *								  (horizontal magnitude).
@@ -1276,7 +1304,7 @@ line_distance(PG_FUNCTION_ARGS)

 /* line_interpt()
@@ -2001,9 +2029,27 @@ point_distance(PG_FUNCTION_ARGS)
 static inline float8
 point_dt(Point *pt1, Point *pt2)
 {
-	return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+	return point_dt_safe(pt1, pt2, NULL);
 }
 
+static inline float8
+point_dt_safe(Point *pt1, Point *pt2, Node *escontext)
+{
+	float8		x;
+	float8		y;
+
+	x = float8_mi_safe(pt1->x, pt2->x, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return 0.0;
+
+	y  = float8_mi_safe(pt1->y, pt2->y, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return 0.0;
+
+	return HYPOT(x, y, escontext);
+}
+
+
 Datum
 point_slope(PG_FUNCTION_ARGS)
 {
@@ -2317,13 +2363,31 @@ lseg_center(PG_FUNCTION_ARGS)
 {
 	LSEG	   *lseg = PG_GETARG_LSEG_P(0);
 	Point	   *result;
+	float8		x;
+	float8		y;

 

@@ -4110,9 +4174,27 @@ construct_point(PG_FUNCTION_ARGS)
 static inline void
 point_add_point(Point *result, Point *pt1, Point *pt2)
 {
-	point_construct(result,
-					float8_pl(pt1->x, pt2->x),
-					float8_pl(pt1->y, pt2->y));
+	(void) point_add_point_safe(result, pt1, pt2, NULL);
+}
+
+static inline bool
+point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+					 Node *escontext)
+{
+	float8		x;
+	float8		y;
+
+	x = float8_pl_safe(pt1->x, pt2->x, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	y = float8_pl_safe(pt1->y, pt2->y, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return false;
+
+	point_construct(result, x, y);
+
+	return true;
 }
 
 Datum
@@ -4458,7 +4540,7 @@ path_poly(PG_FUNCTION_ARGS)

@@ -4508,7 +4590,9 @@ poly_center(PG_FUNCTION_ARGS)

 
 	PG_RETURN_POINT_P(result);
@@ -5005,7 +5089,7 @@ circle_mul_pt(PG_FUNCTION_ARGS)
 	result = (CIRCLE *) palloc(sizeof(CIRCLE));

 	PG_RETURN_CIRCLE_P(result);
 }
@@ -5020,7 +5104,7 @@ circle_div_pt(PG_FUNCTION_ARGS)
 	result = (CIRCLE *) palloc(sizeof(CIRCLE));

 	PG_RETURN_CIRCLE_P(result);
 }
@@ -5191,14 +5275,30 @@ circle_box(PG_FUNCTION_ARGS)

 
 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()
@@ -5209,15 +5309,35 @@ box_circle(PG_FUNCTION_ARGS)
 {
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	CIRCLE	   *circle;
+	float8		x;
+	float8		y;

 
+	circle->radius = point_dt_safe(&circle->center, &box->high, fcinfo->context);

@@ -5281,10 +5401,11 @@ circle_poly(PG_FUNCTION_ARGS)
 
 	Assert(poly->npts > 0);
 
@@ -5293,14 +5414,42 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
 	result->radius = 0;

+		x = point_dt_safe(&poly->p[i], &result->center, escontext);
 
 Datum
@@ -5311,7 +5460,8 @@ poly_circle(PG_FUNCTION_ARGS)

 
 	PG_RETURN_CIRCLE_P(result);
 }
@@ -5516,7 +5666,7 @@ plist_same(int npts, Point *p1, Point *p2)
  *-----------------------------------------------------------------------
  */
 float8
-pg_hypot(float8 x, float8 y)
+pg_hypot(float8 x, float8 y, Node *escontext)
 {
 	float8		yx,
 				result;
@@ -5554,9 +5704,14 @@ pg_hypot(float8 x, float8 y)
 	result = x * sqrt(1.0 + (yx * yx));
 
+		ereturn(escontext, 0.0,
+		ereturn(escontext, 0.0,
 
 	return result;
 }
 
-	return HYPOT(dx, dy);

diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..aec376ffc56 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -121,6 +121,21 @@ float8_pl(const float8 val1, const float8 val2)
 	return result;
 }
 
+/* error safe version of float8_pl */
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	result = val1 + val2;
+	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+		ereturn(escontext, 0,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: overflow"));
+
+	return result;
+}
+
 static inline float4
 float4_mi(const float4 val1, const float4 val2)
 {
@@ -145,6 +160,21 @@ float8_mi(const float8 val1, const float8 val2)
 	return result;
 }
 
+/* error safe version of float8_mi */
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	result = val1 - val2;
+	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+		ereturn(escontext, 0,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: overflow"));
+
+	return result;
+}
+
 static inline float4
 float4_mul(const float4 val1, const float4 val2)
 {
@@ -173,6 +203,27 @@ float8_mul(const float8 val1, const float8 val2)
 	return result;
 }
 
+/* error safe version of float8_mul */
+static inline float8
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	result = val1 * val2;
+
+	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+		ereturn(escontext, 0,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: overflow"));
+
+	if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
+		ereturn(escontext, 0,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("value out of range: underflow"));
+
+	return result;
+}
+
 static inline float4
 float4_div(const float4 val1, const float4 val2)
 {
@@ -205,6 +256,32 @@ float8_div(const float8 val1, const float8 val2)
 	return result;
 }
 
+		ereturn(escontext, 0,
+		ereturn(escontext, 0,
+		ereturn(escontext, 0,
  * Routines for NaN-aware comparisons

 
 
 #endif							/* GEO_DECLS_H */
-- 
2.34.1

From f1d5dc9962c138323ea2e3d0949fe32663e58800 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 25 Nov 2025 10:37:33 +0800
Subject: [PATCH v11 20/20] CAST(expr AS newtype DEFAULT ON ERROR)

demo:

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 .../pg_stat_statements/expected/select.out    |  23 +-
 contrib/pg_stat_statements/sql/select.sql     |   5 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/syntax.sgml                      |  29 +
 src/backend/catalog/pg_cast.c                 |   1 +
 src/backend/executor/execExpr.c               |  61 +-
 src/backend/executor/execExprInterp.c         |  29 +
 src/backend/jit/llvm/llvmjit_expr.c           |  26 +
 src/backend/nodes/nodeFuncs.c                 |  61 ++
 src/backend/optimizer/util/clauses.c          |  26 +
 src/backend/parser/gram.y                     |  22 +
 src/backend/parser/parse_agg.c                |   9 +
 src/backend/parser/parse_expr.c               | 424 +++++++++-
 src/backend/parser/parse_func.c               |   3 +
 src/backend/parser/parse_target.c             |  14 +
 src/backend/utils/adt/arrayfuncs.c            |   8 +
 src/backend/utils/adt/ruleutils.c             |  21 +
 src/include/catalog/pg_cast.dat               | 330 ++++----
 src/include/catalog/pg_cast.h                 |   4 +
 src/include/executor/execExpr.h               |   7 +
 src/include/nodes/execnodes.h                 |  21 +
 src/include/nodes/parsenodes.h                |  11 +
 src/include/nodes/primnodes.h                 |  36 +
 src/include/parser/parse_node.h               |   2 +
 src/test/regress/expected/cast.out            | 791 ++++++++++++++++++
 src/test/regress/expected/create_cast.out     |   5 +
 src/test/regress/expected/opr_sanity.out      |  24 +-
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/cast.sql                 | 340 ++++++++
 src/test/regress/sql/create_cast.sql          |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 31 files changed, 2157 insertions(+), 194 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

+--error safe type cast

+--error safe type cast
index 2fc63442980..ccd1d256003 100644
index 34c83880a66..9b2482e9b99 100644
 
    <para>

 
index b302be36f73..1de0d465f56 100644

 

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4782,6 +4794,53 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+		/* Steps to evaluate the DEFAULT expression */
index 67f4e00eac4..f1d8df1ac41 100644
@@ -1926,6 +1927,28 @@ 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))
+			{
+				*op->resvalue = (Datum) 0;
+				*op->resnull = true;
+
+				/*
+				 * Reset for next use such as for catching errors when coercing
+				 * a expression.
+				 */
+				stcstate->escontext.error_occurred = false;
+				stcstate->escontext.details_wanted = false;
+
+				EEO_NEXT();
+			}
+			else
+				EEO_JUMP(stcstate->jump_end);
+		}
+
 		EEO_CASE(EEOP_JSONEXPR_PATH)
 		{
 			/* too complex for an inline implementation */
@@ -3644,6 +3667,12 @@ 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;
+	}
 }
 
 /*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index ac88881e995..bc7cd3283a5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,32 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
+						 * Reset for next use such as for catching errors when
+						 * coercing a expression.
+						/* set resnull to true */
+						LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+						/* reset resvalue */
+						LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+						LLVMBuildBr(b, opblocks[opno + 1]);
index 32af0df6c70..b6a9dc2f5fd 100644
@@ -2951,6 +2951,32 @@ eval_const_expressions_mutator(Node *node,

index c3a0a354a9c..8a27e045bc0 100644
@@ -16013,6 +16013,28 @@ func_expr_common_subexpr:
index b8340557b34..de77f6e9302 100644
@@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
index 32d6ae918ca..ec43fcd18fb 100644

 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
 
 	/*

-		if (coerce_hard)
@@ -2150,12 +2216,74 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
+					 * pre-evaluate simple constant cast expressions in a way
+					 * that tolerate errors.
+					result = (Node *) evaluate_expr_extended((Expr *) newexpr,
+															 coerce_type_coll,
+															 true);
@@ -2743,7 +2871,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
@@ -2780,6 +2909,271 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	return result;
 }
 
+/*
+				 * pre-evaluate simple constant cast expressions early in a way
+				 * that tolerate errors.
+				result = (Node *) evaluate_expr_extended((Expr *) newexpr,
+														 typeColl,
+														 true);
+					 * source expression can not coerce to the target type.
 
+		/* Exit early if the evaluation fails */
+		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 556ab057e5a..3e41c7b8188 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;

 

@@ -302,57 +302,57 @@

 
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 /* ----------------
index 75366203706..2f8d163a13e 100644
 
+		/* for EEOP_SAFETYPE_CAST */
+		struct
+		{
+			struct SafeTypeCastState *stcstate;	/* original expression node */
+		}			stcexpr;
+
 		/* for EEOP_JSONEXPR_PATH */
 		struct
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18ae8f0d4bb..9bd4cecad63 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
 	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;
 
+typedef struct SafeTypeCastState
+{
+	SafeTypeCastExpr *stcexpr;
+
+	/*
+	 * Address to jump to when skipping all the steps to evaulate the default
+	 * expression.
+	 */
+	int 		jump_end;
+
+	/*
+	 * For error-safe evaluation of coercions. A pointer to this is passed to
+	 * ExecInitExprRec() when initializing the coercion expressions, see
+	 * ExecInitSafeTypeCastExpr.
+	 *
+	 * Reset for 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 d14294a4ece..4b2eb340f97 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,17 @@ typedef struct TypeCast
 	ParseLoc	location;		/* token location, or -1 if unknown */
 } TypeCast;
 
+/*
 
+/*
+ * SafeTypeCastExpr -
index 00000000000..cb40ff0f200
@@ -0,0 +1,791 @@
+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 c.*

 SELECT c.*
 
index f56482fb9f1..2e086cbc7af 100644
 
 # ----------
index 00000000000..905d1f318b5
@@ -0,0 +1,340 @@
+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);

 -- check dependencies generated for that
 SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 57a8f0366a5..9bbb1e560c7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2668,6 +2668,9 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

From e4a1a85b15947357ed53ec3382f4ce821ad6257c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v11 16/20] error safe for casting timestamp to other types per
 pg_cast

 

 
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 9265c7f0533b8f70cbe4eaf452108e3ab69e873a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v11 17/20] error safe for casting jsonb to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
 1 file changed, 58 insertions(+), 16 deletions(-)

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 9399cdb491a..ab24d6dc813 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2006,7 +2006,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
  * Emit correct, translatable cast error message
  */
 static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
 {
 	static const struct
 	{
@@ -2027,7 +2027,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
 
+			ereturn(escontext,,
 
@@ -2042,7 +2042,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	JsonbValue	v;

 	if (v.type == jbvNull)
 	{
@@ -2051,7 +2054,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);
 
@@ -2066,7 +2072,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	Numeric		retValue;

 	if (v.type == jbvNull)
 	{
@@ -2075,7 +2084,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	}

 	/*
 	 * v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2096,7 +2108,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -2105,7 +2120,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int2,
 								   NumericGetDatum(v.val.numeric));
@@ -2123,7 +2141,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -2132,7 +2153,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int4,
 								   NumericGetDatum(v.val.numeric));
@@ -2150,7 +2174,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -2159,7 +2186,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int8,
 								   NumericGetDatum(v.val.numeric));
@@ -2177,7 +2207,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -2186,7 +2219,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_float4,
 								   NumericGetDatum(v.val.numeric));
@@ -2204,7 +2240,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -2213,7 +2252,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	}

From 1217392db3918ca941e6734b5855f7cf130451cc Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 17:03:45 +0800
Subject: [PATCH v11 19/20] invent some error safe functions

evaluate_expr_extended: If you wish to evaluate a constant expression in a
manner that is safe from potential errors, set the error_safe parameter to true

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/executor/execExpr.c      | 41 +++++++++++++
 src/backend/optimizer/util/clauses.c | 21 ++++++-
 src/backend/parser/parse_coerce.c    | 92 ++++++++++++++++++++++++++++
 src/backend/parser/parse_type.c      | 14 +++++
 src/backend/utils/fmgr/fmgr.c        | 13 ++++
 src/include/executor/executor.h      |  1 +
 src/include/fmgr.h                   |  3 +
 src/include/optimizer/optimizer.h    |  3 +
 src/include/parser/parse_coerce.h    |  4 ++
 src/include/parser/parse_type.h      |  2 +
 10 files changed, 193 insertions(+), 1 deletion(-)

+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+	ExprState  *state;
+	ExprEvalStep scratch = {0};
+
+	/* Special case: NULL expression produces a NULL ExprState pointer */
+	if (node == NULL)
+		return NULL;
+
+	/* Initialize ExprState with empty step list */
+	state = makeNode(ExprState);
+	state->expr = node;
+	state->parent = parent;
+	state->ext_params = NULL;
+	state->escontext = makeNode(ErrorSaveContext);
+	state->escontext->type = T_ErrorSaveContext;
+	state->escontext->error_occurred = false;
+	state->escontext->details_wanted = false;
+
+	/* Insert setup steps as needed */
+	ExecCreateExprSetupSteps(state, (Node *) node);
+
+	/* Compile the expression proper */
+	ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+	/* Finally, append a DONE step */
+	scratch.opcode = EEOP_DONE_RETURN;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecReadyExpr(state);
+
+	return state;
+}
+
 /*
  * ExecInitExprWithParams: prepare a standalone expression tree for execution
  *
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 202ba8ed4bb..32af0df6c70 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -5081,6 +5081,16 @@ sql_inline_error_callback(void *arg)
 Expr *
 evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 			  Oid result_collation)
+{
+	return evaluate_expr_extended(expr,
+								  result_type,
+								  result_typmod,
+								  result_collation,
+								  false);
+}
+Expr *
+evaluate_expr_extended(Expr *expr, Oid result_type, int32 result_typmod,
+					   Oid result_collation, bool error_safe)
 {
 	EState	   *estate;
 	ExprState  *exprstate;
@@ -5105,7 +5115,10 @@ 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);
+	if (error_safe)
+		exprstate = ExecInitExprSafe(expr, NULL);
+	else
+		exprstate = ExecInitExpr(expr, NULL);
 
 	/*
 	 * And evaluate it.
@@ -5125,6 +5138,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);
 
+	if (error_safe && SOFT_ERROR_OCCURRED(exprstate->escontext))
+	{
+		FreeExecutorState(estate);
+		return NULL;
+	}
+
 	/*
 	 * Must copy result out of sub-context used by expression eval.
 	 *
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..cdcdd44a799 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -657,6 +657,98 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
 	return true;
 }
 
+/*
 
 /*

+bool
index d0aa8ab0c1c..97a0337452c 100644
@@ -144,6 +144,9 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
 
 extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 						   Oid result_collation);
+extern Expr *evaluate_expr_extended(Expr *expr, Oid result_type,
+									int32 result_typmod, Oid result_collation,
+									bool error_safe);

 

From 5e44ba2f72027bf219412e90b2e8abb72326b5c2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v11 12/20] error safe for casting float8 to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/float.c   | 13 +++++++++----
 src/backend/utils/adt/int8.c    |  2 +-
 src/backend/utils/adt/numeric.c |  3 ++-
 3 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index ab5c38d723d..d746f961fd3 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,14 @@ dtof(PG_FUNCTION_ARGS)
 
 	result = (float4) num;
 
 	PG_RETURN_FLOAT4(result);
 }
@@ -1224,7 +1229,7 @@ dtoi4(PG_FUNCTION_ARGS)
 
 	/* Range check */
 
@@ -1249,7 +1254,7 @@ dtoi2(PG_FUNCTION_ARGS)
 
 	/* Range check */
 
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 8f7c117557a..70e6d89813c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1260,7 +1260,7 @@ dtoi8(PG_FUNCTION_ARGS)
 
 	/* Range check */
 
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b89c35eee65..507c607a799 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4560,7 +4560,8 @@ float8_numeric(PG_FUNCTION_ARGS)
 	init_var(&result);

 
-- 
2.34.1

From fd2c819197756e839eac39198960bea645d931f7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v11 15/20] error safe for casting timestamptz to other types
 per pg_cast

 

 

 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 1570b865c1f6f745cbadc6b5a52b5779237bb830 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v11 14/20] error safe for casting interval to other types per
 pg_cast

 

 

From 10602bb81b53791ce355c732293a42be47be2446 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v11 13/20] error safe for casting date to other types per
 pg_cast

 

 

From 6c396ee467a6237134494000c3227f96fc301ee0 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v11 11/20] error safe for casting float4 to other types per
 pg_cast

 3 files changed, 5 insertions(+), 4 deletions(-)

 	/* Range check */

 	/* Range check */
 
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 3dcbd36bd8e..8f7c117557a 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1295,7 +1295,7 @@ ftoi8(PG_FUNCTION_ARGS)
 
 	/* Range check */
 
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 40d2709b64a..b89c35eee65 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4658,7 +4658,8 @@ float4_numeric(PG_FUNCTION_ARGS)
 	init_var(&result);

 
-- 
2.34.1

From 11996d3917e97543cd89393060af0175c8748670 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v11 09/20] error safe for casting bigint to other types per
 pg_cast

diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bdea490202a..3dcbd36bd8e 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1204,7 +1204,7 @@ int84(PG_FUNCTION_ARGS)
 	int64		arg = PG_GETARG_INT64(0);

@@ -1225,7 +1225,7 @@ int82(PG_FUNCTION_ARGS)
 	int64		arg = PG_GETARG_INT64(0);

@@ -1308,7 +1308,7 @@ i8tooid(PG_FUNCTION_ARGS)
 	int64		arg = PG_GETARG_INT64(0);

-- 
2.34.1

From 4741646c72d5bb8dc9d9c43989173b721d501296 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v11 10/20] error safe for casting numeric to other types per
 pg_cast

discussion: https://postgr.es/m/CACJufxHCMzrHOW=[email-masked]
---
 src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
 1 file changed, 43 insertions(+), 15 deletions(-)

index 2501007d981..40d2709b64a 100644

 
 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

@@ -4489,11 +4506,11 @@ numeric_int2(PG_FUNCTION_ARGS)
 	if (NUMERIC_IS_SPECIAL(num))
 	{
 		if (NUMERIC_IS_NAN(num))
-			ereport(ERROR,
+			ereturn(fcinfo->context, (Datum) 0,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert NaN to %s", "smallint")));
 		else
-			ereport(ERROR,
+			ereturn(fcinfo->context, (Datum) 0,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert infinity to %s", "smallint")));
 	}
@@ -4502,12 +4519,12 @@ numeric_int2(PG_FUNCTION_ARGS)
 	init_var_from_num(num, &x);

 
@@ -4572,10 +4589,14 @@ numeric_float8(PG_FUNCTION_ARGS)

 	PG_RETURN_DATUM(result);
 }
@@ -4667,7 +4688,14 @@ numeric_float4(PG_FUNCTION_ARGS)
 	tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
 											  NumericGetDatum(num)));

 	pfree(tmp);
 
-- 
2.34.1

From 53a534b26296beafb2a8c5edd8f3aa5c9ab5cd32 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v11 07/20] error safe for casting macaddr8 to other types per
 pg_cast

 

From 56ea6746c8c4b11af8ad8342c999f040fc911050 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v11 08/20] error safe for casting integer to other types per
 pg_cast

 

 
-- 
2.34.1

From 723e4abfa7837b1450c29d422f6e0b89831e485b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v11 05/20] error safe for casting character varying to other
 types per pg_cast

From bf710ed00ac486da8bed9918ececd839c81c61b9 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v11 06/20] error safe for casting inet to other types per
 pg_cast

diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..648c8d95f51 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS)

-- 
2.34.1

From ba57d49d0c3441a9c7729e6a2c40c98f6c96fdc5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 13:45:00 +0800
Subject: [PATCH v11 04/20] error safe for casting text to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
 src/backend/utils/adt/regproc.c | 13 ++++++--
 src/backend/utils/adt/varlena.c | 10 ++++--
 src/include/catalog/namespace.h |  6 ++++
 src/include/utils/varlena.h     |  1 +
 5 files changed, 72 insertions(+), 16 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..bef2de5dd39 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
 RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 						 uint32 flags,
 						 RangeVarGetRelidCallback callback, void *callback_arg)
+{
+	return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+										callback, callback_arg,
+										NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+							 RangeVarGetRelidCallback callback, void *callback_arg,
+							 Node *escontext)
 {
 	uint64		inval_count;
 	Oid			relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	if (relation->catalogname)
 	{
 		if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
-			ereport(ERROR,
+			ereturn(escontext, InvalidOid,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
 							relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 					 * return InvalidOid.
 					 */
 					if (namespaceId != myTempNamespace)
-						ereport(ERROR,
+						ereturn(escontext, InvalidOid,
 								(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 								 errmsg("temporary tables cannot specify a schema name")));
 				}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 		{
 			int			elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
 
-			if (relation->schemaname)
-				ereport(elevel,
+			if (relation->schemaname && elevel == DEBUG1)
+				ereport(DEBUG1,
 						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
 						 errmsg("could not obtain lock on relation \"%s.%s\"",
 								relation->schemaname, relation->relname)));
-			else
-				ereport(elevel,
+			else if (relation->schemaname && elevel == ERROR)
+				ereturn(escontext, InvalidOid,
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s.%s\"",
+							   relation->schemaname, relation->relname));
+			else if (elevel == DEBUG1)
+				ereport(DEBUG1,
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s\"",
+							   relation->relname));
+			else if (elevel == ERROR)
+				ereturn(escontext, InvalidOid,
 						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
 						 errmsg("could not obtain lock on relation \"%s\"",
 								relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	{
 		int			elevel = missing_ok ? DEBUG1 : ERROR;
 
-		if (relation->schemaname)
-			ereport(elevel,
+		if (relation->schemaname && elevel == DEBUG1)
+			ereport(DEBUG1,
 					(errcode(ERRCODE_UNDEFINED_TABLE),
 					 errmsg("relation \"%s.%s\" does not exist",
 							relation->schemaname, relation->relname)));
-		else
-			ereport(elevel,
+		else if (relation->schemaname && elevel == ERROR)
+			ereturn(escontext, InvalidOid,
+					errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s.%s\" does not exist",
+						   relation->schemaname, relation->relname));
+		else if (elevel == DEBUG1)
+			ereport(DEBUG1,
+					errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s\" does not exist",
+						   relation->relname));
+		else if (elevel == ERROR)
+			ereturn(escontext, InvalidOid,
 					(errcode(ERRCODE_UNDEFINED_TABLE),
 					 errmsg("relation \"%s\" does not exist",
 							relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
  */
 RangeVar *
 makeRangeVarFromNameList(const List *names)
+{
+	return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
 {
 	RangeVar   *rel = makeRangeVar(NULL, NULL, -1);
 
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
 			rel->relname = strVal(lthird(names));
 			break;
 		default:
-			ereport(ERROR,
+			ereturn(escontext, NULL,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 					 errmsg("improper relation name (too many dotted names): %s",
 							NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..59cc508f805 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
 	text	   *relname = PG_GETARG_TEXT_PP(0);
 	Oid			result;
 	RangeVar   *rv;
+	List	   *namelist;
 
-	rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+	namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		PG_RETURN_NULL();
+
+	rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		PG_RETURN_NULL();
 
 	/* We might not even have permissions on this relation; don't lock it. */
-	result = RangeVarGetRelid(rv, NoLock, false);
-
+	result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+										  fcinfo->context);
 	PG_RETURN_OID(result);
 }
 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 3894457ab40..f8becbcde51 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2684,6 +2684,12 @@ name_text(PG_FUNCTION_ARGS)
  */
 List *
 textToQualifiedNameList(text *textval)
+{
+	return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
 {
 	char	   *rawname;
 	List	   *result = NIL;
@@ -2695,12 +2701,12 @@ textToQualifiedNameList(text *textval)
 	rawname = text_to_cstring(textval);
 
 	if (!SplitIdentifierString(rawname, '.', &namelist))
-		ereport(ERROR,
+		ereturn(escontext, NIL,
 				(errcode(ERRCODE_INVALID_NAME),
 				 errmsg("invalid name syntax")));
 
 	if (namelist == NIL)
-		ereport(ERROR,
+		ereturn(escontext, NIL,
 				(errcode(ERRCODE_INVALID_NAME),
 				 errmsg("invalid name syntax")));

From 07da6941a7cf2aa47622e695d582a238e452eb09 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v11 02/20] error safe for casting bit/varbit to other types
 per pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/varbit.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..6e9b808e20a 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS)
 		PG_RETURN_VARBIT_P(arg);
 
 	if (!isExplicit)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
 				 errmsg("bit string length %d does not match type bit(%d)",
 						VARBITLEN(arg), len)));
@@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS)
 		PG_RETURN_VARBIT_P(arg);
 
 	if (!isExplicit)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
 				 errmsg("bit string too long for type bit varying(%d)",
 						len)));
@@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS)

@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)

-- 
2.34.1

From ef026d46791c7371cfb31e16d985bafbc25897d2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v11 03/20] error safe for casting character to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/executor/execExprInterp.c |  2 +-
 src/backend/utils/adt/varchar.c       |  2 +-
 src/backend/utils/adt/xml.c           | 18 ++++++++++++------
 src/include/utils/xml.h               |  2 +-
 4 files changed, 15 insertions(+), 9 deletions(-)

index 41e775570ec..9e8016456ce 100644

@@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr,

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;
+	ereturn(escontext, NULL
+			errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("unsupported XML feature"),
+			errdetail("This functionality requires the server to be built with libxml support."));
 #endif
 }

From fda9e35fff9e4f81baaa5508ded48671441b08d3 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v11 01/20] error safe for casting bytea to other types per
 pg_cast

 

 

-- 
2.34.1

---

From: jian he <[email-masked]>
Date: Tue, 25 Nov 2025 20:27:52 +0800

On Fri, Nov 21, 2025 at 3:32 PM Corey Huinker <[email-masked]> wrote:
>> I'm fine with it. I can see having 'f' and 's' both mean cast functions, but 's' means safe, but the extra boolean works too and we'll be fine with either method.
>
>

Do you mean change pg_cast.casterrorsafe from boolean to char?
Currently pg_cast.casterrorsafe works just fine.
if the cast function is not applicable, the castfunc would be InvalidOid.
also the cast function is either error safe or not, I don't see a
usage case for the third value.

attached v12-0004 fixes the xmlparse error, see
https://cirrus-ci.com/task/6181359384788992?logs=build#L1217.

all other patches remain the same as v11.

--
jian
https://www.enterprisedb.com/
From 6803dd3fcf06de55544d6e62baa48ad2c7c18d05 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 17:03:45 +0800
Subject: [PATCH v12 19/20] invent some error safe functions

 
+/*
 
 	/*
 	 * And evaluate it.

+/*
 
 /*

+bool

 

From e7032cad38de580b0f6487d1c3be2e6fe817268c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 16:38:23 +0800
Subject: [PATCH v12 18/20] error safe for casting geometry data type

 
-	return HYPOT(dx, dy);
 
 /*

 
-	box_cn(result, box);

+static bool

 
 /* line_interpt()
 
+static inline float8

 

 Datum

 

 

 

 
 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()

 

 
 	Assert(poly->npts > 0);

 
 
 Datum

 

 	return result;
 }
 
-	return HYPOT(dx, dy);

 

 

 
 #endif							/* GEO_DECLS_H */
-- 
2.34.1

From bafd37051cf8a39e2fa5f5c4bc81ae94d5f951e0 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v12 17/20] error safe for casting jsonb to other types per
 pg_cast

 

 
 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From 30fb4299bcc66aa90755fbac42e43d6d8a66b642 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v12 15/20] error safe for casting timestamptz to other types
 per pg_cast

 

 

 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 675190ed0721396503fa3a79d562c72066c9464d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v12 16/20] error safe for casting timestamp to other types per
 pg_cast

 

 
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 61ef3a3c9bd59a83c893fcdb51d0865e43c1fe3a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v12 14/20] error safe for casting interval to other types per
 pg_cast

 

 

From 35664fc4f1aafd5f0058bc796cb83ce9d37c2881 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v12 12/20] error safe for casting float8 to other types per
 pg_cast

 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From 819f444d9cf10248c4a29fc89aab9aa62913ebbb Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v12 13/20] error safe for casting date to other types per
 pg_cast

 

 

From 2d5488319fb708212e8d44a4226c013ad8bf2401 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v12 11/20] error safe for casting float4 to other types per
 pg_cast

 
 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From a4c0b5c782ead8aa18301657bd070b9df0354f2e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v12 10/20] error safe for casting numeric to other types per
 pg_cast

 

 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

 
 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 67e46c5a4ec3964e66b186ead03edcd046b03c68 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v12 09/20] error safe for casting bigint to other types per
 pg_cast

 

 

-- 
2.34.1

From 2a3fce700e11e1a9bf0eb9f7fa5c48e0d4ec10d6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v12 08/20] error safe for casting integer to other types per
 pg_cast

 

 
-- 
2.34.1

From 7db397a55d6ff5e1b96176b704556649b86630c2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v12 07/20] error safe for casting macaddr8 to other types per
 pg_cast

 

From 4ec193ef249c61a71f0ecb7c7601e73399f9aaf9 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v12 06/20] error safe for casting inet to other types per
 pg_cast

 
 
-- 
2.34.1

From 5a31cec8bce660f0f323a7842c27330cb4cb7d60 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v12 05/20] error safe for casting character varying to other
 types per pg_cast

From d81dc3b37242eaa9e8e4a018343c15e4753c8e91 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v12 03/20] error safe for casting character to other types per
 pg_cast

 

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;

From c35febdcc1b540e80cd6b3d779498cbac7de0f99 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v12 02/20] error safe for casting bit/varbit to other types
 per pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
 
 	if (!isExplicit)
-		ereport(ERROR,

 
 
-- 
2.34.1

From 0120c7f31e3b37247ecdc9945fbba850856d626e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 25 Nov 2025 20:07:28 +0800
Subject: [PATCH v12 04/20] error safe for casting character to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
 src/backend/utils/adt/regproc.c | 13 ++++++--
 src/backend/utils/adt/varlena.c | 10 ++++--
 src/backend/utils/adt/xml.c     |  2 +-
 src/include/catalog/namespace.h |  6 ++++
 src/include/utils/varlena.h     |  1 +
 6 files changed, 73 insertions(+), 17 deletions(-)

 

 

 
 	if (namelist == NIL)
-		ereport(ERROR,
 
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 9e8016456ce..8de1f1fc741 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node
 
 	return (xmltype *) data;
 #else
-	ereturn(escontext, NULL
 			errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			errmsg("unsupported XML feature"),
 			errdetail("This functionality requires the server to be built with libxml support."));

From 60228e5c831f170b122da208641061e5ae07b06b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 25 Nov 2025 10:37:33 +0800
Subject: [PATCH v12 20/20] CAST(expr AS newtype DEFAULT ON ERROR)

demo:

 
+--error safe type cast

+--error safe type cast
 
    <para>

 

 

 
+/*

 /*

 
 
 			break;

 
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
 
 	/*

-		if (coerce_hard)
 
+/*

 

 
@@ -302,57 +302,57 @@

 
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 /* ----------------

 
+/*
 
+/*
+ * SafeTypeCastExpr -

 
 
 SELECT c.*

 SELECT c.*

 # ----------

From 5875ac4fdbc6393286aeabaa293a192524400eeb Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v12 01/20] error safe for casting bytea to other types per
 pg_cast

 

 

-- 
2.34.1

---

From: Corey Huinker <[email-masked]>
Date: Sun, 30 Nov 2025 23:41:37 -0600

>
> >> I'm fine with it. I can see having 'f' and 's' both mean cast
> functions, but 's' means safe, but the extra boolean works too and we'll be
> fine with either method.
> >
> >

No, I meant implementing the syntax for being able to declare a custom CAST
function as safe (or not). Basically adding the [SAFE] to

CREATE CAST (*source_type* AS *target_type*)
    WITH [SAFE] FUNCTION *function_name* [ (*argument_type* [, ...]) ]

I'm not tied to this syntax choice, but this one seemed the most obvious
and least invasive.

But this brings up an interesting point: if a cast is declared as WITHOUT
FUNCTION aka COERCION_METHOD_BINARY, then the cast can never fail, and we
should probably check for that because a cast that cannot fail can ignore
the DEFAULT clause altogether and fall back to being an ordinary CAST().

> usage case for the third value.

Agreed, it's fine. A committer may want a char with 's'/'u' values to keep
all the options char types, but even if they do that's a very minor change.

---

From: Amul Sul <[email-masked]>
Date: Mon, 1 Dec 2025 17:38:24 +0530

> float8_div_safe together.

Since you declared float8_div_safe() as static inline, I believe it
wouldn't have any performance degradation since most compilers
optimize it. Also, I suggest you pass the ErrorSafeContext to
float_overflow_error(), float_underflow_error(), and
float_zero_divide_error() so that you can avoid duplicating error
messages.

Regards,
Amul

---

From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 15:52:57 +0800

hi.

First I want to use ereturn, then I found out
float_overflow_error, float_underflow_error, float_zero_divide_error
used both in float4, float8.
ereturn would not be appropriate for both types.
so I choose errsave.
for these 3 functions, now it looks like:

pg_noinline void
float_overflow_error(struct Node *escontext)
{
    errsave(escontext,
            (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
             errmsg("value out of range: overflow")));
}

pg_noinline void
float_underflow_error(struct Node *escontext)
{
    errsave(escontext,
            (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
             errmsg("value out of range: underflow")));
}

pg_noinline void
float_zero_divide_error(struct Node *escontext)
{
    errsave(escontext,
            (errcode(ERRCODE_DIVISION_BY_ZERO),
             errmsg("division by zero")));
}

--
jian
https://www.enterprisedb.com/
From 494a2c19a623104ac7e5d1bcc7d4f4fb57b178e1 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 15:25:29 +0800
Subject: [PATCH v13 18/20] error safe for casting geometry data type

already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
patch make these functions error safe: lseg_center, box_center, box_circle, poly_center, poly_circle
circle_box

can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=[email-masked]
---
 contrib/btree_gist/btree_float4.c |   2 +-
 contrib/btree_gist/btree_float8.c |   4 +-
 src/backend/utils/adt/float.c     | 104 +++++++--------
 src/backend/utils/adt/geo_ops.c   | 205 ++++++++++++++++++++++++++----
 src/include/utils/float.h         | 112 +++++++++++-----
 5 files changed, 313 insertions(+), 114 deletions(-)

diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index d9c859835da..a7325a7bb29 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -101,7 +101,7 @@ float4_dist(PG_FUNCTION_ARGS)
 
 	r = a - b;
 	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT4(fabsf(r));
 }
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 567beede178..7c99b84de35 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -79,7 +79,7 @@ gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo)
 
 	r = arg1 - arg2;
 	if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2))
-		float_overflow_error();
+		float_overflow_error(NULL);
 	return fabs(r);
 }
 
@@ -109,7 +109,7 @@ float8_dist(PG_FUNCTION_ARGS)
 
 	r = a - b;
 	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(fabs(r));
 }
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index d746f961fd3..4a7623b1456 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -83,25 +83,25 @@ static void init_degree_constants(void);
  * This does mean that you don't get a useful error location indicator.
  */
 pg_noinline void
-float_overflow_error(void)
+float_overflow_error(struct Node *escontext)
 {
-	ereport(ERROR,
+	errsave(escontext,
 			(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 			 errmsg("value out of range: overflow")));
 }
 
 pg_noinline void
-float_underflow_error(void)
+float_underflow_error(struct Node *escontext)
 {
-	ereport(ERROR,
+	errsave(escontext,
 			(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 			 errmsg("value out of range: underflow")));
 }
 
 pg_noinline void
-float_zero_divide_error(void)
+float_zero_divide_error(struct Node *escontext)
 {
-	ereport(ERROR,
+	errsave(escontext,
 			(errcode(ERRCODE_DIVISION_BY_ZERO),
 			 errmsg("division by zero")));
 }
@@ -1460,9 +1460,9 @@ dsqrt(PG_FUNCTION_ARGS)
 
 	result = sqrt(arg1);
 	if (unlikely(isinf(result)) && !isinf(arg1))
-		float_overflow_error();
+		float_overflow_error(NULL);
 	if (unlikely(result == 0.0) && arg1 != 0.0)
-		float_underflow_error();
+		float_underflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1479,9 +1479,9 @@ dcbrt(PG_FUNCTION_ARGS)
 
 	result = cbrt(arg1);
 	if (unlikely(isinf(result)) && !isinf(arg1))
-		float_overflow_error();
+		float_overflow_error(NULL);
 	if (unlikely(result == 0.0) && arg1 != 0.0)
-		float_underflow_error();
+		float_underflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1617,24 +1617,24 @@ dpow(PG_FUNCTION_ARGS)
 				if (absx == 1.0)
 					result = 1.0;
 				else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0))
-					float_overflow_error();
+					float_overflow_error(NULL);
 				else
-					float_underflow_error();
+					float_underflow_error(NULL);
 			}
 		}
 		else if (errno == ERANGE)
 		{
 			if (result != 0.0)
-				float_overflow_error();
+				float_overflow_error(NULL);
 			else
-				float_underflow_error();
+				float_underflow_error(NULL);
 		}
 		else
 		{
 			if (unlikely(isinf(result)))
-				float_overflow_error();
+				float_overflow_error(NULL);
 			if (unlikely(result == 0.0) && arg1 != 0.0)
-				float_underflow_error();
+				float_underflow_error(NULL);
 		}
 	}
 
@@ -1674,14 +1674,14 @@ dexp(PG_FUNCTION_ARGS)
 		if (unlikely(errno == ERANGE))
 		{
 			if (result != 0.0)
-				float_overflow_error();
+				float_overflow_error(NULL);
 			else
-				float_underflow_error();
+				float_underflow_error(NULL);
 		}
 		else if (unlikely(isinf(result)))
-			float_overflow_error();
+			float_overflow_error(NULL);
 		else if (unlikely(result == 0.0))
-			float_underflow_error();
+			float_underflow_error(NULL);
 	}
 
 	PG_RETURN_FLOAT8(result);
@@ -1712,9 +1712,9 @@ dlog1(PG_FUNCTION_ARGS)
 
 	result = log(arg1);
 	if (unlikely(isinf(result)) && !isinf(arg1))
-		float_overflow_error();
+		float_overflow_error(NULL);
 	if (unlikely(result == 0.0) && arg1 != 1.0)
-		float_underflow_error();
+		float_underflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1745,9 +1745,9 @@ dlog10(PG_FUNCTION_ARGS)
 
 	result = log10(arg1);
 	if (unlikely(isinf(result)) && !isinf(arg1))
-		float_overflow_error();
+		float_overflow_error(NULL);
 	if (unlikely(result == 0.0) && arg1 != 1.0)
-		float_underflow_error();
+		float_underflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1778,7 +1778,7 @@ dacos(PG_FUNCTION_ARGS)
 
 	result = acos(arg1);
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1809,7 +1809,7 @@ dasin(PG_FUNCTION_ARGS)
 
 	result = asin(arg1);
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1835,7 +1835,7 @@ datan(PG_FUNCTION_ARGS)
 	 */
 	result = atan(arg1);
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1861,7 +1861,7 @@ datan2(PG_FUNCTION_ARGS)
 	 */
 	result = atan2(arg1, arg2);
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1902,7 +1902,7 @@ dcos(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1957,7 +1957,7 @@ dsin(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2137,7 +2137,7 @@ dacosd(PG_FUNCTION_ARGS)
 		result = 90.0 + asind_q1(-arg1);
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2174,7 +2174,7 @@ dasind(PG_FUNCTION_ARGS)
 		result = -asind_q1(-arg1);
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2206,7 +2206,7 @@ datand(PG_FUNCTION_ARGS)
 	result = (atan_arg1 / atan_1_0) * 45.0;
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2242,7 +2242,7 @@ datan2d(PG_FUNCTION_ARGS)
 	result = (atan2_arg1_arg2 / atan_1_0) * 45.0;
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2365,7 +2365,7 @@ dcosd(PG_FUNCTION_ARGS)
 	result = sign * cosd_q1(arg1);
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2487,7 +2487,7 @@ dsind(PG_FUNCTION_ARGS)
 	result = sign * sind_q1(arg1);
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2645,7 +2645,7 @@ dcosh(PG_FUNCTION_ARGS)
 		result = get_float8_infinity();
 
 	if (unlikely(result == 0.0))
-		float_underflow_error();
+		float_underflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2665,7 +2665,7 @@ dtanh(PG_FUNCTION_ARGS)
 	result = tanh(arg1);
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2765,7 +2765,7 @@ derf(PG_FUNCTION_ARGS)
 	result = erf(arg1);
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2785,7 +2785,7 @@ derfc(PG_FUNCTION_ARGS)
 	result = erfc(arg1);
 
 	if (unlikely(isinf(result)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2814,7 +2814,7 @@ dgamma(PG_FUNCTION_ARGS)
 		/* Per POSIX, an input of -Inf causes a domain error */
 		if (arg1 < 0)
 		{
-			float_overflow_error();
+			float_overflow_error(NULL);
 			result = get_float8_nan();	/* keep compiler quiet */
 		}
 		else
@@ -2836,12 +2836,12 @@ dgamma(PG_FUNCTION_ARGS)
 		if (errno != 0 || isinf(result) || isnan(result))
 		{
 			if (result != 0.0)
-				float_overflow_error();
+				float_overflow_error(NULL);
 			else
-				float_underflow_error();
+				float_underflow_error(NULL);
 		}
 		else if (result == 0.0)
-			float_underflow_error();
+			float_underflow_error(NULL);
 	}
 
 	PG_RETURN_FLOAT8(result);
@@ -2873,7 +2873,7 @@ dlgamma(PG_FUNCTION_ARGS)
 	 * to report overflow, but it should never underflow.
 	 */
 	if (errno == ERANGE || (isinf(result) && !isinf(arg1)))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -3013,7 +3013,7 @@ float8_combine(PG_FUNCTION_ARGS)
 		tmp = Sx1 / N1 - Sx2 / N2;
 		Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N;
 		if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
-			float_overflow_error();
+			float_overflow_error(NULL);
 	}
 
 	/*
@@ -3080,7 +3080,7 @@ float8_accum(PG_FUNCTION_ARGS)
 		if (isinf(Sx) || isinf(Sxx))
 		{
 			if (!isinf(transvalues[1]) && !isinf(newval))
-				float_overflow_error();
+				float_overflow_error(NULL);
 
 			Sxx = get_float8_nan();
 		}
@@ -3163,7 +3163,7 @@ float4_accum(PG_FUNCTION_ARGS)
 		if (isinf(Sx) || isinf(Sxx))
 		{
 			if (!isinf(transvalues[1]) && !isinf(newval))
-				float_overflow_error();
+				float_overflow_error(NULL);
 
 			Sxx = get_float8_nan();
 		}
@@ -3393,7 +3393,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
 				(isinf(Sxy) &&
 				 !isinf(transvalues[1]) && !isinf(newvalX) &&
 				 !isinf(transvalues[3]) && !isinf(newvalY)))
-				float_overflow_error();
+				float_overflow_error(NULL);
 
 			if (isinf(Sxx))
 				Sxx = get_float8_nan();
@@ -3545,15 +3545,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
 		tmp1 = Sx1 / N1 - Sx2 / N2;
 		Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N;
 		if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
-			float_overflow_error();
+			float_overflow_error(NULL);
 		Sy = float8_pl(Sy1, Sy2);
 		tmp2 = Sy1 / N1 - Sy2 / N2;
 		Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N;
 		if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2))
-			float_overflow_error();
+			float_overflow_error(NULL);
 		Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N;
 		if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2))
-			float_overflow_error();
+			float_overflow_error(NULL);
 	}
 
 	/*
index 9101a720744..0c792c17e83 100644

 
-	box_cn(result, box);

+static bool
 
 /*		box_wd	-		returns the width (length) of the box
  *								  (horizontal magnitude).
@@ -2004,6 +2032,23 @@ point_dt(Point *pt1, Point *pt2)
 	return hypot(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
 }
 
+static inline float8
+	return hypot(x, y);
@@ -2317,13 +2362,31 @@ lseg_center(PG_FUNCTION_ARGS)

 

@@ -4110,9 +4173,27 @@ construct_point(PG_FUNCTION_ARGS)
 static inline void
 
 Datum
@@ -4458,7 +4539,7 @@ path_poly(PG_FUNCTION_ARGS)

@@ -4508,7 +4589,9 @@ poly_center(PG_FUNCTION_ARGS)

 
 	PG_RETURN_POINT_P(result);
@@ -5191,14 +5274,30 @@ circle_box(PG_FUNCTION_ARGS)

 
 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()
@@ -5209,15 +5308,35 @@ box_circle(PG_FUNCTION_ARGS)
 {
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	CIRCLE	   *circle;
+	float8		x;
+	float8		y;

 

@@ -5281,10 +5400,11 @@ circle_poly(PG_FUNCTION_ARGS)
 
 	Assert(poly->npts > 0);
 
@@ -5293,14 +5413,42 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
 	result->radius = 0;

 
 Datum
@@ -5311,7 +5459,8 @@ poly_circle(PG_FUNCTION_ARGS)

 
 	PG_RETURN_CIRCLE_P(result);
 }
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..c8abc7b3d78 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -30,9 +30,9 @@ extern PGDLLIMPORT int extra_float_digits;
 /*
  * Utility functions in float.c
  */
-pg_noreturn extern void float_overflow_error(void);
-pg_noreturn extern void float_underflow_error(void);
-pg_noreturn extern void float_zero_divide_error(void);
+extern void float_overflow_error(struct Node *escontext);
+extern void float_underflow_error(struct Node *escontext);
+extern void float_zero_divide_error(struct Node *escontext);
 extern int	is_infinite(float8 val);
 extern float8 float8in_internal(char *num, char **endptr_p,
 								const char *type_name, const char *orig_string,
@@ -104,7 +104,22 @@ float4_pl(const float4 val1, const float4 val2)
 
 	result = val1 + val2;
 	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
+		float_overflow_error(NULL);
+
+	return result;
+}
+
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	result = val1 + val2;
+	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+	{
+		float_overflow_error(escontext);
+		return 0.0;
+	}
 
 	return result;
 }
@@ -112,15 +127,10 @@ float4_pl(const float4 val1, const float4 val2)
 static inline float8
 float8_pl(const float8 val1, const float8 val2)
 {
-	float8		result;
-
-	result = val1 + val2;
-	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
-
-	return result;
+	return float8_pl_safe(val1, val2, NULL);
 }
 
+
 static inline float4
 float4_mi(const float4 val1, const float4 val2)
 {
@@ -128,7 +138,22 @@ float4_mi(const float4 val1, const float4 val2)
 
 	result = val1 - val2;
 	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
+		float_overflow_error(NULL);
+
+	return result;
+}
+
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	result = val1 - val2;
+	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+	{
+		float_overflow_error(escontext);
+		return 0.0;
+	}
 
 	return result;
 }
@@ -136,15 +161,10 @@ float4_mi(const float4 val1, const float4 val2)
 static inline float8
 float8_mi(const float8 val1, const float8 val2)
 {
-	float8		result;
-
-	result = val1 - val2;
-	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
-
-	return result;
+	return float8_mi_safe(val1, val2, NULL);
 }
 
+
 static inline float4
 float4_mul(const float4 val1, const float4 val2)
 {
@@ -152,59 +172,89 @@ float4_mul(const float4 val1, const float4 val2)
 
 	result = val1 * val2;
 	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
+		float_overflow_error(NULL);
 	if (unlikely(result == 0.0f) && val1 != 0.0f && val2 != 0.0f)
-		float_underflow_error();
+		float_underflow_error(NULL);
 
 	return result;
 }
 
 static inline float8
-float8_mul(const float8 val1, const float8 val2)
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
 {
 	float8		result;
 
 	result = val1 * val2;
 	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
+	{
+		float_overflow_error(escontext);
+		return 0.0;
+	}
+
 	if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
-		float_underflow_error();
+	{
+		float_underflow_error(escontext);
+		return 0.0;
+	}
 
 	return result;
 }
 
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+	return float8_mul_safe(val1, val2, NULL);
+}
+
 static inline float4
 float4_div(const float4 val1, const float4 val2)
 {
 	float4		result;
 
 	if (unlikely(val2 == 0.0f) && !isnan(val1))
-		float_zero_divide_error();
+		float_zero_divide_error(NULL);
 	result = val1 / val2;
 	if (unlikely(isinf(result)) && !isinf(val1))
-		float_overflow_error();
+		float_overflow_error(NULL);
 	if (unlikely(result == 0.0f) && val1 != 0.0f && !isinf(val2))
-		float_underflow_error();
+		float_underflow_error(NULL);
 
 	return result;
 }
 
 static inline float8
-float8_div(const float8 val1, const float8 val2)
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
 {
 	float8		result;
 
 	if (unlikely(val2 == 0.0) && !isnan(val1))
-		float_zero_divide_error();
+	{
+		float_zero_divide_error(escontext);
+		return 0.0;
+	}
+
 	result = val1 / val2;
 	if (unlikely(isinf(result)) && !isinf(val1))
-		float_overflow_error();
+	{
+		float_overflow_error(escontext);
+		return 0.0;
+	}
+
 	if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
-		float_underflow_error();
+	{
+		float_underflow_error(escontext);
+		return 0.0;
+	}
 
 	return result;
 }
 
+static inline float8
+float8_div(const float8 val1, const float8 val2)
+{
+	return float8_div_safe(val1, val2, NULL);
+}
+
 /*
  * Routines for NaN-aware comparisons
  *
-- 
2.34.1

From 75671a9e79e2be40218812a9754b6efc8ad0cb06 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 15:47:42 +0800
Subject: [PATCH v13 20/20] CAST(expr AS newtype DEFAULT ON ERROR)

demo:

 src/backend/parser/parse_expr.c               | 417 ++++++++-
 31 files changed, 2150 insertions(+), 194 deletions(-)

+--error safe type cast

+--error safe type cast
 
    <para>

 

 

 
+/*

 /*
 
index d228318dc72..b3ae69068d0 100644
@@ -1550,6 +1561,9 @@ exprLocation(const Node *expr)
index e5dbd40cda2..04a8ca63459 100644
@@ -2958,6 +2958,32 @@ eval_const_expressions_mutator(Node *node,

 
 			break;
 
index 44fd1385f8c..57010d6434e 100644

 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
@@ -2011,16 +2024,22 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
+ * Most of the time, can_coerce will be NULL.  It is not NULL only when
+ * performing parse analysis for CAST(DEFAULT ... ON CONVERSION ERROR)
+ * expression. It's default to true. If coercing array elements fails, it will
+ * be set to false.
 
 	/*
@@ -2045,9 +2064,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  can_coerce);
 			/* we certainly have an array here */
-			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			Assert(can_coerce || array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
 		}
 		else
@@ -2088,6 +2108,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	}
 	else
 	{
+		/* Target type must valid for CAST DEFAULT */
+		Assert(can_coerce == NULL);
+
 		/* Can't handle an empty array without a target type */
 		if (newelems == NIL)
 			ereport(ERROR,
@@ -2125,6 +2148,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		coerce_hard = false;
 	}
 
@@ -2134,13 +2159,55 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
-		if (coerce_hard)
+			 * We have to to use CoerceUnknownConstSafe rather than
+			 * coerce_to_target_type. because coerce_to_target_type is not error
+			 * safe.
+			 * Can not coerce, just append the transformed element expression to
+			 * the list.
@@ -2150,12 +2217,74 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
+					 * Can not coerce, just append the transformed element
+					 * expression to the list.
+			else if (expr_is_const)
@@ -2743,7 +2872,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
@@ -2780,6 +2910,263 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	return result;
 }
 
+/*
+	/* Location of the coercion is the location of CAST symbol. */
+				 * pre-evaluate simple constant cast expressions in a way that
+				 * tolerate errors.
+					/* can not coerce, set can_coerce to false */
@@ -3193,6 +3580,8 @@ ParseExprKindName(ParseExprKind exprKind)
 
index 556ab057e5a..24820a739f3 100644
 
+				 * Here, we cannot deparsing cast_expr directly, since

 
 
@@ -302,57 +302,57 @@

 
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 /* ----------------
index 75366203706..937cbf3bc9b 100644
 
+		/* for EEOP_SAFETYPE_CAST */
+		struct
+		{
+			struct SafeTypeCastState *stcstate;
+		}			stcexpr;
+
 		/* for EEOP_JSONEXPR_PATH */
 		struct
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 64ff6996431..9018e190cc7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
 	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;

+/*
 
+/*
+ * SafeTypeCastExpr -

 
 
 SELECT c.*

 SELECT c.*
 
index cc6d799bcea..0b031a37c36 100644
 
 # ----------

 -- check dependencies generated for that
 SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cf3f6a7dafd..cf4bab63c89 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2672,6 +2672,9 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

From 91548362b43cac6ca621ca5471517b99644c132c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 17:03:45 +0800
Subject: [PATCH v13 19/20] invent some error safe functions

 
+/*
index bda4c4eb292..e5dbd40cda2 100644
@@ -5156,6 +5156,16 @@ sql_inline_error_callback(void *arg)
@@ -5180,7 +5190,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 
 	/*
 	 * And evaluate it.
@@ -5200,6 +5213,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);

+/*
 
 /*

+bool
index 44ec5296a18..f7a8c353a6b 100644

 

From 267e3a950faa281017affa6d79f99877ca6329ce Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v13 16/20] error safe for casting timestamp to other types per
 pg_cast

from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/date.c      |  7 +++++--
 src/backend/utils/adt/timestamp.c | 10 ++++++++--
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index bbc864a80cd..91cc8cc85f0 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1330,7 +1330,10 @@ timestamp_date(PG_FUNCTION_ARGS)
 	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);
 	DateADT		result;
 
-	result = timestamp2date_safe(timestamp, NULL);
+	result = timestamp2date_safe(timestamp, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		PG_RETURN_NULL();
+
 	PG_RETURN_DATEADT(result);
 }
 
@@ -2008,7 +2011,7 @@ timestamp_time(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();

index 6adcf950426..442bc061ba2 100644
 
 	result = timestamp;

 	PG_RETURN_TIMESTAMP(result);
 }
@@ -6432,8 +6433,13 @@ Datum
 timestamp_timestamptz(PG_FUNCTION_ARGS)
 {
 	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);
+	TimestampTz result;
 
-	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+	result = timestamp2timestamptz_safe(timestamp, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		PG_RETURN_NULL();
+
+	PG_RETURN_TIMESTAMPTZ(result);
 }
 
 /*
-- 
2.34.1

From 7404ae790cb80b8050ef3228e01dffca95c66048 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v13 15/20] error safe for casting timestamptz to other types
 per pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/date.c      |  9 ++++++---
 src/backend/utils/adt/timestamp.c | 10 ++++++++--
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 8fa336da250..bbc864a80cd 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1406,7 +1406,10 @@ timestamptz_date(PG_FUNCTION_ARGS)
 	TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
 	DateADT		result;
 
-	result = timestamptz2date_safe(timestamp, NULL);
+	result = timestamptz2date_safe(timestamp, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->args))
+		PG_RETURN_NULL();
+
 	PG_RETURN_DATEADT(result);
 }
 
@@ -2036,7 +2039,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();

@@ -2955,7 +2958,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();

index 1e69e6adb77..6adcf950426 100644
 
 	result = timestamp;

 	PG_RETURN_TIMESTAMPTZ(result);
 }
@@ -6494,8 +6495,13 @@ Datum
 timestamptz_timestamp(PG_FUNCTION_ARGS)
 {
 	TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	result;
 
-	PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+	result = timestamptz2timestamp_safe(timestamp, fcinfo->context);
+	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+		PG_RETURN_NULL();
+
+	PG_RETURN_TIMESTAMP(result);
 }
 
 /*
-- 
2.34.1

From 8e292e11d9e770011151b6bcb2cc49baa818688c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v13 17/20] error safe for casting jsonb to other types per
 pg_cast

 

 
 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From f15b5bc1c06dddbdf2c09f7baf4ec9922bbc81ce Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v13 13/20] error safe for casting date to other types per
 pg_cast

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/date.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c4b8125dd66..cf241ea9794 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -730,15 +730,6 @@ date2timestamptz_safe(DateADT dateVal, Node *escontext)
 	return result;
 }
 
-/*
- * Promote date to timestamptz, throwing error for overflow.
- */
-static TimestampTz
-date2timestamptz(DateADT dateVal)
-{
-	return date2timestamptz_safe(dateVal, NULL);
-}
-
 /*
  * date2timestamp_no_overflow
  *
@@ -1323,7 +1314,9 @@ date_timestamp(PG_FUNCTION_ARGS)
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
 	Timestamp	result;
 
-	result = date2timestamp(dateVal);
+	result = date2timestamp_safe(dateVal, fcinfo->context);
+	if(SOFT_ERROR_OCCURRED(fcinfo->context))
+		PG_RETURN_NULL();
 
 	PG_RETURN_TIMESTAMP(result);
 }
@@ -1396,7 +1389,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
 	TimestampTz result;
 
-	result = date2timestamptz(dateVal);
+	result = date2timestamptz_safe(dateVal, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		PG_RETURN_NULL();

From 4815fa673ae5da4609546c2ca793f3e00ff6deaf Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v13 14/20] error safe for casting interval to other types per
 pg_cast

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cf241ea9794..8fa336da250 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2106,7 +2106,7 @@ interval_time(PG_FUNCTION_ARGS)
 	TimeADT		result;

index af48527d436..1e69e6adb77 100644

From 14ecad4c8c6e1b87dfac93bc951869b4e5b65e2e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v13 10/20] error safe for casting numeric to other types per
 pg_cast

 

 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

 
 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From a79115e9feb53040700473083abccd2fcb04f889 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v13 11/20] error safe for casting float4 to other types per
 pg_cast

 
 	/* Range check */

 	/* Range check */
 
index d5631f35465..68299e91512 100644
 
 	/* Range check */

 
 
-- 
2.34.1

From 11e0c35f5dea406829914a58334f521b003a59dd Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v13 12/20] error safe for casting float8 to other types per
 pg_cast

 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */
 
index 68299e91512..7ff7f267b7e 100644
 
 	/* Range check */

 
 
-- 
2.34.1

From 6efb0ea1f56648b943210756d39e35c164df26f3 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v13 09/20] error safe for casting bigint to other types per
 pg_cast

index 9cd420b4b9d..d5631f35465 100644

 

 
-- 
2.34.1

From aa7af977a912a6a0c9a17e564f9da56327ecf08e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v13 06/20] error safe for casting inet to other types per
 pg_cast

 
 
-- 
2.34.1

From f21da63d159abe81a2a3949662f349296af33cc8 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v13 07/20] error safe for casting macaddr8 to other types per
 pg_cast

 

From eac1fd0e0faebf8ebd0b90f01d538c309e404e17 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v13 08/20] error safe for casting integer to other types per
 pg_cast

 

 
-- 
2.34.1

From 4e50479b892bad667251f2d0e810c4a8d048b98b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v13 05/20] error safe for casting character varying to other
 types per pg_cast

From 3069a32ec8a4360286ca782ae6033334e7a30e34 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v13 03/20] error safe for casting character to other types per
 pg_cast

 

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;

From 55106063589c356276a6c5b9475b516ce4b09cc0 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 25 Nov 2025 20:07:28 +0800
Subject: [PATCH v13 04/20] error safe for casting character to other types per
 pg_cast

 

 

 
 
 	if (namelist == NIL)
-		ereport(ERROR,

 	return (xmltype *) data;
 #else

From cc1824b43dc0340f474fc80e27cfa81a7edada26 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v13 02/20] error safe for casting bit/varbit to other types
 per pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
 
 	if (!isExplicit)
-		ereport(ERROR,

 
 
-- 
2.34.1

From f4f0e093e60f50b5ee0da5a941bd2e1015be3236 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v13 01/20] error safe for casting bytea to other types per
 pg_cast

 

 

-- 
2.34.1

---

From: Amul Sul <[email-masked]>
Date: Thu, 4 Dec 2025 18:16:50 +0530

> for these 3 functions, now it looks like:

Make sense, thanks for updating the patch.

Regarding v13-0019 and v13-0020 patches, I have a bunch of comments
for the code, but I'd like to understand the implemented design first.
I have some basic questions regarding the commit message and code
comment, as follows:

"
"

I might not have understood the implementation completely -- can't we
use fmgr_last_builtin_oid to detect built-in functions?
--

+           /*

How difficult would it be to modify coerce_to_target_type() to make it
error safe?

Could we utilize the existing type casting infrastructure for this, perhaps by
simply considering the evolution and use of the additional default
expression? If we could, the resulting implementation would be very
clean, IMHO.

Kindly excuse and point me if that is already discussed.
--

Here are few comments for v13-0018:

I think we should avoid introducing the "_safe" version of static
routines in the .c file. Instead, we can add the Node *escontext
argument to the existing routines, similar to how single_decode() was
previously modified to accept it.
--

-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node
*escontext);

Following the previous suggestion, please keep the existing function
as it is and just add one more argument to it.
--

 }

+
 static inline float4

A spurious change.
--

Regards,
Amul

---

From: jian he <[email-masked]>
Date: Mon, 8 Dec 2025 22:58:00 +0800

> use fmgr_last_builtin_oid to detect built-in functions?

hi, Amul.

I have removed the pg_cast.casterrorsafe field.
But in the future, to make the below (Examples) CASTs associated with
built-in function
error safe, I think we need pg_cast.casterrorsafe.  Otherwise,
how can we know if the pg_cast.castfunc (built-in function) is error
safe or not.

CREATE CAST (interval AS uuid ) WITH FUNCTION uuidv7(interval);
CREATE CAST (jsonb AS text) WITH FUNCTION jsonb_array_element_text(jsonb, int);
CREATE CAST (json AS text) WITH FUNCTION json_array_element_text(json, int);

I’ve added a dedicated function: CoercionErrorSafeCheck, which
checks whether the cast can be evaluated safely. If not, we throw an error.

SELECT CAST('A' AS DATE DEFAULT NULL ON CONVERSION ERROR)
Here, 'A' is an Unknown Const. As mentioned earlier, Unknown Const Node must be
coerced error safe, otherwise, it will raise an error instead of returning NULL.

The coercion of Unknown Const happens inside coerce_to_target_type, which means
part of that function must run in an error safe.  coerce_to_target_type is used
widely, adding another parameter to it seems no good, so I introduced
coerce_to_target_type_extended, coerce_type_extend.
CoerceUnknownConstSafe is being removed.

stringTypeDatumSafe, OidInputFunctionCallSafe functions are needed for coercing
Unknown Const to the target type.  Refactoring existing function seems not
ideal, so i invented these two functions.

--
jian
https://www.enterprisedb.com
From 732b6d48fe00592a81842c271712aff933179891 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v14 16/19] error safe for casting timestamp to other types per
 pg_cast

 

 
index c144caf2458..e84cf4b25f8 100644
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 7bb658eeabe81b0aba4a0c29de1c386bbbec8d6a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 8 Dec 2025 22:53:15 +0800
Subject: [PATCH v14 19/19] CAST(expr AS newtype DEFAULT ON ERROR)

demo:

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 contrib/citext/expected/citext_1.out          |   5 +
 contrib/citext/sql/citext.sql                 |   2 +
 .../pg_stat_statements/expected/select.out    |  23 +-
 contrib/pg_stat_statements/sql/select.sql     |   5 +
 doc/src/sgml/syntax.sgml                      |  30 +
 src/backend/executor/execExpr.c               | 102 ++-
 src/backend/executor/execExprInterp.c         |  29 +
 src/backend/jit/llvm/llvmjit_expr.c           |  26 +
 src/backend/nodes/nodeFuncs.c                 |  61 ++
 src/backend/optimizer/util/clauses.c          |  51 +-
 src/backend/parser/gram.y                     |  22 +
 src/backend/parser/parse_agg.c                |   9 +
 src/backend/parser/parse_coerce.c             |  78 +-
 src/backend/parser/parse_expr.c               | 412 ++++++++-
 src/backend/parser/parse_func.c               |   3 +
 src/backend/parser/parse_target.c             |  14 +
 src/backend/parser/parse_type.c               |  14 +
 src/backend/parser/parse_utilcmd.c            |   2 +-
 src/backend/utils/adt/arrayfuncs.c            |   8 +
 src/backend/utils/adt/ruleutils.c             |  21 +
 src/backend/utils/fmgr/fmgr.c                 |  13 +
 src/include/executor/execExpr.h               |   7 +
 src/include/executor/executor.h               |   1 +
 src/include/fmgr.h                            |   3 +
 src/include/nodes/execnodes.h                 |  21 +
 src/include/nodes/parsenodes.h                |  11 +
 src/include/nodes/primnodes.h                 |  36 +
 src/include/optimizer/optimizer.h             |   2 +-
 src/include/parser/parse_coerce.h             |  15 +
 src/include/parser/parse_node.h               |   2 +
 src/include/parser/parse_type.h               |   2 +
 src/test/regress/expected/cast.out            | 810 ++++++++++++++++++
 src/test/regress/expected/create_cast.out     |   5 +
 src/test/regress/expected/equivclass.out      |   7 +
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/cast.sql                 | 350 ++++++++
 src/test/regress/sql/create_cast.sql          |   1 +
 src/test/regress/sql/equivclass.sql           |   3 +
 src/tools/pgindent/typedefs.list              |   3 +
 39 files changed, 2166 insertions(+), 45 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_1.out b/contrib/citext/expected/citext_1.out
index c5e5f180f2b..647eea19142 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,6 +10,11 @@ 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 when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+                    ^
+HINT:  Safe type cast for user-defined types are not yet supported
 -- 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..99794497d47 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 <>.
 
+--error safe type cast

+--error safe type cast
index 34c83880a66..526df9922e7 100644
 
    <para>
@@ -2160,6 +2164,32 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
      <xref linkend="sql-createcast"/>.
     </para>
    </note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+  <title>Safe Type Cast</title>
+   <para>
+Sometimes a type cast may fail; to avoid such fail case, an <literal>ON ERROR</literal> clause can be
+used to avoid potential errors. The syntax is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+    If type cast fails, it falls back to evaluating the supplied
+    default <replaceable>expression</replaceable> in <literal>ON ERROR</literal> clause.
+    Currently, this only support built-in system type casts, see <xref linkend="catalog-pg-cast"/>.
+    Type casts created using <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+  </para>
+
+    <para>
+    Some examples:
+<screen>
+SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
+SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1.1</computeroutput>
+</screen>
+    </para>
+  </sect3>
+
   </sect2>
 
index b05ff476a63..2629aefe74f 100644
 
+/*

 

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4741,6 +4794,53 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
index 1d88cdd2cb4..ca6e0fd56cd 100644

 /*
 
index bda4c4eb292..e6d8b6c467f 100644
@@ -2447,7 +2447,8 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 	((Node *) evaluate_expr((Expr *) (node), \
 							exprType((Node *) (node)), \
 							exprTypmod((Node *) (node)), \
-							exprCollation((Node *) (node))))
+							exprCollation((Node *) (node)), \
+							false))
 
 /*
  * Recursive guts of eval_const_expressions/estimate_expression_value
@@ -2958,6 +2959,32 @@ eval_const_expressions_mutator(Node *node,
 												  copyObject(jve->format));
 			}

@@ -3380,7 +3407,8 @@ eval_const_expressions_mutator(Node *node,
 					return (Node *) evaluate_expr((Expr *) svf,
 												  svf->type,
 												  svf->typmod,
-												  InvalidOid);
+												  InvalidOid,
+												  false);
 				else
 					return copyObject((Node *) svf);
 			}
@@ -4698,7 +4726,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	newexpr->location = -1;
 
 	return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
-						 result_collid);
+						 result_collid, false);
 }
 
 /*
@@ -5152,10 +5180,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.
+ *
+ * When error_safe set to true, we will evaluate the constant expression in a
+ * error safe way. If the evaluation fails, return NULL instead of throwing
+ * error.
  */
 Expr *
 evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
-			  Oid result_collation)
+			  Oid result_collation, bool error_safe)
 {
 	EState	   *estate;
 	ExprState  *exprstate;
@@ -5180,7 +5212,10 @@ 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);
+	if (error_safe)
+		exprstate = ExecInitExprSafe(expr, NULL);
+	else
+		exprstate = ExecInitExpr(expr, NULL);
 
 	/*
 	 * And evaluate it.
@@ -5200,6 +5235,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);

 			break;
 
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("window functions are not allowed in check constraints");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("window functions are not allowed in CAST DEFAULT expressions");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..4ae87e2030b 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -81,6 +81,31 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
 					  CoercionContext ccontext,
 					  CoercionForm cformat,
 					  int location)
+{
+	return coerce_to_target_type_extended(pstate,
+										  expr,
+										  exprtype,
+										  targettype,
+										  targettypmod,
+										  ccontext,
+										  cformat,
+										  location,
+										  NULL);
+}
+
+/*
+ * escontext: If not NULL, expr (Unknown Const node type) will be coerced to the
+ * target type in an error-safe way. If it fails, NULL is returned.
+ *
+ * For other parameters, see above coerce_to_target_type.
+ */
+Node *
+coerce_to_target_type_extended(ParseState *pstate, Node *expr, Oid exprtype,
+							   Oid targettype, int32 targettypmod,
+							   CoercionContext ccontext,
+							   CoercionForm cformat,
+							   int location,
+							   Node *escontext)
 {
 	Node	   *result;
 	Node	   *origexpr;
@@ -102,9 +127,12 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
 	while (expr && IsA(expr, CollateExpr))
 		expr = (Node *) ((CollateExpr *) expr)->arg;
 
+	result = coerce_type_extend(pstate, expr, exprtype,
 
 	/*
 	 * If the target is a fixed-length type, it may need a length coercion as
@@ -158,6 +186,18 @@ Node *
 coerce_type(ParseState *pstate, Node *node,
 			Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
 			CoercionContext ccontext, CoercionForm cformat, int location)
+{
+	return coerce_type_extend(pstate, node,
+							  inputTypeId, targetTypeId, targetTypeMod,
+							  ccontext, cformat, location, NULL);
+}
+
+Node *
+coerce_type_extend(ParseState *pstate, Node *node,
+				   Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+				   CoercionContext ccontext, CoercionForm cformat,
+				   int location,
+				   Node *escontext)
 {
 	Node	   *result;
 	CoercionPathType pathtype;
@@ -256,6 +296,8 @@ coerce_type(ParseState *pstate, Node *node,
 		int32		inputTypeMod;
 		Type		baseType;
 		ParseCallbackState pcbstate;
+		char		*string;
+		bool		coercion_failed = false;
 
 		/*
 		 * If the target type is a domain, we want to call its base type's
@@ -308,21 +350,27 @@ coerce_type(ParseState *pstate, Node *node,
 		 * We assume here that UNKNOWN's internal representation is the same
 		 * as CSTRING.
 		 */
-		if (!con->constisnull)
-			newcon->constvalue = stringTypeDatum(baseType,
-												 DatumGetCString(con->constvalue),
-												 inputTypeMod);
+		if (con->constisnull)
+			string = NULL;
 		else
-			newcon->constvalue = stringTypeDatum(baseType,
-												 NULL,
-												 inputTypeMod);
+			string = DatumGetCString(con->constvalue);
+
+		if (!stringTypeDatumSafe(baseType,
+								 string,
+								 inputTypeMod,
+								 escontext,
+								 &newcon->constvalue))
+		{
+			coercion_failed = true;
+			newcon->constisnull = true;
+		}
 
 		/*
+		if (!coercion_failed && !con->constisnull && newcon->constlen == -1)
 
@@ -339,7 +387,7 @@ coerce_type(ParseState *pstate, Node *node,
 		 * identical may not get recognized as such.  See pgsql-hackers
 		 * discussion of 2008-04-04.
 		 */
-		if (!con->constisnull && !newcon->constbyval)
+		if (!coercion_failed && !con->constisnull && !newcon->constbyval)
 		{
 			Datum		val2;
 
@@ -358,8 +406,10 @@ coerce_type(ParseState *pstate, Node *node,
 
 		result = (Node *) newcon;
 
-		/* If target is a domain, apply constraints. */
-		if (baseTypeId != targetTypeId)
+		if (coercion_failed)
+			result = NULL;
+		else if (baseTypeId != targetTypeId)
+			/* If target is a domain, apply constraints. */
 			result = coerce_to_domain(result,
 									  baseTypeId, baseTypeMod,
 									  targetTypeId,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 4524e49c326..7d3ed0eb890 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@

@@ -60,7 +62,8 @@ static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
@@ -76,6 +79,10 @@ static Node *transformWholeRowRef(ParseState *pstate,
+static void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
+								   Node *sourceexpr, Oid inputType,
+								   Oid targetType);
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
@@ -564,6 +575,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
@@ -1824,6 +1836,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -2011,17 +2026,24 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 
 	/*
 	 * Transform the element expressions
@@ -2045,9 +2067,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  can_coerce);
 			/* we certainly have an array here */
-			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			Assert(can_coerce || array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
 		}
 		else
@@ -2088,6 +2111,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	}
 	else
 	{
+		/* Target type must valid for CAST DEFAULT */
+		Assert(can_coerce == NULL);
+
 		/* Can't handle an empty array without a target type */
 		if (newelems == NIL)
 			ereport(ERROR,
@@ -2125,6 +2151,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		coerce_hard = false;
 	}
 
@@ -2134,28 +2162,82 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
 		if (coerce_hard)
 		{
-			newe = coerce_to_target_type(pstate, e,
-										 exprType(e),
-										 coerce_type,
-										 typmod,
-										 COERCION_EXPLICIT,
-										 COERCE_EXPLICIT_CAST,
-										 -1);
-			if (newe == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_CANNOT_COERCE),
-						 errmsg("cannot cast type %s to %s",
-								format_type_be(exprType(e)),
-								format_type_be(coerce_type)),
-						 parser_errposition(pstate, exprLocation(e))));
+			/*
+			 * Can not coerce, just append the transformed element expression to
+			 * the list.
+			 */
+			if (can_coerce && (!*can_coerce))
+				newe = e;
+			else
+			{
+				Node	   *ecopy = NULL;
+
+				if (can_coerce)
+					ecopy = copyObject(e);
+
+				newe = coerce_to_target_type_extended(pstate, e,
+													  exprType(e),
+													  coerce_type,
+													  typmod,
+													  COERCION_EXPLICIT,
+													  COERCE_EXPLICIT_CAST,
+													  -1,
+													  (Node *) &escontext);
+				if (newe == NULL)
+				{
+					if (!can_coerce)
+						ereport(ERROR,
+								(errcode(ERRCODE_CANNOT_COERCE),
+								errmsg("cannot cast type %s to %s",
+									   format_type_be(exprType(e)),
+									   format_type_be(coerce_type)),
+								parser_errposition(pstate, exprLocation(e))));
+					else
+					{
+						/*
+						 * Can not coerce, just append the transformed element
+						 * expression to the list.
+						 */
+						newe = ecopy;
+						*can_coerce = false;
+					}
+				}
+				else if (can_coerce && IsA(ecopy, Const) && IsA(newe, FuncExpr))
+				{
+					Node	   *result;
+
+					/*
+					 * pre-evaluate simple constant cast expressions in a way
+					 * that tolerate errors.
+					 */
+					FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+					assign_expr_collations(pstate, (Node *) newexpr);
+
+					result = (Node *) evaluate_expr((Expr *) newexpr,
+													coerce_type,
+													typmod,
+													coerce_type_coll,
+													true);
+					if (result == NULL)
+					{
+						newe = ecopy;
+						*can_coerce = false;
+					}
+				}
+			}
 		}
 		else
 			newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2825,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 									  (A_ArrayExpr *) arg,
 									  targetBaseType,
 									  elementType,
-									  targetBaseTypmod);
+									  targetBaseTypmod,
+									  NULL);
 		}
 		else
 			expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2863,291 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	return result;
 }
 
+/*
+ * Handle an explicit CAST(... DEFAULT ... ON CONVERSION ERROR) construct.
+ *
+ * Transform SafeTypeCast node, look up the type name, and apply any necessary
+ * coercion function(s).
+ */
+static Node *
+transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc)
+{
+	SafeTypeCastExpr   *result;
+	TypeCast   *tcast = (TypeCast *) tc->cast;
+	Node	   *source_expr;
+	Node	   *srcexpr;
+	Node	   *defexpr;
+	Node	   *cast_expr = NULL;
+	Oid			inputType;
+	Oid			targetType;
+	int32		targetTypmod;
+	Oid			targetTypecoll;
+	Oid			targetBaseType;
+	int32		targetBaseTypmod;
+	bool		can_coerce = true;
+
+	/* Look up the type name first */
+	typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+	targetBaseTypmod = targetTypmod;
+
+	targetTypecoll = get_typcollation(targetType);
+
+	targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+	/* now looking at DEFAULT expression */
+	defexpr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+	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->raw_default), defexpr));
+
+	assign_expr_collations(pstate, defexpr);
+
+	/*
+	 * The collation of DEFAULT expression must match the collation of the
+	 * target type.
+	 */
+	if (OidIsValid(targetTypecoll))
+	{
+		Oid		defColl = exprCollation(defexpr);
+
+		if (OidIsValid(defColl) && targetTypecoll != defColl)
+			ereport(ERROR,
+					errcode(ERRCODE_DATATYPE_MISMATCH),
+					errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+					errdetail("\"%s\" versus \"%s\"",
+							  get_collation_name(targetTypecoll),
+							  get_collation_name(defColl)),
+					parser_errposition(pstate, exprLocation(defexpr)));
+	}
+
+	/*
+	 * If the type cast 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.  Otherwise, just transform the argument normally.
+	 */
+	if (IsA(tcast->arg, A_ArrayExpr))
+	{
+		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.
+		 */
+		elementType = get_element_type(targetBaseType);
+
+		if (OidIsValid(elementType))
+		{
+			source_expr = transformArrayExpr(pstate,
+											 (A_ArrayExpr *) tcast->arg,
+											 targetBaseType,
+											 elementType,
+											 targetBaseTypmod,
+											 &can_coerce);
+		}
+		else
+			source_expr = transformExprRecurse(pstate, tcast->arg);
+	}
+	else
+		source_expr = transformExprRecurse(pstate, tcast->arg);
+
+	/*
+	 * Since we can't be certain that coerce_to_target_type_extended won't
+	 * modify the source expression, we create a copy of it first. This ensures
+	 * the transformed source expression is correctly passed to SafeTypeCastExpr
+	 */
+	srcexpr = copyObject(source_expr);
+
+	inputType = exprType(source_expr);
+	if (inputType == InvalidOid)
+		return (Node *) NULL;			/* return NULL if NULL input */
+
+	if (can_coerce)
+	{
+		ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+		cast_expr = coerce_to_target_type_extended(pstate, source_expr, inputType,
+												   targetType, targetTypmod,
+												   COERCION_EXPLICIT,
+												   COERCE_EXPLICIT_CAST,
+												   tcast->location,
+												   (Node *) &escontext);
+		if (cast_expr == NULL)
+			can_coerce = false;
+
+		CoercionErrorSafeCheck(pstate, cast_expr, source_expr, inputType,
+							   targetType);
+	}
+
+	if (IsA(srcexpr, Const) && cast_expr && IsA(cast_expr, FuncExpr))
+	{
+		Node	   *result;
+
+		/*
+		 * pre-evaluate simple constant cast expressions in a error safe way
+		 *
+		 * Rationale:
+		 * 1. When deparsing safe cast expressions (or in other cases),
+		 *    eval_const_expressions might be invoked, but it cannot
+		 *    handle errors gracefully.
+		 * 2. If the cast expression involves only simple constants, we
+		 *    can safely evaluate it ahead of time. If the evaluation
+		 *    fails, it indicates that such a cast is not possible, and
+		 *    we can then fall back to the CAST DEFAULT expression.
+		 * 3. Even if FuncExpr (for castfunc) node has three arguments,
+		 *    the second and third arguments will also be constants per
+		 *    coerce_to_target_type. Evaluating a FuncExpr whose
+		 *    arguments are all Const is safe.
+		 */
+		FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+		assign_expr_collations(pstate, (Node *) newexpr);
+
+		result = (Node *) evaluate_expr((Expr *) newexpr,
+										targetType,
+										targetTypmod,
+										targetTypecoll,
+										true);
+		if (result == NULL)
+		{
+			/* can not coerce, set can_coerce to false */
+			can_coerce = false;
+			cast_expr = NULL;
+		}
+	}
+
+	Assert(can_coerce || cast_expr == NULL);
+
+	result = makeNode(SafeTypeCastExpr);
+	result->source_expr = srcexpr;
+	result->cast_expr = cast_expr;
+	result->default_expr = defexpr;
+	result->resulttype = targetType;
+	result->resulttypmod = targetTypmod;
+	result->resultcollid = targetTypecoll;
+	result->location = tcast->location;
+
+	return (Node *) result;
+}
+
+/*
+ * Check coercion is error safe or not.  If not then report error
+ */
+static void
+CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid inputType,
+					   Oid targetType)
+{
+	HeapTuple	tuple;
+	bool 	errorsafe = true;
+	bool	userdefined = false;
+	Oid		inputBaseType;
+	Oid		targetBaseType;
+	Oid 	inputElementType;
+	Oid		inputElementBaseType;
+	Oid 	targetElementType;
+	Oid		targetElementBaseType;
+
+	/*
+	 * Binary coercion cast is equivalent to no cast at all.  CoerceViaIO can
+	 * also be evaluated in an error-safe manner. So skip these two cases.
+	 */
+	if (!castexpr || IsA(castexpr, RelabelType) ||
+		IsA(castexpr, CoerceViaIO))
+		return;
+
+	/*
+	 * Type cast involving with some types is not error safe, do the check now.
+	 * We need consider domain over array and array over domain scarenio. We
+	 * already checked user-defined type cast above.
+	 */
+	inputElementType = get_element_type(inputType);
+
+	if (OidIsValid(inputElementType))
+		inputElementBaseType = getBaseType(inputElementType);
+	else
+	{
+		inputBaseType = getBaseType(inputType);
+
+		inputElementBaseType = get_element_type(inputBaseType);
+		if (!OidIsValid(inputElementBaseType))
+			inputElementBaseType = inputBaseType;
+	}
+
+	targetElementType = get_element_type(targetType);
+
+	if (OidIsValid(targetElementType))
+		targetElementBaseType = getBaseType(targetElementType);
+	else
+	{
+		targetBaseType = getBaseType(targetType);
+
+		targetElementBaseType = get_element_type(targetBaseType);
+		if (!OidIsValid(targetElementBaseType))
+			targetElementBaseType = targetBaseType;
+	}
+
+	if (inputElementBaseType == MONEYOID ||
+		targetElementBaseType == MONEYOID ||
+		(inputElementBaseType == CIRCLEOID &&
+		targetElementBaseType == POLYGONOID))
+	{
+		/*
+		 * Casts involving MONEY type are not error safe. The CIRCLE to POLYGON
+		 * cast is also unsafe because it relies on a SQL-language function;
+		 * only C-language functions are currently supported for error safe
+		 * casts.
+		 */
+		errorsafe = false;
+	}
+
+	if (errorsafe)
+	{
+		/* Look in pg_cast */
+		tuple = SearchSysCache2(CASTSOURCETARGET,
+								ObjectIdGetDatum(inputElementBaseType),
+								ObjectIdGetDatum(targetElementBaseType));
+
+		if (HeapTupleIsValid(tuple))
+		{
+			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+			if (castForm->oid > FirstUnpinnedObjectId)
+			{
+				errorsafe = false;
+				userdefined = true;
+			}
+
+			ReleaseSysCache(tuple);
+		}
+	}
+
+	if (!errorsafe)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+					   format_type_be(inputType),
+					   format_type_be(targetType),
+					   "DEFAULT",
+					   "CAST ... ON CONVERSION ERROR"),
+				userdefined
+				? errhint("Safe type cast for user-defined types are not yet supported")
+				: errhint("Explicit cast is defined but definition is not error safe"),
+				parser_errposition(pstate, exprLocation(sourceexpr)));
+}
+
+
 /*
  * Handle an explicit COLLATE clause.
  *
@@ -3193,6 +3561,8 @@ ParseExprKindName(ParseExprKind exprKind)
 		case EXPR_KIND_CHECK_CONSTRAINT:
 		case EXPR_KIND_DOMAIN_CHECK:
 			return "CHECK";
+		case EXPR_KIND_CAST_DEFAULT:
+			return "CAST DEFAULT";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("set-returning functions are not allowed in check constraints");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..dc03cf4ce74 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,20 @@ FigureColnameInternal(Node *node, char **name)
 				}
 			}
 			break;
+		case T_SafeTypeCast:
+			strength = FigureColnameInternal(((SafeTypeCast *) node)->cast,
+											 name);
+			if (strength <= 1)
+			{
+				TypeCast 	*node_cast;
+				node_cast = (TypeCast *)((SafeTypeCast *) node)->cast;
+				if (node_cast->typeName != NULL)
+				{
+					*name = strVal(llast(node_cast->typeName->names));
+					return 1;
+				}
+			}
+			break;
 		case T_CollateClause:
 			return FigureColnameInternal(((CollateClause *) node)->arg, name);
 		case T_GroupingFunc:
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..d04a729f4db 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "utils/array.h"
@@ -660,6 +661,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
 	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
 }
 
+/* error safe version of stringTypeDatum */
+bool
+stringTypeDatumSafe(Type tp, char *string, int32 atttypmod,
+					Node *escontext, Datum *result)
+{
+	Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+	Oid			typinput = typform->typinput;
+	Oid			typioparam = getTypeIOParam(tp);
+
+	return OidInputFunctionCallSafe(typinput, 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 e96b38a59d5..6df0b975a88 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4630,7 +4630,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, false);
 		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 cc76bdde723..2a3b4649123 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
 		/* Apply the given expression to source element */
 		values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
 
index 6cf90be40bb..50ae41837ba 100644

+bool
 
index c0dbe85ed1c..a9357b98d70 100644
 
index d14294a4ece..82b0fb83b4b 100644
 
+/*
+	Node	   	*raw_default;	/* untransformed DEFAULT expression */
index 1b4436f2ff6..1401109fb3c 100644
 
+/*
+ * SafeTypeCastExpr -
+	/* transformed source expression */
index 44ec5296a18..587e711596a 100644
@@ -143,7 +143,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, bool error_safe);

index 8d775c72c59..1eca1ce727d 100644
@@ -43,11 +43,26 @@ extern Node *coerce_to_target_type(ParseState *pstate,
+extern Node *coerce_to_target_type_extended(ParseState *pstate,
+											Node *expr,
+											Oid exprtype,
+											Oid targettype,
+											int32 targettypmod,
+											int location,
+extern Node *coerce_type_extend(ParseState *pstate, Node *node,
+								Oid inputTypeId,
+								Oid targetTypeId, int32 targetTypeMod,
+								int location,
index 0d919d8bfa2..40aca2b31c9 100644
+								Node *escontext, Datum *result);
 
index 00000000000..b0dc7b8faea
@@ -0,0 +1,810 @@
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+ A
+--test source expression is a unknown const
+SELECT CAST('a' as int DEFAULT 18 ON CONVERSION ERROR);
+   18
+-- DEFAULT expression can not be set-returning
+-- test with user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION 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,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+ {42,42}
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+ {41,43}
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+ {1}
+HINT:  Explicit cast is defined but definition is not error safe
+HINT:  Explicit cast is defined but definition is not error safe
+HINT:  Explicit cast is defined but definition is not error safe
+HINT:  Explicit cast is defined but definition is not error safe
+HINT:  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[] when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVE...
+HINT:  Explicit cast is defined but definition is not error safe
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+   19
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+   20
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+   21
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+   22
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+   23
+SET datestyle TO ISO, YMD;
+       CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+    CAST(1 AS date DEFAULT '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast0,
+SELECT * FROM safecastview;
+ bpchar |   cast0    | cast1 | cast2 | cast3 
+--------+------------+-------+-------+-------
+ 123    | 2025-12-07 | {1,2} |       | {1,2}
+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;
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+    CAST(ARRAY[ARRAY[1, 2, 1.1::integer], ARRAY['three', true, '01'::"bit"]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+---------+---------
+ {41,43} | {41,43}
+RESET datestyle;
+--test CAST DEFAULT expression mutability
index 0e69644bca2..0054ed0ef67 100644
 
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR:  cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+                    ^
+HINT:  Safe type cast for user-defined types are not yet supported
 -- 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..5aee2c7a9fb 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -95,6 +95,13 @@ 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. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+ int8alias2 
+------------
+ 1
+(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/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..0b031a37c36 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
-test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252
+test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252 cast
 
 # ----------
index 00000000000..9b6f3cc0649
@@ -0,0 +1,350 @@
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+--test source expression is a unknown const
+SELECT CAST('a' as int DEFAULT 18 ON CONVERSION ERROR);
+-- DEFAULT expression can not be set-returning
+-- test with user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION 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,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);
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+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('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+SET datestyle TO ISO, YMD;
+       CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+SELECT * FROM safecastview;
+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;
+RESET datestyle;
+--test CAST DEFAULT expression mutability

 -- 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..5ad1d26311d 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -98,6 +98,9 @@ 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. so this is ok
+select cast('1'::int8 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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6e2ed0c8825..f196d3671c5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2673,6 +2673,9 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

From 20f7e337f2677267566521e135d4be7bf9c63a7d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v14 17/19] error safe for casting jsonb to other types per
 pg_cast

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index c4fe6e00dcd..1600204fa4a 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1815,7 +1815,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
  * Emit correct, translatable cast error message
  */
 static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
 {
 	static const struct
 	{
@@ -1836,7 +1836,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)

@@ -1851,7 +1851,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	JsonbValue	v;

 	if (v.type == jbvNull)
 	{
@@ -1860,7 +1863,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);
 
@@ -1875,7 +1881,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	Numeric		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1884,7 +1893,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	}

 	/*
 	 * v.val.numeric points into jsonb body, so we need to make a copy to
@@ -1905,7 +1917,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1914,7 +1929,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int2,
 								   NumericGetDatum(v.val.numeric));
@@ -1932,7 +1950,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1941,7 +1962,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int4,
 								   NumericGetDatum(v.val.numeric));
@@ -1959,7 +1983,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1968,7 +1995,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int8,
 								   NumericGetDatum(v.val.numeric));
@@ -1986,7 +2016,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1995,7 +2028,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_float4,
 								   NumericGetDatum(v.val.numeric));
@@ -2013,7 +2049,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -2022,7 +2061,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	}

From 4e74eb428baff34133cc6e5462e8d43edfc256c7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 8 Dec 2025 10:02:08 +0800
Subject: [PATCH v14 18/19] error safe for casting geometry data type

can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=[email-masked]
---
 contrib/btree_gist/btree_float4.c |   2 +-
 contrib/btree_gist/btree_float8.c |   4 +-
 src/backend/utils/adt/float.c     | 104 ++++++------
 src/backend/utils/adt/geo_ops.c   | 268 ++++++++++++++++++++++--------
 src/include/utils/float.h         | 110 ++++++++----
 5 files changed, 330 insertions(+), 158 deletions(-)

 	r = a - b;

 	r = arg1 - arg2;

 	r = a - b;
 
index 55c7030ba81..2710e42f5bb 100644
 
 pg_noinline void
 
 pg_noinline void
 
 	result = sqrt(arg1);

 	result = cbrt(arg1);

 
 
 	result = log(arg1);

 	result = log10(arg1);

 	result = acos(arg1);

 	result = asin(arg1);

 

 

 

 

 

 

 

 

 
 
 	/*
 
 			Sxx = get_float8_nan();
 		}
 
 			Sxx = get_float8_nan();
 		}
@@ -3430,7 +3430,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
 				(isinf(Sxy) &&
 
 			if (isinf(Sxx))
 				Sxx = get_float8_nan();
@@ -3603,15 +3603,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
 		if (float8_eq(Cx1, Cx2))
 			Cx = Cx1;
index 9101a720744..19f75e59a6d 100644
@@ -77,12 +77,13 @@ enum path_delim
 
 /* Routines for points */
 static inline void point_construct(Point *result, float8 x, float8 y);
-static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline void point_add_point(Point *result, Point *pt1, Point *pt2,
+								   Node *escontext);
 static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
 static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
 static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
 static inline bool point_eq_point(Point *pt1, Point *pt2);
-static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext);
 static inline float8 point_sl(Point *pt1, Point *pt2);
 static int	point_inside(Point *p, int npts, Point *plist);
 
@@ -108,7 +109,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
 
 /* Routines for boxes */
 static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
-static void box_cn(Point *center, BOX *box);
+static void box_cn(Point *center, BOX *box, Node* escontext);
 static bool box_ov(BOX *box1, BOX *box2);
 static float8 box_ar(BOX *box);
 static float8 box_ht(BOX *box);
@@ -125,7 +126,7 @@ static float8 circle_ar(CIRCLE *circle);
 
 /* Routines for polygons */
 static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext);
 static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
 static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
 static bool plist_same(int npts, Point *p1, Point *p2);
@@ -836,10 +837,10 @@ box_distance(PG_FUNCTION_ARGS)
 	Point		a,
 				b;
 
-	box_cn(&a, box1);
-	box_cn(&b, box2);
+	box_cn(&a, box1, NULL);
+	box_cn(&b, box2, NULL);
 
-	PG_RETURN_FLOAT8(point_dt(&a, &b));
+	PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
 }

@@ -851,7 +852,9 @@ box_center(PG_FUNCTION_ARGS)
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	Point	   *result = (Point *) palloc(sizeof(Point));
 
-	box_cn(result, box);
+	box_cn(result, box, fcinfo->context);
+	if ((SOFT_ERROR_OCCURRED(fcinfo->context)))
+		PG_RETURN_NULL();
 
 	PG_RETURN_POINT_P(result);
 }
@@ -869,13 +872,26 @@ box_ar(BOX *box)
 /*		box_cn	-		stores the centerpoint of the box into *center.
  */
 static void
-box_cn(Point *center, BOX *box)
+box_cn(Point *center, BOX *box, Node *escontext)
 {
-	center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
-	center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+	float8		x;
+	float8		y;
+
+	x = float8_pl_safe(box->high.x, box->low.x, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return;
+
+	center->x = float8_div_safe(x, 2.0, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return;
+
+	y = float8_pl_safe(box->high.y, box->low.y, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return;
+
+	center->y = float8_div_safe(y, 2.0, escontext);
 }
 
-
 /*		box_wd	-		returns the width (length) of the box
  *								  (horizontal magnitude).
  */
@@ -1808,7 +1824,7 @@ path_length(PG_FUNCTION_ARGS)
 			iprev = path->npts - 1; /* include the closure segment */
 		}
 
-		result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i]));
+		result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i], NULL));
 	}
 
 	PG_RETURN_FLOAT8(result);
@@ -1995,13 +2011,24 @@ point_distance(PG_FUNCTION_ARGS)
 	Point	   *pt1 = PG_GETARG_POINT_P(0);
 	Point	   *pt2 = PG_GETARG_POINT_P(1);
 
-	PG_RETURN_FLOAT8(point_dt(pt1, pt2));
+	PG_RETURN_FLOAT8(point_dt(pt1, pt2, NULL));
 }
 
 static inline float8
-point_dt(Point *pt1, Point *pt2)
+point_dt(Point *pt1, Point *pt2, Node *escontext)
 {
-	return hypot(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+	float8		x;
+	float8		y;
+
+	x = float8_mi_safe(pt1->x, pt2->x, escontext);
+	if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+		return 0.0;
+
+	y  = float8_mi_safe(pt1->y, pt2->y, escontext);
+	if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+		return 0.0;
+
+	return hypot(x, y);
 }
 
 Datum
@@ -2173,7 +2200,7 @@ lseg_length(PG_FUNCTION_ARGS)
 {
 	LSEG	   *lseg = PG_GETARG_LSEG_P(0);
 
-	PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1]));
+	PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1], NULL));
 }
 
 /*----------------------------------------------------------
@@ -2258,8 +2285,8 @@ lseg_lt(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);
 
-	PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]),
-						point_dt(&l2->p[0], &l2->p[1])));
+	PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1], NULL),
+						point_dt(&l2->p[0], &l2->p[1], NULL)));
 }
 
 Datum
@@ -2268,8 +2295,8 @@ lseg_le(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);
 
-	PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]),
-						point_dt(&l2->p[0], &l2->p[1])));
+	PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1], NULL),
+						point_dt(&l2->p[0], &l2->p[1], NULL)));
 }
 
 Datum
@@ -2278,8 +2305,8 @@ lseg_gt(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);
 
-	PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]),
-						point_dt(&l2->p[0], &l2->p[1])));
+	PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1], NULL),
+						point_dt(&l2->p[0], &l2->p[1], NULL)));
 }
 
 Datum
@@ -2288,8 +2315,8 @@ lseg_ge(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);
 
-	PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]),
-						point_dt(&l2->p[0], &l2->p[1])));
+	PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1], NULL),
+						point_dt(&l2->p[0], &l2->p[1], NULL)));
 }

@@ -2317,13 +2344,31 @@ lseg_center(PG_FUNCTION_ARGS)
 {
 	LSEG	   *lseg = PG_GETARG_LSEG_P(0);
 	Point	   *result;
+	float8		x;
+	float8		y;

 

@@ -2743,7 +2788,7 @@ line_closept_point(Point *result, LINE *line, Point *point)
 	if (result != NULL)
 		*result = closept;
 
-	return point_dt(&closept, point);
+	return point_dt(&closept, point, NULL);
 }
 
 Datum
@@ -2784,7 +2829,7 @@ lseg_closept_point(Point *result, LSEG *lseg, Point *pt)
 	if (result != NULL)
 		*result = closept;
 
-	return point_dt(&closept, pt);
+	return point_dt(&closept, pt, NULL);
 }
 
 Datum
@@ -3108,9 +3153,9 @@ on_pl(PG_FUNCTION_ARGS)
 static bool
 lseg_contain_point(LSEG *lseg, Point *pt)
 {
-	return FPeq(point_dt(pt, &lseg->p[0]) +
-				point_dt(pt, &lseg->p[1]),
-				point_dt(&lseg->p[0], &lseg->p[1]));
+	return FPeq(point_dt(pt, &lseg->p[0], NULL) +
+				point_dt(pt, &lseg->p[1], NULL),
+				point_dt(&lseg->p[0], &lseg->p[1], NULL));
 }
 
 Datum
@@ -3176,11 +3221,11 @@ on_ppath(PG_FUNCTION_ARGS)
 	if (!path->closed)
 	{
 		n = path->npts - 1;
-		a = point_dt(pt, &path->p[0]);
+		a = point_dt(pt, &path->p[0], NULL);
 		for (i = 0; i < n; i++)
 		{
-			b = point_dt(pt, &path->p[i + 1]);
-			if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1])))
+			b = point_dt(pt, &path->p[i + 1], NULL);
+			if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1], NULL)))
 				PG_RETURN_BOOL(true);
 			a = b;
 		}
@@ -3277,7 +3322,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
 
 	if (result != NULL)
 	{
-		box_cn(&point, box);
+		box_cn(&point, box, NULL);
 		lseg_closept_point(result, lseg, &point);
 	}
 
@@ -4108,11 +4153,20 @@ construct_point(PG_FUNCTION_ARGS)

 static inline void
-point_add_point(Point *result, Point *pt1, Point *pt2)
+point_add_point(Point *result, Point *pt1, Point *pt2, Node *escontext)
 {
-	point_construct(result,
-					float8_pl(pt1->x, pt2->x),
-					float8_pl(pt1->y, pt2->y));
+	float8		x;
+	float8		y;
+
+	x = float8_pl_safe(pt1->x, pt2->x, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return;
+
+	y = float8_pl_safe(pt1->y, pt2->y, escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return;
+
+	point_construct(result, x, y);
 }
 
 Datum
@@ -4124,7 +4178,7 @@ point_add(PG_FUNCTION_ARGS)

-	point_add_point(result, p1, p2);
+	point_add_point(result, p1, p2, NULL);
 
 	PG_RETURN_POINT_P(result);
 }
@@ -4236,8 +4290,8 @@ box_add(PG_FUNCTION_ARGS)
 
 	result = (BOX *) palloc(sizeof(BOX));
 
-	point_add_point(&result->high, &box->high, p);
-	point_add_point(&result->low, &box->low, p);
+	point_add_point(&result->high, &box->high, p, NULL);
+	point_add_point(&result->low, &box->low, p, NULL);
 
 	PG_RETURN_BOX_P(result);
 }
@@ -4400,7 +4454,7 @@ path_add_pt(PG_FUNCTION_ARGS)
 	int			i;
 
 	for (i = 0; i < path->npts; i++)
-		point_add_point(&path->p[i], &path->p[i], point);
+		point_add_point(&path->p[i], &path->p[i], point, NULL);
 
 	PG_RETURN_PATH_P(path);
 }
@@ -4458,7 +4512,7 @@ path_poly(PG_FUNCTION_ARGS)

@@ -4508,7 +4562,9 @@ poly_center(PG_FUNCTION_ARGS)

-	poly_to_circle(&circle, poly);
+	if (!poly_to_circle(&circle, poly, fcinfo->context))
+		PG_RETURN_NULL();
+
 	*result = circle.center;
 
 	PG_RETURN_POINT_P(result);
@@ -4766,7 +4822,7 @@ circle_overlap(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle1 = PG_GETARG_CIRCLE_P(0);
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);
 
-	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
 						float8_pl(circle1->radius, circle2->radius)));
 }
 
@@ -4828,7 +4884,7 @@ circle_contained(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle1 = PG_GETARG_CIRCLE_P(0);
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);
 
-	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
 						float8_mi(circle2->radius, circle1->radius)));
 }
 
@@ -4840,7 +4896,7 @@ circle_contain(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle1 = PG_GETARG_CIRCLE_P(0);
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);
 
-	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
 						float8_mi(circle1->radius, circle2->radius)));
 }
 
@@ -4970,7 +5026,7 @@ circle_add_pt(PG_FUNCTION_ARGS)

-	point_add_point(&result->center, &circle->center, point);
+	point_add_point(&result->center, &circle->center, point, NULL);
 	result->radius = circle->radius;
 
 	PG_RETURN_CIRCLE_P(result);
@@ -5069,7 +5125,7 @@ circle_distance(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);
 	float8		result;
 
-	result = float8_mi(point_dt(&circle1->center, &circle2->center),
+	result = float8_mi(point_dt(&circle1->center, &circle2->center, NULL),
 					   float8_pl(circle1->radius, circle2->radius));
 	if (result < 0.0)
 		result = 0.0;
@@ -5085,7 +5141,7 @@ circle_contain_pt(PG_FUNCTION_ARGS)
 	Point	   *point = PG_GETARG_POINT_P(1);
 	float8		d;
 
-	d = point_dt(&circle->center, point);
+	d = point_dt(&circle->center, point, NULL);
 	PG_RETURN_BOOL(d <= circle->radius);
 }
 
@@ -5097,7 +5153,7 @@ pt_contained_circle(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle = PG_GETARG_CIRCLE_P(1);
 	float8		d;
 
-	d = point_dt(&circle->center, point);
+	d = point_dt(&circle->center, point, NULL);
 	PG_RETURN_BOOL(d <= circle->radius);
 }
 
@@ -5112,7 +5168,7 @@ dist_pc(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle = PG_GETARG_CIRCLE_P(1);
 	float8		result;
 
-	result = float8_mi(point_dt(point, &circle->center),
+	result = float8_mi(point_dt(point, &circle->center, NULL),
 					   circle->radius);
 	if (result < 0.0)
 		result = 0.0;
@@ -5130,7 +5186,7 @@ dist_cpoint(PG_FUNCTION_ARGS)
 	Point	   *point = PG_GETARG_POINT_P(1);
 	float8		result;
 
-	result = float8_mi(point_dt(point, &circle->center), circle->radius);
+	result = float8_mi(point_dt(point, &circle->center, NULL), circle->radius);
 	if (result < 0.0)
 		result = 0.0;
 
@@ -5191,14 +5247,30 @@ circle_box(PG_FUNCTION_ARGS)

 
 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()
@@ -5209,15 +5281,35 @@ box_circle(PG_FUNCTION_ARGS)
 {
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	CIRCLE	   *circle;
+	float8		x;
+	float8		y;

 
+	circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);

@@ -5281,10 +5373,11 @@ circle_poly(PG_FUNCTION_ARGS)
  * XXX This algorithm should use weighted means of line segments
  *	rather than straight average values of points - tgl 97/01/21.
  */
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext)
 {
 	int			i;
+	float8		x;
 
 	Assert(poly->npts > 0);
 
@@ -5293,14 +5386,44 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
 	result->radius = 0;
 
+		point_add_point(&result->center,
 
+		x = point_dt(&poly->p[i], &result->center, escontext);
 
 Datum
@@ -5311,7 +5434,8 @@ poly_circle(PG_FUNCTION_ARGS)

-	poly_to_circle(result, poly);
+	if (!poly_to_circle(result, poly, fcinfo->context))
+		PG_RETURN_NULL();
 
index fc2a9cf6475..1b37e17defc 100644
 
 	result = val1 + val2;
 
 	return result;
 }
@@ -112,13 +127,7 @@ float4_pl(const float4 val1, const float4 val2)
 static inline float8
 
 static inline float4
@@ -128,7 +137,22 @@ float4_mi(const float4 val1, const float4 val2)
 
 	result = val1 - val2;
 
 	return result;
 }
@@ -136,13 +160,7 @@ float4_mi(const float4 val1, const float4 val2)
 static inline float8
 
 static inline float4
@@ -152,59 +170,89 @@ float4_mul(const float4 val1, const float4 val2)
 
 	result = val1 * val2;
 
 	return result;
 }
 
 static inline float8
 
 	result = val1 * val2;
 
 	return result;
 }
 
+static inline float8

 	return result;
 }
 
 static inline float8

 	return result;
 }
 
+static inline float8

From 094ab28fb8ecda149d642d5b6039c587c4252783 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v14 15/19] error safe for casting timestamptz to other types
 per pg_cast

 

 

index 1e8b859ff29..c144caf2458 100644
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 531bbac679e8fab04b85c4e2e02561df434ffb78 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v14 14/19] error safe for casting interval to other types per
 pg_cast

 
 
index 2dc90a2b8a9..1e8b859ff29 100644

From 90210f038093872ee0cffd3ca1fec15a582b90d2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v14 13/19] error safe for casting date to other types per
 pg_cast

 
-/*

 

From 83e48fc73bf383a079c70c6bc5c20d67a294fae7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v14 12/19] error safe for casting float8 to other types per
 pg_cast

index d5f15bfa7de..55c7030ba81 100644
 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */

 	/* Range check */
 
index c9d6669a7de..11c76ee40b5 100644

 
-- 
2.34.1

From 74e8597ebb8c26ae15e6eecd8bafcaab0f6d584c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v14 10/19] error safe for casting numeric to other types per
 pg_cast

index 1d626aecbe7..3e78cdf4ea0 100644

 
 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

 
 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 6c4cd958d9b95280d95d99c1d539c0bec9ce138f Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v14 11/19] error safe for casting float4 to other types per
 pg_cast

index 849639fda9f..d5f15bfa7de 100644
 
 	/* Range check */

 	/* Range check */

 	/* Range check */
 
index 3e78cdf4ea0..c9d6669a7de 100644

 
-- 
2.34.1

From dd9e831d32adbcfde1b8a4ddec7da1d5974d7341 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v14 09/19] error safe for casting bigint to other types per
 pg_cast

 

 

-- 
2.34.1

From f821f985d05320bb0e07328e842e909a728a13b5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v14 08/19] error safe for casting integer to other types per
 pg_cast

 

 
-- 
2.34.1

From 32a42d09a153a0ded3c76bf5dfeffc4bfbee9f40 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v14 07/19] error safe for casting macaddr8 to other types per
 pg_cast

 

From b941b0cf4c0394d8da0c17a8d2cc19bfe43b5164 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v14 05/19] error safe for casting character varying to other
 types per pg_cast

From 17bd7516d9b51d8a88227b8d3de48c15b52c8a93 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v14 06/19] error safe for casting inet to other types per
 pg_cast

 
 
-- 
2.34.1

From 438d1e598bba5427813381976d5d12cdf2fdc108 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 25 Nov 2025 20:07:28 +0800
Subject: [PATCH v14 04/19] error safe for casting character to other types per
 pg_cast

 

 

diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index f202b8df4e2..c4b8f61f5d6 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2695,6 +2695,12 @@ name_text(PG_FUNCTION_ARGS)
  */
 List *
 textToQualifiedNameList(text *textval)
+{
+	return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
 {
 	char	   *rawname;
 	List	   *result = NIL;
@@ -2706,12 +2712,12 @@ textToQualifiedNameList(text *textval)
 	rawname = text_to_cstring(textval);

 	if (namelist == NIL)
-		ereport(ERROR,

 	return (xmltype *) data;
 #else

From 6835b9c6833b353f764f1d68e67e0692eb9cb3c1 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v14 03/19] error safe for casting character to other types per
 pg_cast

index 5e7bd933afc..1d88cdd2cb4 100644

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;

From ca1c431c9e88f19c8badbf97d506a13f0a5c12b0 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v14 01/19] error safe for casting bytea to other types per
 pg_cast

 

 

-- 
2.34.1

From 177f97dc0078adebc0a10d50cd88b3fa1557c64e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v14 02/19] error safe for casting bit/varbit to other types
 per pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
 
 	if (!isExplicit)
-		ereport(ERROR,

 
 
-- 
2.34.1

---

From: jian he <[email-masked]>
Date: Tue, 9 Dec 2025 11:39:11 +0800

On Mon, Dec 1, 2025 at 1:41 PM Corey Huinker <[email-masked]> wrote:
> No, I meant implementing the syntax for being able to declare a custom CAST function as safe (or not). Basically adding the [SAFE] to
>
> I'm not tied to this syntax choice, but this one seemed the most obvious and least invasive.
>

hi.
In v14, I have removed pg_cast.casterrorsafe, but for user-defined CREATE CAST,
castfunc can be built-in function or user-defined function, we do need a way to
distinguish if the cast function is error safe or not.

I’ll incorporate pg_cast.casterrorsafe along with the implementation of the
user-defined CREATE CAST syntax into one patch.

> But this brings up an interesting point: if a cast is declared as WITHOUT FUNCTION aka COERCION_METHOD_BINARY, then the cast can never fail, and we should probably check for that because a cast that cannot fail can ignore the DEFAULT clause altogether and fall back to being an ordinary CAST().
>
>>

integer and oid are binary coercible, but the following should fail.
SELECT CAST(11 as oid DEFAULT 'a' ON CONVERSION ERROR);

if you mean that skip
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
For binary-coercible types, this approach seems fine. We’ve done something
similar in ExecInitExprRec for T_ArrayCoerceExpr.
```
                if (elemstate->steps_len == 1 &&
                    elemstate->steps[0].opcode == EEOP_CASE_TESTVAL)
                {
                    /* Trivial, so we need no per-element work at runtime */
                    elemstate = NULL;
                }
```
in V14, I didn't do this part, I'll keep this in mind.

---

From: jian he <[email-masked]>
Date: Wed, 10 Dec 2025 16:57:09 +0800

> > I'm not tied to this syntax choice, but this one seemed the most obvious and least invasive.

hi.
please see the attached v15.
the primary implementation of CAST DEFAULT is contained in V15-0021.

changes compared to v14.
1. separate patch (v15-0017) for float error.

2. separate patch (v15-0018) for newly added float8 functions:
float8_pl_safe
float8_mi_safe
float8_mul_safe
float8_div_safe
refactoring existing functions is too invasive, I choose not to.

3. refactor point_dt (v15-0019). This is necessary for making geometry data type
error-safe, separate from the main patch (v15-0020). I hope to make it easier to
review.
-static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext);

4. skip compile DEFAULT expression (ExecInitExprRec) for binary coercion cast,
as mentioned before.  See ExecInitSafeTypeCastExpr.

5.  Support user-defined type cast error-safe, see v15-0022.
user-defined error-safe cast syntax:
CREATE CAST (source_type AS target_type)
    WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]

this only adds a new keyword SAFE.
This works for C and internal language functions only now.
To make it really usable, I have made citext, hstore module castfunc error safe.
A new column: pg_cast.casterrorsafe was added, this is needed for
CREATE CAST WITH SAFE FUNCTION.

+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+       DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+

6. slightly polished the doc.

--
jian
https://www.enterprisedb.com/
From 99d299237edcb65151a94b7ab17497dad6d7b778 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 10 Dec 2025 16:15:37 +0800
Subject: [PATCH v15 22/22] error safe for user defined CREATE CAST

pg_cast.casterrorsafe column to indicate castfunc is error safe or not.

change src/include/catalog/pg_cast.dat to indicate that most of the system cast
function support soft error evaluation.

The SAFE keyword is introduced for allow user-defined CREATE CAST can also be
evaluated in soft-error. now the synopsis of CREATE CAST is:
CREATE CAST (source_type AS target_type)
    WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]

The following cast in citext, hstore module refactored to error safe:
CAST (bpchar AS citext)
CAST (boolean AS citext)
CAST (inet AS citext)
CAST (text[] AS hstore)
CAST (hstore AS json)
CAST (hstore AS jsonb)

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 contrib/citext/citext--1.4.sql            |   6 +-
 contrib/citext/expected/citext.out        |  24 +-
 contrib/citext/expected/citext_1.out      |  24 +-
 contrib/citext/sql/citext.sql             |   5 +-
 contrib/hstore/expected/hstore.out        |  37 +++
 contrib/hstore/hstore--1.2--1.3.sql       |   2 +-
 contrib/hstore/hstore--1.4.sql            |   6 +-
 contrib/hstore/hstore_io.c                |   8 +-
 contrib/hstore/sql/hstore.sql             |  11 +
 doc/src/sgml/catalogs.sgml                |  15 +
 doc/src/sgml/ref/create_cast.sgml         |  14 +-
 doc/src/sgml/syntax.sgml                  |   3 +-
 src/backend/catalog/pg_cast.c             |   4 +-
 src/backend/commands/functioncmds.c       |   9 +-
 src/backend/commands/typecmds.c           |   1 +
 src/backend/parser/gram.y                 |  18 +-
 src/backend/parser/parse_expr.c           |  10 +-
 src/include/catalog/pg_cast.dat           | 330 +++++++++++-----------
 src/include/catalog/pg_cast.h             |   5 +
 src/include/nodes/parsenodes.h            |   1 +
 src/include/parser/kwlist.h               |   1 +
 src/test/regress/expected/create_cast.out |   8 +-
 src/test/regress/expected/opr_sanity.out  |  24 +-
 src/test/regress/sql/create_cast.sql      |   5 +
 24 files changed, 353 insertions(+), 218 deletions(-)

diff --git a/contrib/citext/citext--1.4.sql b/contrib/citext/citext--1.4.sql
index 7b061989352..5c87820388f 100644
--- a/contrib/citext/citext--1.4.sql
+++ b/contrib/citext/citext--1.4.sql
@@ -85,9 +85,9 @@ CREATE CAST (citext AS varchar) WITHOUT FUNCTION AS IMPLICIT;
 CREATE CAST (citext AS bpchar)  WITHOUT FUNCTION AS ASSIGNMENT;
 CREATE CAST (text AS citext)    WITHOUT FUNCTION AS ASSIGNMENT;
 CREATE CAST (varchar AS citext) WITHOUT FUNCTION AS ASSIGNMENT;
-CREATE CAST (bpchar AS citext)  WITH FUNCTION citext(bpchar)  AS ASSIGNMENT;
-CREATE CAST (boolean AS citext) WITH FUNCTION citext(boolean) AS ASSIGNMENT;
-CREATE CAST (inet AS citext)    WITH FUNCTION citext(inet)    AS ASSIGNMENT;
+CREATE CAST (bpchar AS citext)  WITH SAFE FUNCTION citext(bpchar)  AS ASSIGNMENT;
+CREATE CAST (boolean AS citext) WITH SAFE FUNCTION citext(boolean) AS ASSIGNMENT;
+CREATE CAST (inet AS citext)    WITH SAFE FUNCTION citext(inet)    AS ASSIGNMENT;
 
 --
 -- Operator Functions.
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 33da19d8df4..be328715492 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,11 +10,12 @@ 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 when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
-                    ^
-HINT:  Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext 
+--------
+ abc
+(1 row)
+
 -- Test the operators and indexing functions
 -- Test = and <>.
 SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
  t
 (1 row)
 
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext 
+--------
+ true
+(1 row)
+
 SELECT 'true'::citext::boolean = true AS t;
  t 
 ---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
  t
 (1 row)
 
+SELECT CAST(inet '192.168.100.128' AS citext
+           DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t 
+---
+ t
+(1 row)
+
 SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
  t 
 ---
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index 647eea19142..e9f8454c662 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,11 +10,12 @@ 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 when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
-                    ^
-HINT:  Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext 
+--------
+ abc
+(1 row)
+
 -- Test the operators and indexing functions
 -- Test = and <>.
 SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
  t
 (1 row)
 
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext 
+--------
+ true
+(1 row)
+
 SELECT 'true'::citext::boolean = true AS t;
  t 
 ---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
  t
 (1 row)
 
+SELECT CAST(inet '192.168.100.128' AS citext
+           DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t 
+---
+ t
+(1 row)
+
 SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
  t 
 ---
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index 99794497d47..62f83d749f9 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,7 +9,7 @@ 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
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);

@@ -165,6 +165,7 @@ SELECT name FROM srt WHERE name SIMILAR TO '%A.*';
 
 -- Explicit casts.
 SELECT true::citext = 'true' AS t;
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
 SELECT 'true'::citext::boolean = true AS t;
 
 SELECT 4::citext = '4' AS t;
@@ -224,6 +225,8 @@ SELECT '192.168.100.128/25'::citext::cidr = '192.168.100.128/25'::cidr AS t;
 
 SELECT '192.168.100.128'::inet::citext = '192.168.100.128/32' AS t;
 SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
+SELECT CAST(inet '192.168.100.128' AS citext
+           DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
 
 SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
 SELECT '08:00:2b:01:02:03'::citext::macaddr = '08:00:2b:01:02:03'::macaddr AS t;
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index 1836c9acf39..2622137cbf9 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -841,12 +841,28 @@ select '{}'::text[]::hstore;
 
 select ARRAY['a','g','b','h','asd']::hstore;
 ERROR:  array must have even number of elements
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+       DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
 select ARRAY['a','g','b','h','asd','i']::hstore;
              array              
 --------------------------------
  "a"=>"g", "b"=>"h", "asd"=>"i"
 (1 row)
 
+select ARRAY['a','g','b','h',null,'i']::hstore;
+ERROR:  null value not allowed for hstore key
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+       DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
 select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
              array              
 --------------------------------
@@ -855,6 +871,13 @@ select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
 
 select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
 ERROR:  array must have two columns
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+       DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
 select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
 ERROR:  wrong number of array subscripts
 select hstore('{}'::text[]);
@@ -1553,6 +1576,13 @@ select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
  {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
 (1 row)
 
+select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+            default null on conversion error);
+                                              json                                               
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
 select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
                                             hstore_to_json_loose                                             
 -------------------------------------------------------------------------------------------------------------
@@ -1571,6 +1601,13 @@ select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
  {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
 (1 row)
 
+select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+            default null on conversion error);
+                                              jsonb                                              
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
 select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
                                           hstore_to_jsonb_loose                                           
 ----------------------------------------------------------------------------------------------------------
diff --git a/contrib/hstore/hstore--1.2--1.3.sql b/contrib/hstore/hstore--1.2--1.3.sql
index 0a7056015b7..cbb1a139e69 100644
--- a/contrib/hstore/hstore--1.2--1.3.sql
+++ b/contrib/hstore/hstore--1.2--1.3.sql
@@ -9,7 +9,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
 LANGUAGE C IMMUTABLE STRICT;
 
 CREATE CAST (hstore AS jsonb)
-  WITH FUNCTION hstore_to_jsonb(hstore);
+  WITH SAFE FUNCTION hstore_to_jsonb(hstore);
 
 CREATE FUNCTION hstore_to_jsonb_loose(hstore)
 RETURNS jsonb
diff --git a/contrib/hstore/hstore--1.4.sql b/contrib/hstore/hstore--1.4.sql
index 4294d14ceb5..451c2ed8187 100644
--- a/contrib/hstore/hstore--1.4.sql
+++ b/contrib/hstore/hstore--1.4.sql
@@ -232,7 +232,7 @@ AS 'MODULE_PATHNAME', 'hstore_from_array'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE CAST (text[] AS hstore)
-  WITH FUNCTION hstore(text[]);
+  WITH SAFE FUNCTION hstore(text[]);
 
 CREATE FUNCTION hstore_to_json(hstore)
 RETURNS json
@@ -240,7 +240,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_json'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE CAST (hstore AS json)
-  WITH FUNCTION hstore_to_json(hstore);
+  WITH SAFE FUNCTION hstore_to_json(hstore);
 
 CREATE FUNCTION hstore_to_json_loose(hstore)
 RETURNS json
@@ -253,7 +253,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE CAST (hstore AS jsonb)
-  WITH FUNCTION hstore_to_jsonb(hstore);
+  WITH SAFE FUNCTION hstore_to_jsonb(hstore);
 
 CREATE FUNCTION hstore_to_jsonb_loose(hstore)
 RETURNS jsonb
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 34e3918811c..f5166679783 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -738,20 +738,20 @@ hstore_from_array(PG_FUNCTION_ARGS)
 
 		case 1:
 			if ((ARR_DIMS(in_array)[0]) % 2)
-				ereport(ERROR,
+				ereturn(fcinfo->context, (Datum) 0,
 						(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
 						 errmsg("array must have even number of elements")));
 			break;
 
 		case 2:
 			if ((ARR_DIMS(in_array)[1]) != 2)
-				ereport(ERROR,
+				ereturn(fcinfo->context, (Datum) 0,
 						(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
 						 errmsg("array must have two columns")));
 			break;
 
 		default:
-			ereport(ERROR,
+				ereturn(fcinfo->context, (Datum) 0,
 					(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
 					 errmsg("wrong number of array subscripts")));
 	}
@@ -772,7 +772,7 @@ hstore_from_array(PG_FUNCTION_ARGS)
 	for (i = 0; i < count; ++i)
 	{
 		if (in_nulls[i * 2])
-			ereport(ERROR,
+			ereturn(fcinfo->context, (Datum) 0,
 					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
 					 errmsg("null value not allowed for hstore key")));
 
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index efef91292a3..8fa46630d6d 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -192,9 +192,16 @@ select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']))
 -- array input
 select '{}'::text[]::hstore;
 select ARRAY['a','g','b','h','asd']::hstore;
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+       DEFAULT NULL ON CONVERSION ERROR);
 select ARRAY['a','g','b','h','asd','i']::hstore;
+select ARRAY['a','g','b','h',null,'i']::hstore;
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+       DEFAULT NULL ON CONVERSION ERROR);
 select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
 select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+       DEFAULT NULL ON CONVERSION ERROR);
 select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
 select hstore('{}'::text[]);
 select hstore(ARRAY['a','g','b','h','asd']);
@@ -363,10 +370,14 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
 -- json and jsonb
 select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
 select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
+select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+            default null on conversion error);
 select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
 
 select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
 select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
+select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+            default null on conversion error);
 select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
 
 create table test_json_agg (f1 text, f2 hstore);
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2fc63442980..8fca3534f32 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,21 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>casterrorsafe</structfield> <type>bool</type>
+       </para>
+       <para>
+        This flag indicates whether the <structfield>castfunc</structfield> function
+        is error-safe. It is meaningful only when <structfield>castfunc</structfield>
+        is not zero. User-defined casts can set it
+        to <literal>true</literal> via <link linkend="sql-createcast">CREATE CAST</link>.
+        For error-safe type cast, see <xref linkend="sql-syntax-type-casts-safe"/>.
+       </para>
+      </entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/create_cast.sgml b/doc/src/sgml/ref/create_cast.sgml
index bad75bc1dce..888d7142e42 100644
--- a/doc/src/sgml/ref/create_cast.sgml
+++ b/doc/src/sgml/ref/create_cast.sgml
@@ -22,7 +22,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
-    WITH FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
+    WITH [SAFE] FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
     [ AS ASSIGNMENT | AS IMPLICIT ]
 
 CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
@@ -194,6 +194,18 @@ SELECT CAST ( 2 AS numeric ) + 4.0;
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>SAFE</literal></term>
+     <listitem>
+      <para>
+       The function used to perform the cast support soft-error evaluation,
+       Currently, only functions written in C or the internal language are supported.
+       An alternate expression can be specified to be evaluated if the cast
+       error occurs.  See <link linkend="sql-syntax-type-casts-safe">safe type cast</link>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><literal><replaceable>function_name</replaceable>[(<replaceable>argument_type</replaceable> [, ...])]</literal></term>
 
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 32af9ea061c..24d1dc6de0b 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2176,8 +2176,7 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
 </synopsis>
     If the type cast fails, instead of error out, evaluation falls back to the
     default <replaceable>expression</replaceable> specified in the <literal>ON ERROR</literal> clause.
-    At present, this only support built-in type casts; see <xref linkend="catalog-pg-cast"/>.
-    User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+    User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link> are supported too.
   </para>
 
     <para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..4116b1708b0 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -48,7 +48,8 @@
 ObjectAddress
 CastCreate(Oid sourcetypeid, Oid targettypeid,
 		   Oid funcid, Oid incastid, Oid outcastid,
-		   char castcontext, char castmethod, DependencyType behavior)
+		   char castcontext, char castmethod, bool casterrorsafe,
+		   DependencyType behavior)
 {
 	Relation	relation;
 	HeapTuple	tuple;
@@ -84,6 +85,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
 	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
 	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
 	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+	values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(casterrorsafe);

diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 59d00638ee6..2f7bdf26d37 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1667,6 +1667,13 @@ CreateCast(CreateCastStmt *stmt)
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("cast function must not return a set")));
 
+		if (stmt->safe &&
+			procstruct->prolang != INTERNALlanguageId &&
+			procstruct->prolang != ClanguageId)
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("Safe type cast functions are only supported for C and internal languages"));
+
 		ReleaseSysCache(tuple);
 	}
 	else
@@ -1795,7 +1802,7 @@ CreateCast(CreateCastStmt *stmt)
 	}
 
 	myself = CastCreate(sourcetypeid, targettypeid, funcid, incastid, outcastid,
-						castcontext, castmethod, DEPENDENCY_NORMAL);
+						castcontext, castmethod, stmt->safe, DEPENDENCY_NORMAL);
 	return myself;
 }
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 47d5047fe8b..6fce17ce02b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1754,6 +1754,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 	/* Create cast from the range type to its multirange type */
 	CastCreate(typoid, multirangeOid, castFuncOid, InvalidOid, InvalidOid,
 			   COERCION_CODE_EXPLICIT, COERCION_METHOD_FUNCTION,
+			   false,
 			   DEPENDENCY_INTERNAL);
 
 	pfree(multirangeArrayName);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8a27e045bc0..e7ff7e5c275 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -353,7 +353,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <defelt>	drop_option
 %type <boolean>	opt_or_replace opt_no
 				opt_grant_grant_option
-				opt_nowait opt_if_exists opt_with_data
+				opt_nowait opt_safe opt_if_exists opt_with_data
 				opt_transaction_chain
 %type <list>	grant_role_opt_list
 %type <defelt>	grant_role_opt
@@ -774,7 +774,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
-	SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+	SAFE SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
 	SEQUENCE SEQUENCES
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SOURCE SQL_P STABLE STANDALONE_P
@@ -9291,14 +9291,15 @@ dostmt_opt_item:
  *****************************************************************************/
 
 CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
-					WITH FUNCTION function_with_argtypes cast_context
+					WITH opt_safe FUNCTION function_with_argtypes cast_context
 				{
 					CreateCastStmt *n = makeNode(CreateCastStmt);
 
 					n->sourcetype = $4;
 					n->targettype = $6;
-					n->func = $10;
-					n->context = (CoercionContext) $11;
+					n->safe = $9;
+					n->func = $11;
+					n->context = (CoercionContext) $12;
 					n->inout = false;
 					$$ = (Node *) n;
 				}
@@ -9309,6 +9310,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = false;
@@ -9321,6 +9323,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = true;
@@ -9333,6 +9336,9 @@ cast_context:  AS IMPLICIT_P					{ $$ = COERCION_IMPLICIT; }
 		| /*EMPTY*/								{ $$ = COERCION_EXPLICIT; }
 		;
 
+opt_safe: SAFE						{ $$ = true; }
+		| /*EMPTY*/					{ $$ = false; }
+		;
 
 DropCastStmt: DROP CAST opt_if_exists '(' Typename AS Typename ')' opt_drop_behavior
 				{
@@ -18108,6 +18114,7 @@ unreserved_keyword:
 			| ROUTINES
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
@@ -18744,6 +18751,7 @@ bare_label_keyword:
 			| ROW
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7d3ed0eb890..a350c7edd11 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -3051,7 +3051,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid
 {
 	HeapTuple	tuple;
 	bool 	errorsafe = true;
-	bool	userdefined = false;
 	Oid		inputBaseType;
 	Oid		targetBaseType;
 	Oid 	inputElementType;
@@ -3123,11 +3122,8 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid
 		{
 			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
 
-			if (castForm->oid > FirstUnpinnedObjectId)
-			{
+			if (OidIsValid(castForm->castfunc) && !castForm->casterrorsafe)
 				errorsafe = false;
-				userdefined = true;
-			}
 
 			ReleaseSysCache(tuple);
 		}
@@ -3141,9 +3137,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid
 					   format_type_be(targetType),
 					   "DEFAULT",
 					   "CAST ... ON CONVERSION ERROR"),
-				userdefined
-				? errhint("Safe type cast for user-defined types are not yet supported")
-				: errhint("Explicit cast is defined but definition is not error safe"),
+				errhint("Explicit cast is defined but definition is not error safe"),
 				parser_errposition(pstate, exprLocation(sourceexpr)));
 }

 

@@ -302,57 +302,57 @@

 
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
index 6a0ca337153..bf47544f675 100644
 
 	/* cast method */
 	char		castmethod;
+
 
 /* ----------------
@@ -101,6 +105,7 @@ extern ObjectAddress CastCreate(Oid sourcetypeid,
 								Oid outcastid,
 								char castcontext,
 								char castmethod,
+								bool casterrorsafe,
 								DependencyType behavior);
 
 #endif							/* PG_CAST_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 82b0fb83b4b..e99499196cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4155,6 +4155,7 @@ typedef struct CreateCastStmt
 	ObjectWithArgs *func;
 	CoercionContext context;
 	bool		inout;
+	bool		safe;
 } CreateCastStmt;
 
 /* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5d4fe27ef96..f2ff6091fd2 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -396,6 +396,7 @@ PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("safe", SAFE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0054ed0ef67..1e32c041b9f 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -81,6 +81,12 @@ NOTICE:  drop cascades to cast from integer to casttesttype
 -- Try it with a function that requires an implicit cast
 CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
 $$ SELECT ('bar'::text || $1::text); $$;
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+ERROR:  Safe type cast functions are only supported for C and internal languages
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
+ERROR:  Safe type cast functions are only supported for C and internal languages
 CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
 SELECT 1234::int4::casttesttype; -- Should work now
  casttesttype 
@@ -92,7 +98,7 @@ SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- err
 ERROR:  cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
 LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
                     ^
-HINT:  Safe type cast for user-defined types are not yet supported
+HINT:  Explicit cast is defined but definition is not error safe
 -- 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/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
 FROM pg_cast c
 WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
     OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod 
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe 
+-----+------------+------------+----------+-------------+------------+---------------
 (0 rows)

 
 SELECT c.*

 SELECT c.*
 
 -- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 0a15a795d87..30a0ff077c9 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -60,6 +60,11 @@ DROP FUNCTION int4_casttesttype(int4) CASCADE;
 CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
 $$ SELECT ('bar'::text || $1::text); $$;
 
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
 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
-- 
2.34.1

From 35715cfacd3ee194601443ba00dcac65630454d5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 10 Dec 2025 12:00:54 +0800
Subject: [PATCH v15 19/22] refactor point_dt

point_dt used in many places, it will be used in later on patch, thus
refactoring make it error safe.

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/geo_ops.c | 77 +++++++++++++++++++--------------
 1 file changed, 44 insertions(+), 33 deletions(-)

index 9101a720744..ad6ba1d6621 100644
@@ -82,7 +82,7 @@ static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
 
@@ -839,7 +839,7 @@ box_distance(PG_FUNCTION_ARGS)
 	box_cn(&a, box1);
 	box_cn(&b, box2);

@@ -1808,7 +1808,7 @@ path_length(PG_FUNCTION_ARGS)
 			iprev = path->npts - 1; /* include the closure segment */
 		}

 	PG_RETURN_FLOAT8(result);
@@ -1995,13 +1995,24 @@ point_distance(PG_FUNCTION_ARGS)
 	Point	   *pt1 = PG_GETARG_POINT_P(0);
 	Point	   *pt2 = PG_GETARG_POINT_P(1);

 static inline float8
 
 Datum
@@ -2173,7 +2184,7 @@ lseg_length(PG_FUNCTION_ARGS)
 {
 	LSEG	   *lseg = PG_GETARG_LSEG_P(0);

 /*----------------------------------------------------------
@@ -2258,8 +2269,8 @@ lseg_lt(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);

 Datum
@@ -2268,8 +2279,8 @@ lseg_le(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);

 Datum
@@ -2278,8 +2289,8 @@ lseg_gt(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);

 Datum
@@ -2288,8 +2299,8 @@ lseg_ge(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);

@@ -2743,7 +2754,7 @@ line_closept_point(Point *result, LINE *line, Point *point)
 	if (result != NULL)
 		*result = closept;

 Datum
@@ -2784,7 +2795,7 @@ lseg_closept_point(Point *result, LSEG *lseg, Point *pt)
 	if (result != NULL)
 		*result = closept;

 Datum
@@ -3108,9 +3119,9 @@ on_pl(PG_FUNCTION_ARGS)
 static bool
 
 Datum
@@ -3176,11 +3187,11 @@ on_ppath(PG_FUNCTION_ARGS)
 	if (!path->closed)
 	{
 		n = path->npts - 1;
@@ -4766,7 +4777,7 @@ circle_overlap(PG_FUNCTION_ARGS)

@@ -4828,7 +4839,7 @@ circle_contained(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle1 = PG_GETARG_CIRCLE_P(0);
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);

@@ -4840,7 +4851,7 @@ circle_contain(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle1 = PG_GETARG_CIRCLE_P(0);
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);

@@ -5069,7 +5080,7 @@ circle_distance(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);
 	float8		result;
 
@@ -5085,7 +5096,7 @@ circle_contain_pt(PG_FUNCTION_ARGS)

@@ -5097,7 +5108,7 @@ pt_contained_circle(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle = PG_GETARG_CIRCLE_P(1);
 	float8		d;

@@ -5112,7 +5123,7 @@ dist_pc(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle = PG_GETARG_CIRCLE_P(1);
 	float8		result;
 
-	result = float8_mi(point_dt(point, &circle->center),
+	result = float8_mi(point_dt(point, &circle->center, NULL),
 					   circle->radius);
 	if (result < 0.0)
 		result = 0.0;
@@ -5130,7 +5141,7 @@ dist_cpoint(PG_FUNCTION_ARGS)
 	Point	   *point = PG_GETARG_POINT_P(1);
 	float8		result;

@@ -5215,7 +5226,7 @@ box_circle(PG_FUNCTION_ARGS)
 	circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
 	circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
 
-	circle->radius = point_dt(&circle->center, &box->high);
+	circle->radius = point_dt(&circle->center, &box->high, NULL);
 
 	PG_RETURN_CIRCLE_P(circle);
 }
@@ -5299,7 +5310,7 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
 
 	for (i = 0; i < poly->npts; i++)
 		result->radius = float8_pl(result->radius,
-								   point_dt(&poly->p[i], &result->center));
+								   point_dt(&poly->p[i], &result->center, NULL));
 	result->radius = float8_div(result->radius, poly->npts);
 }
 
-- 
2.34.1

From 89539ec5c8663872ad954e98d435f3d4e321b7d0 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 10 Dec 2025 13:35:42 +0800
Subject: [PATCH v15 20/22] error safe for casting geometry data type

already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box,
circle_center
This patch make these functions error safe: path_poly, lseg_center, box_center,
box_circle, poly_center, poly_circle, circle_box

can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=[email-masked]
---
 src/backend/utils/adt/geo_ops.c | 194 +++++++++++++++++++++++++-------
 1 file changed, 153 insertions(+), 41 deletions(-)

diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index ad6ba1d6621..abb401a913a 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -77,7 +77,8 @@ enum path_delim

 /* Routines for boxes */
 
@@ -836,8 +837,8 @@ box_distance(PG_FUNCTION_ARGS)
 	Point		a,
 				b;
 
-	box_cn(&a, box1);
-	box_cn(&b, box2);
 
 	PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
 }
@@ -851,7 +852,9 @@ box_center(PG_FUNCTION_ARGS)
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	Point	   *result = (Point *) palloc(sizeof(Point));
 
-	box_cn(result, box);

-
 /*		box_wd	-		returns the width (length) of the box
  *								  (horizontal magnitude).
  */
@@ -2328,13 +2344,31 @@ lseg_center(PG_FUNCTION_ARGS)
 {
 	LSEG	   *lseg = PG_GETARG_LSEG_P(0);
 	Point	   *result;
+	float8		x;
+	float8		y;

 

@@ -3288,7 +3322,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
 
 	if (result != NULL)
 	{
-		box_cn(&point, box);
 
@@ -4119,11 +4153,20 @@ construct_point(PG_FUNCTION_ARGS)

 static inline void
 
 Datum
@@ -4135,7 +4178,7 @@ point_add(PG_FUNCTION_ARGS)

 
 	PG_RETURN_POINT_P(result);
 }
@@ -4247,8 +4290,8 @@ box_add(PG_FUNCTION_ARGS)

 
 	PG_RETURN_BOX_P(result);
 }
@@ -4411,7 +4454,7 @@ path_add_pt(PG_FUNCTION_ARGS)
 	int			i;

 	PG_RETURN_PATH_P(path);
 }
@@ -4469,7 +4512,7 @@ path_poly(PG_FUNCTION_ARGS)

@@ -4519,7 +4562,9 @@ poly_center(PG_FUNCTION_ARGS)

 
 	PG_RETURN_POINT_P(result);
@@ -4981,7 +5026,7 @@ circle_add_pt(PG_FUNCTION_ARGS)

 
 	PG_RETURN_CIRCLE_P(result);
@@ -5202,14 +5247,30 @@ circle_box(PG_FUNCTION_ARGS)

 
 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()
@@ -5220,15 +5281,35 @@ box_circle(PG_FUNCTION_ARGS)
 {
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	CIRCLE	   *circle;
+	float8		x;
+	float8		y;

 
-	circle->radius = point_dt(&circle->center, &box->high, NULL);

@@ -5292,10 +5373,11 @@ circle_poly(PG_FUNCTION_ARGS)
 
 	Assert(poly->npts > 0);
 
@@ -5304,14 +5386,43 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
 	result->radius = 0;

-								   point_dt(&poly->p[i], &result->center, NULL));
 
 Datum
@@ -5322,7 +5433,8 @@ poly_circle(PG_FUNCTION_ARGS)

 

From 72fa0fc1a8a154b68442d2a8731bb82e39eff4fa Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 10 Dec 2025 11:55:40 +0800
Subject: [PATCH v15 18/22] introduce float8 safe function

this patch introduce the following function:
float8_pl_safe
float8_mi_safe
float8_mul_safe
float8_div_safe

refactoring existing function is be too invasive.  thus add these new functions.
It's close to existing non-safe version functions.

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/include/utils/float.h | 79 ++++++++++++++++++++++++++++-----------
 1 file changed, 58 insertions(+), 21 deletions(-)

diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 1d0cb026d4e..f46861ab5c3 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,16 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
 	return result;
 }
 
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+	float8		result;
+
+	result = val1 + val2;
+	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+	{
+		float_overflow_error(escontext);
+		return 0.0;
+	}
+	return result;
+}
+
 static inline float8
 float8_pl(const float8 val1, const float8 val2)
 {
-	float8		result;
-
-	result = val1 + val2;
-	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error(NULL);
-
-	return result;
+	return float8_pl_safe(val1, val2, NULL);;
 }
 
 static inline float4
@@ -133,16 +141,24 @@ float4_mi(const float4 val1, const float4 val2)
 	return result;
 }
 
+static inline float8
-		float_overflow_error(NULL);
 
 static inline float4
@@ -160,19 +176,33 @@ float4_mul(const float4 val1, const float4 val2)
 }
 
 static inline float8
 
 	result = val1 * val2;
 	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error(NULL);
+	{
+		float_overflow_error(escontext);
+		return 0.0;
+	}
+
 	if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
-		float_underflow_error(NULL);
+	{
+		float_underflow_error(escontext);
+		return 0.0;
+	}
 
 	return result;
 }
 
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+	return float8_mul_safe(val1, val2, NULL);
+}
+
+
 static inline float4
 float4_div(const float4 val1, const float4 val2)
 {
@@ -190,21 +220,28 @@ float4_div(const float4 val1, const float4 val2)
 }
 
 static inline float8
 
 	if (unlikely(val2 == 0.0) && !isnan(val1))
-		float_zero_divide_error(NULL);
+		float_zero_divide_error(escontext);
 	result = val1 / val2;
 	if (unlikely(isinf(result)) && !isinf(val1))
-		float_overflow_error(NULL);
+		float_overflow_error(escontext);
 	if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
-		float_underflow_error(NULL);
+		float_underflow_error(escontext);
 
 	return result;
 }
 
+static inline float8

From 08a0595584f9540a54586cfce0c50ea410a592bc Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 10 Dec 2025 16:10:38 +0800
Subject: [PATCH v15 21/22] CAST(expr AS newtype DEFAULT ON ERROR)

* With this patchset, most functions in pg_cast.castfunc are now error-safe.
* CoerceViaIO and CoerceToDomain were already error-safe in the HEAD.
* this patch extends error-safe behavior to ArrayCoerceExpr.
* We also ensure that when a coercion fails, execution falls back to evaluating
the specified default node.
* The doc has been refined, though it may still need more review.

demo:

 contrib/citext/expected/citext.out            |   5 +
 src/backend/executor/execExpr.c               | 109 ++-
 src/backend/utils/fmgr/fmgr.c                 |  14 +
 40 files changed, 2179 insertions(+), 45 deletions(-)

diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 8c0bf54f0f3..33da19d8df4 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
 --------+---------
 (0 rows)

 
 
 -- Test = and <>.
 
+--error safe type cast

+--error safe type cast
index 34c83880a66..32af9ea061c 100644
 
    <para>
@@ -2160,6 +2164,32 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
      <xref linkend="sql-createcast"/>.
     </para>
    </note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+  <title>Safe Type Cast</title>
+   <para>
+    A type cast may occasionally fail. To guard against such failures, you can
+    provide an <literal>ON ERROR</literal> clause to handle potential errors.
+    The syntax for safe type cast is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+    If the type cast fails, instead of error out, evaluation falls back to the
+    default <replaceable>expression</replaceable> specified in the <literal>ON ERROR</literal> clause.
+    At present, this only support built-in type casts; see <xref linkend="catalog-pg-cast"/>.
+    User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+  </para>
+
+    <para>
+    Some examples:
+<screen>
+SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
+SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1.1</computeroutput>
+</screen>
+    </para>
+  </sect3>
   </sect2>
 
index b05ff476a63..e6738c0ae75 100644
 
+/*

 

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4741,6 +4794,60 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+		 * Steps to evaluate the DEFAULT expression.  Skip it if this is a
+		 * binary coercion cast.
+		if (!IsA(stcexpr->cast_expr, RelabelType))

 /*

 /*

 
 
 /*
 
 	/*
 	 * And evaluate it.

 			break;

 
 	/*
 
 		/*
 
 		/*

 

 
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
+		case T_SafeTypeCast:
 
 	/*

 		if (coerce_hard)
 		{
 
+/*
index 7713bdc6af0..fef83ce73b4 100644
 
+/* error-safe version of stringTypeDatum */
+bool

index 0fe63c6bb83..c9e5d0de8ed 100644
@@ -1759,6 +1759,20 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
 
+/* error-safe version of OidInputFunctionCall */
+bool

 
+/*
 
+/*
+ * SafeTypeCastExpr -

 

 
 # ----------

 

From e9c2d202693dbea0610b30dc32ee75c11548d564 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v15 16/22] error safe for casting jsonb to other types per
 pg_cast

 

 
 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From d357e4541ca62c39982e886df927184714e9d088 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 9 Dec 2025 14:36:39 +0800
Subject: [PATCH v15 17/22] refactor
 float_overflow_error,float_underflow_error,float_zero_divide_error

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 contrib/btree_gist/btree_float4.c |   2 +-
 contrib/btree_gist/btree_float8.c |   4 +-
 src/backend/utils/adt/float.c     | 104 +++++++++++++++---------------
 src/include/utils/float.h         |  34 +++++-----
 4 files changed, 72 insertions(+), 72 deletions(-)

 	r = a - b;

 	r = arg1 - arg2;

 	r = a - b;

 pg_noinline void
 
 pg_noinline void
 
 	result = sqrt(arg1);

 	result = cbrt(arg1);

 
 
 	result = log(arg1);

 	result = log10(arg1);

 	result = acos(arg1);

 	result = asin(arg1);

 

 

 

 

 

 

 

 

 
 
 	/*
 
 			Sxx = get_float8_nan();
 		}
 
 			Sxx = get_float8_nan();
 		}
 
 			if (isinf(Sxx))
 				Sxx = get_float8_nan();
index fc2a9cf6475..1d0cb026d4e 100644
@@ -104,7 +104,7 @@ float4_pl(const float4 val1, const float4 val2)
 
 	result = val1 + val2;
 
 	return result;
 }
@@ -116,7 +116,7 @@ float8_pl(const float8 val1, const float8 val2)
 
 	result = val1 + val2;
 
 	return result;
 }
@@ -128,7 +128,7 @@ float4_mi(const float4 val1, const float4 val2)
 
 	result = val1 - val2;
 
 	return result;
 }
@@ -140,7 +140,7 @@ float8_mi(const float8 val1, const float8 val2)
 
 	result = val1 - val2;
 
 	return result;
 }
@@ -152,9 +152,9 @@ float4_mul(const float4 val1, const float4 val2)
 
 	result = val1 * val2;
 
 	return result;
 }
@@ -166,9 +166,9 @@ float8_mul(const float8 val1, const float8 val2)
 
 	result = val1 * val2;
 
 	return result;
 }
@@ -179,12 +179,12 @@ float4_div(const float4 val1, const float4 val2)
 	float4		result;

 	return result;
 }
@@ -195,12 +195,12 @@ float8_div(const float8 val1, const float8 val2)
 	float8		result;

 	return result;
 }
-- 
2.34.1

From 6dce08e996087a3bba2f766b74b584fef78db9c1 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v15 15/22] error safe for casting timestamp to other types per
 pg_cast

 

 
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 978901cde740075e912ea9a24a82e20f1ec8798a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v15 14/22] error safe for casting timestamptz to other types
 per pg_cast

 

 

 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 5f38709a59f0e7a00ec09adb9c935056f78d4028 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v15 13/22] error safe for casting interval to other types per
 pg_cast

 

 

From 42d29ceea40e0d1f006f59bfa3b4f55b3cbe454c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v15 12/22] error safe for casting date to other types per
 pg_cast

 
-/*

 

From 031b8a5ea41f3ac8ffb35235969b313af01e7ec3 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v15 11/22] error safe for casting float8 to other types per
 pg_cast

 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From bc44fa418746256b09c6c1d5ce7c839e10374aa2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v15 10/22] error safe for casting float4 to other types per
 pg_cast

 
 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From 1b8c0e0ce6c3d9f45c451de3ae155b07b81f51b8 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v15 09/22] error safe for casting numeric to other types per
 pg_cast

 

 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

 
 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From fd1a8676cf8ba7bff310337b97011a784d2174d4 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v15 07/22] error safe for casting integer to other types per
 pg_cast

 

 
-- 
2.34.1

From 4623763149ef4c2aaf822aaa0fc6bead2611e39c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v15 08/22] error safe for casting bigint to other types per
 pg_cast

 

 

-- 
2.34.1

From 287ab2b7d17c850b69ff295f774ed9da6817f84c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v15 05/22] error safe for casting inet to other types per
 pg_cast

 
 
-- 
2.34.1

From fef9b1d6ffb6b1e48c5536aefc0fd981b911fa05 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v15 06/22] error safe for casting macaddr8 to other types per
 pg_cast

 

From fbc8f5c3c53eafddc10b6d8d9b7f1ea2d67f3c30 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v15 04/22] error safe for casting character varying to other
 types per pg_cast

From 78f154c0f30f7cbc209916116638a245984c2a2e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v15 02/22] error safe for casting character to other types per
 pg_cast

 

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;

From 70416850ab1ef1ef8244e51b63e9bf9cbe335300 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v15 01/22] error safe for casting bit/varbit to other types
 per pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
 
 	if (!isExplicit)
-		ereport(ERROR,

 
 
-- 
2.34.1

From 7867024f1ec405db6aa0c91de715bd8707550f3f Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 25 Nov 2025 20:07:28 +0800
Subject: [PATCH v15 03/22] error safe for casting character to other types per
 pg_cast

 

 

 
 
 	if (namelist == NIL)
-		ereport(ERROR,

 	return (xmltype *) data;
 #else

---

From: Kirill Reshke <[email-masked]>
Date: Wed, 10 Dec 2025 18:32:06 +0500

> 6. slightly polished the doc.

Hi!

Overall, I think this patch is doing a good thing. Also, are we
holding it until the next SQL standard release, because sql/23 leaks
this feature?

Below are my 2c.

1)
First of all, I would prefer the `Bumps catversion` comment in the
commit msg of v15-0022.

2)
In v15-0006, if dont understand when memory allocated by
`result = (macaddr *) palloc0(sizeof(macaddr));` will be freed. Does
it persist until the query ends? I tried to get OOM with a query that
errors out macaddr8 casts repeatedly, but failed.

3)
 > * When error_safe set to true, we will evaluate the constant expression in a
 > * error safe way. If the evaluation fails, return NULL instead of throwing
 > * error.

Somebody has to say it - s/error_safe set/error_safe is set/, also
s/throwing error/throwing an error/

--
Best regards,
Kirill Reshke

---

From: Corey Huinker <[email-masked]>
Date: Wed, 10 Dec 2025 16:54:37 -0500

> holding it until the next SQL standard release, because sql/23 leaks

The impression that I get is that the SQL Standard has become more
descriptive and less prescriptive. It takes things that exist and
builds the standard around those, so by implementing this draft feature, we
help ensure that it makes it into the standard.

This might be counter-intuitive, but that's what dictionaries do: they
describe how the language is used (descriptive), rather than dictate how it
should be used (prescriptive).

---

From: jian he <[email-masked]>
Date: Mon, 15 Dec 2025 12:37:43 +0800

hi.

> First of all, I would prefer the `Bumps catversion` comment in the
ok.

the change is minor, changing ereport to ereturn.
The whole refactoring does not related to OOM errror,
OOM errors will behave exactly as they did previously.

> s/throwing error/throwing an error/
sure.

mainly rebase due to recent palloc_object, palloc_array conflict.

---

From: Corey Huinker <[email-masked]>
Date: Tue, 10 Mar 2026 12:22:25 -0400

On Wed, Jan 14, 2026 at 5:16 PM Corey Huinker <[email-masked]>
wrote:

>> > I am wondering how other places handle expression deparsing; do they
>> > hold the original expression along with the transformed expression?
>>
>
> In my initial implementation of this, I modified CoalesceExpr to ensure
> that the default clause was evaluated if and only if the initial expression
> failed.
>
>
>
>> rebased, many variable names have been renamed, comments adjusted.
>
>
> The new SAFE option on user defined cast functions looks good to me.
>
> I am concerned that adding _safe overhead to all cast functions will be
> received poorly, but if it is poorly received then reverting isn't that
> hard.
>
> I'm going to re-review the whole patch set, but I wanted to say that I
> like the main points of the changes so far.
>

I picked this back up again, pondered adding the IS CASTABLE AS syntax to
the patch, but ultimately decided that it should be a separate patch.

While I still think the patch order is a bit backwards [1], the order
chosen does have a sense to it, and whether or not that is the right order
is up to the committer. Please post a rebase so I can mark it as ready for
committer.

[1] I would have preferred adding the CAST functionality first, then
switching over each datatype one-by-one, thus demonstrating that non-safe
datatypes can co-exist with this new functionality. But that's just my
personal preference.

---

From: jian he <[email-masked]>
Date: Fri, 13 Mar 2026 09:00:13 +0800

On Wed, Mar 11, 2026 at 12:22 AM Corey Huinker <[email-masked]> wrote:
>
> While I still think the patch order is a bit backwards [1], the order chosen does have a sense to it, and whether or not that is the right order is up to the committer. Please post a rebase so I can mark it as ready for committer.
>
>
> [1] I would have preferred adding the CAST functionality first, then switching over each datatype one-by-one, thus demonstrating that non-safe datatypes can co-exist with this new functionality. But that's just my personal preference.

We can evaluate CoerceViaIO in an error-safe manner in the HEAD.
However, as discussed in this thread[1], we cannot simply take a FuncExpr
produced by a TYPE CAST, convert it to a CoerceViaIO node, and rely on
CoerceViaIO to do the actual soft-error evaluation.
Therefore to support the CAST(expr AS newtype DEFAULT expr ON CONVERSION ERROR)
syntax, refactoring the existing cast functions to make them error-safe is
really necessary.
Overall I think refactoring the existing cast function (replacing
ereport to ereturn or errsave) should be done first.

I did one more round of comment refactoring and variable renaming.

[1]: https://www.postgresql.org/message-id/CACJufxGw_OY7K3rfG4kDb902O2guhT-wgTjTJQ%3DpWeVWRTHpHQ%40mail.gmail.com

--
jian
https://www.enterprisedb.com/
From 1d2032c3cb0530aa0556a3ae557a82c6f4dd5252 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v19 09/23] error safe for casting bigint to other types per
 pg_cast

index 6c8fb7b7275..da392189743 100644

 

 
-- 
2.34.1

From a49f519bc7e2400b9a2882268f67b391e7f12ce1 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v19 08/23] error safe for casting integer to other types per
 pg_cast

index 3e4def68fe4..698863924ee 100644

diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index ff54d50ea9d..4c894a49d5d 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -379,7 +379,7 @@ i4toi2(PG_FUNCTION_ARGS)
 	int32		arg1 = PG_GETARG_INT32(0);

-- 
2.34.1

From 66aa9006ae1bb630fbc85054f280c4e2c6a5f444 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 12 Dec 2025 15:03:06 +0800
Subject: [PATCH v19 07/23] error safe for casting macaddr8 to other types per
 pg_cast

diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index c1bf9fd5783..0425ea473a5 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
 	result = palloc0_object(macaddr);

From 37732073efe79641b4858a90106c27ea1833f33c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 09:36:05 +0800
Subject: [PATCH v19 03/23] error safe for casting character to other types per
 pg_cast

diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 61ff5ddc74c..69c17e024f2 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4546,7 +4546,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
 
index df305098130..6f083973fe7 100644
index 79f6cf7b4fa..52f4f8fc490 100644

@@ -1029,19 +1029,25 @@ xmlelement(XmlExpr *xexpr,

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;
 
index 023fdeb4531..ca266f448d6 100644

From 501e0988e5c0e15ae46614800e278f0283360218 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v19 10/23] error safe for casting numeric to other types per
 pg_cast

index d25b8ad505d..d66fb86662f 100644

 
 	free_var(&var);
 
@@ -3018,7 +3020,10 @@ numeric_mul(PG_FUNCTION_ARGS)
 	Numeric		num2 = PG_GETARG_NUMERIC(1);
 	Numeric		res;

 	PG_RETURN_NUMERIC(res);
 }
@@ -4392,9 +4397,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
 Datum
 numeric_int4(PG_FUNCTION_ARGS)
 {
+	int32		result;
 	Numeric		num = PG_GETARG_NUMERIC(0);

 /*
@@ -4462,9 +4473,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
 Datum
 numeric_int8(PG_FUNCTION_ARGS)
 {
+	int64		result;
 	Numeric		num = PG_GETARG_NUMERIC(0);

@@ -4488,11 +4505,11 @@ numeric_int2(PG_FUNCTION_ARGS)
 	if (NUMERIC_IS_SPECIAL(num))
 	{
 		if (NUMERIC_IS_NAN(num))
-			ereport(ERROR,
+			ereturn(fcinfo->context, (Datum) 0,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert NaN to %s", "smallint")));
 		else
-			ereport(ERROR,
+			ereturn(fcinfo->context, (Datum) 0,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot convert infinity to %s", "smallint")));
 	}
@@ -4501,12 +4518,12 @@ numeric_int2(PG_FUNCTION_ARGS)
 	init_var_from_num(num, &x);

 
@@ -4571,10 +4588,14 @@ numeric_float8(PG_FUNCTION_ARGS)

 	PG_RETURN_DATUM(result);
 }
@@ -4666,7 +4687,14 @@ numeric_float4(PG_FUNCTION_ARGS)
 	tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
 											  NumericGetDatum(num)));

 	pfree(tmp);
 
-- 
2.34.1

From 77f6ffa7df7ff1824d8741002acc77aa84c2dac4 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v19 06/23] error safe for casting inet to other types per
 pg_cast

index 3a2002097dd..c7e0828764e 100644

-- 
2.34.1

From 57d5fddfdcae1136e9978d140a61584b4de0ad12 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v19 01/23] error safe for casting bytea to other types per
 pg_cast

diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index fd7662d41ee..a02b054b873 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1255,7 +1255,7 @@ bytea_int2(PG_FUNCTION_ARGS)

@@ -1280,7 +1280,7 @@ bytea_int4(PG_FUNCTION_ARGS)

@@ -1305,7 +1305,7 @@ bytea_int8(PG_FUNCTION_ARGS)

-- 
2.34.1

From a9d1b2261b80f035f4179925307d2972cc34227b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v19 02/23] error safe for casting bit/varbit to other types
 per pg_cast

index 65ad1bfe18f..c46b5bae70d 100644
 
 	if (!isExplicit)
-		ereport(ERROR,
 
 	if (!isExplicit)
-		ereport(ERROR,

 
 
-- 
2.34.1

From f67c0a07911aa5e859250b21056ccc8916f98ed3 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v19 05/23] error safe for casting character varying to other
 types per pg_cast

index 6f083973fe7..a62e55eec19 100644

From eecfd97d76b4ccff74481a7b5cb1a8168053b5c1 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 09:38:53 +0800
Subject: [PATCH v19 04/23] error safe for casting text to other types per
 pg_cast

index 56b87d878e8..19c8b586b55 100644
@@ -442,6 +442,16 @@ Oid
@@ -458,7 +468,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
@@ -515,7 +525,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
@@ -595,13 +605,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 
@@ -628,13 +648,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 
@@ -3624,6 +3654,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
 
@@ -3642,7 +3678,7 @@ makeRangeVarFromNameList(const List *names)
index ee34d1d85f8..604e19a1cb9 100644

 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 7caf700fd61..d0f241d37e4 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2715,6 +2715,12 @@ name_text(PG_FUNCTION_ARGS)
  */
 List *
 textToQualifiedNameList(text *textval)
+{
+	return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
 {
 	char	   *rawname;
 	List	   *result = NIL;
@@ -2726,12 +2732,12 @@ textToQualifiedNameList(text *textval)
 	rawname = text_to_cstring(textval);

 	if (namelist == NIL)
-		ereport(ERROR,
 
index 1a25973685c..153a439e374 100644
 
index 4b32574a075..5bc78aa02c0 100644

From cb7e728547f39fcce227823cf789b3b68ec3aa86 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v19 11/23] error safe for casting float4 to other types per
 pg_cast

index 641e7de21a0..f6633ded092 100644
 
 	/* Range check */

 	/* Range check */
 
index da392189743..8fd551bd52b 100644
 
 	/* Range check */
 
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index d66fb86662f..916053007ea 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4657,7 +4657,8 @@ float4_numeric(PG_FUNCTION_ARGS)
 	init_var(&result);

 
-- 
2.34.1

From 7186e33565a30a72d7e00253da6e7895aca0d2a9 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 5 Jan 2026 13:33:51 +0800
Subject: [PATCH v19 20/23] refactor point_dt

point_dt is used in multiple locations and will be needed by a later patch, thus
refactoring make it error safe.

discussion: https://postgr.es/m/CADkLM=[email-masked]
---
 src/backend/utils/adt/geo_ops.c | 79 +++++++++++++++++++--------------
 1 file changed, 46 insertions(+), 33 deletions(-)

index bfb4859b4cb..c655b015c14 100644

-	PG_RETURN_FLOAT8(point_dt(&a, &b));
+	PG_RETURN_FLOAT8(point_dt(&a, &b, fcinfo->context));
 }

@@ -1808,7 +1808,8 @@ path_length(PG_FUNCTION_ARGS)
 			iprev = path->npts - 1; /* include the closure segment */
 		}
 
-		result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i]));
+		result = float8_pl(result,
+						   point_dt(&path->p[iprev], &path->p[i], fcinfo->context));
 	}
 
 	PG_RETURN_FLOAT8(result);
@@ -1995,13 +1996,24 @@ point_distance(PG_FUNCTION_ARGS)
 	Point	   *pt1 = PG_GETARG_POINT_P(0);
 	Point	   *pt2 = PG_GETARG_POINT_P(1);
 
-	PG_RETURN_FLOAT8(point_dt(pt1, pt2));
+	PG_RETURN_FLOAT8(point_dt(pt1, pt2, fcinfo->context));
 }
 
 static inline float8
 
 Datum
@@ -2173,7 +2185,7 @@ lseg_length(PG_FUNCTION_ARGS)
 {
 	LSEG	   *lseg = PG_GETARG_LSEG_P(0);
 
-	PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1]));
+	PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1], fcinfo->context));
 }
 
 /*----------------------------------------------------------
@@ -2258,8 +2270,8 @@ lseg_lt(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);
 
-	PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]),
-						point_dt(&l2->p[0], &l2->p[1])));
+	PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+						point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
 }
 
 Datum
@@ -2268,8 +2280,8 @@ lseg_le(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);
 
-	PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]),
-						point_dt(&l2->p[0], &l2->p[1])));
+	PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+						point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
 }
 
 Datum
@@ -2278,8 +2290,8 @@ lseg_gt(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);
 
-	PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]),
-						point_dt(&l2->p[0], &l2->p[1])));
+	PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+						point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
 }
 
 Datum
@@ -2288,8 +2300,8 @@ lseg_ge(PG_FUNCTION_ARGS)
 	LSEG	   *l1 = PG_GETARG_LSEG_P(0);
 	LSEG	   *l2 = PG_GETARG_LSEG_P(1);
 
-	PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]),
-						point_dt(&l2->p[0], &l2->p[1])));
+	PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+						point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
 }

@@ -2743,7 +2755,7 @@ line_closept_point(Point *result, LINE *line, Point *point)
 	if (result != NULL)
 		*result = closept;

 Datum
@@ -2784,7 +2796,7 @@ lseg_closept_point(Point *result, LSEG *lseg, Point *pt)
 	if (result != NULL)
 		*result = closept;

 Datum
@@ -3108,9 +3120,9 @@ on_pl(PG_FUNCTION_ARGS)
 static bool
 
 Datum
@@ -3176,11 +3188,12 @@ on_ppath(PG_FUNCTION_ARGS)
 	if (!path->closed)
 	{
 		n = path->npts - 1;
-		a = point_dt(pt, &path->p[0]);
+		a = point_dt(pt, &path->p[0], fcinfo->context);
 		for (i = 0; i < n; i++)
 		{
-			b = point_dt(pt, &path->p[i + 1]);
-			if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1])))
+			b = point_dt(pt, &path->p[i + 1], fcinfo->context);
+			if (FPeq(float8_pl(a, b),
+					 point_dt(&path->p[i], &path->p[i + 1], fcinfo->context)))
 				PG_RETURN_BOOL(true);
 			a = b;
 		}
@@ -4766,7 +4779,7 @@ circle_overlap(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle1 = PG_GETARG_CIRCLE_P(0);
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);
 
-	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, fcinfo->context),
 						float8_pl(circle1->radius, circle2->radius)));
 }
 
@@ -4828,7 +4841,7 @@ circle_contained(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle1 = PG_GETARG_CIRCLE_P(0);
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);
 
-	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, fcinfo->context),
 						float8_mi(circle2->radius, circle1->radius)));
 }
 
@@ -4840,7 +4853,7 @@ circle_contain(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle1 = PG_GETARG_CIRCLE_P(0);
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);
 
-	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+	PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, fcinfo->context),
 						float8_mi(circle1->radius, circle2->radius)));
 }
 
@@ -5069,7 +5082,7 @@ circle_distance(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle2 = PG_GETARG_CIRCLE_P(1);
 	float8		result;
 
-	result = float8_mi(point_dt(&circle1->center, &circle2->center),
+	result = float8_mi(point_dt(&circle1->center, &circle2->center, fcinfo->context),
 					   float8_pl(circle1->radius, circle2->radius));
 	if (result < 0.0)
 		result = 0.0;
@@ -5085,7 +5098,7 @@ circle_contain_pt(PG_FUNCTION_ARGS)
 	Point	   *point = PG_GETARG_POINT_P(1);
 	float8		d;
 
-	d = point_dt(&circle->center, point);
+	d = point_dt(&circle->center, point, fcinfo->context);
 	PG_RETURN_BOOL(d <= circle->radius);
 }
 
@@ -5097,7 +5110,7 @@ pt_contained_circle(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle = PG_GETARG_CIRCLE_P(1);
 	float8		d;
 
-	d = point_dt(&circle->center, point);
+	d = point_dt(&circle->center, point, fcinfo->context);
 	PG_RETURN_BOOL(d <= circle->radius);
 }
 
@@ -5112,7 +5125,7 @@ dist_pc(PG_FUNCTION_ARGS)
 	CIRCLE	   *circle = PG_GETARG_CIRCLE_P(1);
 	float8		result;
 
-	result = float8_mi(point_dt(point, &circle->center),
+	result = float8_mi(point_dt(point, &circle->center, fcinfo->context),
 					   circle->radius);
 	if (result < 0.0)
 		result = 0.0;
@@ -5130,7 +5143,7 @@ dist_cpoint(PG_FUNCTION_ARGS)
 	Point	   *point = PG_GETARG_POINT_P(1);
 	float8		result;
 
-	result = float8_mi(point_dt(point, &circle->center), circle->radius);
+	result = float8_mi(point_dt(point, &circle->center, fcinfo->context), circle->radius);
 	if (result < 0.0)
 		result = 0.0;
 
@@ -5215,7 +5228,7 @@ box_circle(PG_FUNCTION_ARGS)
 	circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
 	circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);

 	PG_RETURN_CIRCLE_P(circle);
 }
@@ -5299,7 +5312,7 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)

-- 
2.34.1

From c5fe01f500d62e6d425a2b469bcd58ce853845e9 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 17:10:45 +0800
Subject: [PATCH v19 23/23] error safe for user defined CREATE CAST

evaluated in soft-error.

The new synopsis of CREATE CAST is:
CREATE CAST (source_type AS target_type)
    WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]

The following cast in citext, hstore contrib module refactored to error safe:
CAST (bpchar AS citext)
CAST (boolean AS citext)
CAST (inet AS citext)
CAST (text[] AS hstore)
CAST (hstore AS json)
CAST (hstore AS jsonb)

commitfest: https://commitfest.postgresql.org/patch/5941
 src/backend/parser/parse_expr.c           |  12 +-
 24 files changed, 354 insertions(+), 219 deletions(-)

index ecfaf2aa85a..be328715492 100644
 
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error
-ERROR:  cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
-                    ^
-HINT:  Safe type cast for user-defined types are not yet supported.
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext 
+--------
+ abc
+(1 row)
+
 -- Test the operators and indexing functions
 -- Test = and <>.
 SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
  t
 (1 row)

index 3e12cd793ad..e9f8454c662 100644
 
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error
-ERROR:  cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
-                    ^
-HINT:  Safe type cast for user-defined types are not yet supported.
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext 
+--------
+ abc
+(1 row)
+
 -- Test the operators and indexing functions
 -- Test = and <>.
 SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
  t
 (1 row)

index c820b0bd4d9..62f83d749f9 100644
 
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);

 
 -- Explicit casts.

 
 SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
 SELECT '08:00:2b:01:02:03'::citext::macaddr = '08:00:2b:01:02:03'::macaddr AS t;
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index acea8806ba4..19084f3371d 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -840,12 +840,28 @@ select '{}'::text[]::hstore;

@@ -854,6 +870,13 @@ select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
 
@@ -1552,6 +1575,13 @@ select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
 
@@ -1570,6 +1600,13 @@ select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=

 

 

 
 CREATE FUNCTION hstore_to_jsonb_loose(hstore)
 RETURNS jsonb
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 9b72efb8674..59a45b52a9e 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -739,20 +739,20 @@ hstore_from_array(PG_FUNCTION_ARGS)
 
 		case 1:
 
 		case 2:
 
 		default:
-			ereport(ERROR,
@@ -773,7 +773,7 @@ hstore_from_array(PG_FUNCTION_ARGS)
 
index 8ae95e8a510..a803e11ec84 100644
@@ -190,9 +190,16 @@ select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']))
@@ -361,10 +368,14 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe

index 69588937719..322b05296b4 100644

+    <varlistentry>
 
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 4abd5e671ae..0d6635bbebd 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2142,9 +2142,8 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
     If the type cast fails, an error is not raised.  Instead, evaluation falls
     back to the default <replaceable>expression</replaceable> specified in the
     <literal>ON CONVERSION ERROR</literal> clause.
-    At present, this only support built-in type casts, see <xref linkend="catalog-pg-cast"/>.
     User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link>
-    are not supported.
+    are supported too.
   </para>
 
     <para>
index 5119c2acda2..9eff941eabb 100644

diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 242372b1e68..00d9dc797c5 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1668,6 +1668,13 @@ CreateCast(CreateCastStmt *stmt)
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("cast function must not return a set")));
 
+		if (stmt->safe &&
+			procstruct->prolang != INTERNALlanguageId &&
+			procstruct->prolang != ClanguageId)
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("safe type cast functions are only supported for C and internal languages"));
+
 		ReleaseSysCache(tuple);
 	}
 	else
@@ -1796,7 +1803,7 @@ CreateCast(CreateCastStmt *stmt)
 	}

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 3dab6bb5a79..b3713b8513c 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1765,6 +1765,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 	/* Create cast from the range type to its multirange type */
 	CastCreate(typoid, multirangeOid, castFuncOid, InvalidOid, InvalidOid,
 			   COERCION_CODE_EXPLICIT, COERCION_METHOD_FUNCTION,
+			   false,
 			   DEPENDENCY_INTERNAL);
 
index 7612b884ae3..024d718bea0 100644
@@ -357,7 +357,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -782,7 +782,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
-	SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+	SAFE SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
 	SEQUENCE SEQUENCES
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SPLIT SOURCE SQL_P STABLE STANDALONE_P
@@ -9360,14 +9360,15 @@ dostmt_opt_item:
  *****************************************************************************/

 					n->sourcetype = $4;
 					n->targettype = $6;
-					n->func = $10;
-					n->context = (CoercionContext) $11;
+					n->safe = $9;
+					n->func = $11;
+					n->context = (CoercionContext) $12;
 					n->inout = false;
 					$$ = (Node *) n;
 				}
@@ -9378,6 +9379,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = false;
@@ -9390,6 +9392,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = true;
@@ -9402,6 +9405,9 @@ cast_context:  AS IMPLICIT_P					{ $$ = COERCION_IMPLICIT; }
 		| /*EMPTY*/								{ $$ = COERCION_EXPLICIT; }
 		;

 DropCastStmt: DROP CAST opt_if_exists '(' Typename AS Typename ')' opt_drop_behavior
 				{
@@ -18274,6 +18280,7 @@ unreserved_keyword:
 			| ROUTINES
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
@@ -18913,6 +18920,7 @@ bare_label_keyword:
 			| ROW
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 997cc87649d..de02f1fbc60 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2994,7 +2994,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 					   Oid inputType, Oid targetType)
 {
 	bool		errorsafe_coercion = true;
-	bool		userdefined = false;
 	Oid			inputBaseType;
 	Oid			targetBaseType;
 	Oid			inputElementType;
@@ -3065,11 +3064,8 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 		{
 			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
 
-			if (castForm->oid > FirstUnpinnedObjectId)
-			{
-				errorsafe_coercion = false;
-				userdefined = true;
-			}
+			if (OidIsValid(castForm->castfunc))
+				errorsafe_coercion = castForm->casterrorsafe;
 
 			ReleaseSysCache(tuple);
 		}
@@ -3083,9 +3079,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 					   format_type_be(targetType),
 					   "DEFAULT",
 					   "CAST ... ON CONVERSION ERROR"),
-				userdefined
-				? errhint("Safe type cast for user-defined types are not yet supported.")
-				: errhint("Explicit cast is defined but definition is not error safe."),
+				errhint("Explicit cast is defined but definition is not error safe."),
 				parser_errposition(pstate, exprLocation(source)));
 }
 
index 9b1cfb1b590..d3b5f27b15c 100644

 
 
@@ -316,57 +316,57 @@

 
@@ -392,73 +392,73 @@
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category
@@ -468,13 +468,13 @@

@@ -485,68 +485,68 @@

 
 
 # json to/from jsonb
@@ -556,36 +556,36 @@

 # range to multirange
index d81314946ee..6deded3e04e 100644
@@ -49,6 +49,10 @@ CATALOG(pg_cast,2605,CastRelationId)
 
 	/* cast method */
 	char		castmethod;
+
 
 END_CATALOG_STRUCT
@@ -105,6 +109,7 @@ extern ObjectAddress CastCreate(Oid sourcetypeid,
 								Oid outcastid,
 								char castcontext,
 								char castmethod,
+								bool casterrorsafe,
 								DependencyType behavior);
 
 #endif							/* PG_CAST_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8b0ed3aefa5..10394139e0c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4185,6 +4185,7 @@ typedef struct CreateCastStmt
 	ObjectWithArgs *func;
 	CoercionContext context;
 	bool		inout;
+	bool		safe;
 } CreateCastStmt;
 
index 6f74a8c05c7..afcf9a53b4c 100644
@@ -398,6 +398,7 @@ PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD, BARE_LABEL)
index 68e69d1c584..b3c7d62e526 100644
+ERROR:  safe type cast functions are only supported for C and internal languages
+ERROR:  safe type cast functions are only supported for C and internal languages
-HINT:  Safe type cast for user-defined types are not yet supported.
+HINT:  Explicit cast is defined but definition is not error safe.
index 6ff4d7ee901..9943a419695 100644
@@ -950,8 +950,8 @@ SELECT *
 
@@ -960,8 +960,8 @@ SELECT *
 
@@ -970,15 +970,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
 
 SELECT c.*
 
@@ -996,8 +996,8 @@ WHERE c.castfunc = p.oid AND
 
 SELECT c.*
@@ -1005,8 +1005,8 @@ FROM pg_cast c, pg_proc p

From 427e0fbe1f352b46949cf9d29661024f368615fd Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 13 Mar 2026 08:37:33 +0800
Subject: [PATCH v19 22/23] 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.

Example:
    -- Returns '2011-01-01' instead of throwing an invalid input syntax error.
    SELECT CAST('1' AS date DEFAULT '2011-01-01' ON CONVERSION ERROR);

# Bumps catversion required

[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=[email-masked]
commitfest: https://commitfest.postgresql.org/patch/5941
---
 contrib/citext/expected/citext.out            |   5 +
 contrib/citext/expected/citext_1.out          |   5 +
 contrib/citext/sql/citext.sql                 |   2 +
 .../pg_stat_statements/expected/select.out    |  24 +-
 contrib/pg_stat_statements/sql/select.sql     |   5 +
 doc/src/sgml/syntax.sgml                      |  32 +
 src/backend/executor/execExpr.c               |  69 +-
 src/backend/executor/execExprInterp.c         |  29 +
 src/backend/jit/llvm/llvmjit_expr.c           |  27 +
 src/backend/nodes/nodeFuncs.c                 |  53 ++
 src/backend/optimizer/util/clauses.c          |  52 +-
 src/backend/parser/gram.y                     |  23 +
 src/backend/parser/parse_agg.c                |   9 +
 src/backend/parser/parse_coerce.c             |  69 +-
 src/backend/parser/parse_expr.c               | 373 +++++++-
 src/backend/parser/parse_func.c               |   3 +
 src/backend/parser/parse_type.c               |  18 +
 src/backend/parser/parse_utilcmd.c            |   2 +-
 src/backend/utils/adt/arrayfuncs.c            |   9 +
 src/backend/utils/adt/ruleutils.c             |  25 +
 src/backend/utils/fmgr/fmgr.c                 |  13 +
 src/include/executor/execExpr.h               |   7 +
 src/include/fmgr.h                            |   3 +
 src/include/nodes/execnodes.h                 |  21 +
 src/include/nodes/parsenodes.h                |   1 +
 src/include/nodes/primnodes.h                 |  25 +
 src/include/optimizer/optimizer.h             |   2 +-
 src/include/parser/parse_coerce.h             |  13 +
 src/include/parser/parse_node.h               |   2 +
 src/include/parser/parse_type.h               |   2 +
 src/test/regress/expected/cast.out            | 809 ++++++++++++++++++
 src/test/regress/expected/create_cast.out     |   5 +
 src/test/regress/expected/equivclass.out      |   7 +
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/cast.sql                 | 347 ++++++++
 src/test/regress/sql/create_cast.sql          |   1 +
 src/test/regress/sql/equivclass.sql           |   3 +
 src/tools/pgindent/typedefs.list              |   2 +
 38 files changed, 2043 insertions(+), 56 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

index 8c0bf54f0f3..ecfaf2aa85a 100644
 
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR:  cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+                    ^
+HINT:  Safe type cast for user-defined types are not yet supported.
 -- 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..3e12cd793ad 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,6 +10,11 @@ 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 when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+                    ^
+HINT:  Safe type cast for user-defined types are not yet supported.
 -- 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 <>.
index a069119c790..84f10810f72 100644
 
+-- error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4 
+------
+    2
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric 
+---------
+      12
+(1 row)
+
+SELECT CAST('12' AS numeric(10) DEFAULT 2 ON CONVERSION ERROR);
+ numeric 
+---------
+      12
+(1 row)
+
 -- DISTINCT and ORDER BY patterns
 -- Try some query permutations which once produced identical query IDs
 SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,9 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      2 |    2 | SELECT $1 AS "int" ORDER BY 1
      1 |    2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
      1 |    1 | SELECT $1 || $2
+     1 |    1 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+     1 |    1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
+     1 |    1 | SELECT CAST($1 AS numeric(10) DEFAULT $2 ON CONVERSION ERROR)
      2 |    2 | SELECT DISTINCT $1 AS "int"
      0 |    0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
      1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +252,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
        |      | )                                                                           +
        |      |   SELECT f FROM t ORDER BY f
      1 |    1 | select $1::jsonb ? $2
-(17 rows)
+(20 rows)
 
index a10d618c034..4be97977ba6 100644
 
+-- error safe type cast
+SELECT CAST('12' AS numeric(10) DEFAULT 2 ON CONVERSION ERROR);
index 67482996861..4abd5e671ae 100644
@@ -2071,6 +2071,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
 
    <para>
@@ -2125,6 +2129,34 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
      <xref linkend="sql-createcast"/>.
     </para>
    </note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+  <title>Safe Type Cast</title>
+   <para>
+    A type cast may occasionally fail. To guard against such failures, you can
+    provide an <literal>ON CONVERSION ERROR</literal> clause to handle potential errors.
+    The syntax for safe type cast is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+    If the type cast fails, an error is not raised.  Instead, evaluation falls
+    back to the default <replaceable>expression</replaceable> specified in the
+    <literal>ON CONVERSION ERROR</literal> clause.
+    At present, this only support built-in type casts, see <xref linkend="catalog-pg-cast"/>.
+    User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link>
+    are not supported.
+  </para>
+
+    <para>
+    Some examples:
+<screen>
+SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
+SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1.1</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 bd46b75e498..0e824b655a1 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -99,6 +99,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
 static void ExecInitJsonExpr(JsonExpr *jsexpr, 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,
@@ -263,6 +266,7 @@ ExecInitQual(List *qual, PlanState *parent)
 	state->expr = (Expr *) qual;
 	state->parent = parent;
 	state->ext_params = NULL;
+	state->escontext = (ErrorSaveContext *) escontext;
 
 	/* mark expression as to be used with ExecQual() */
 	state->flags = EEO_FLAG_IS_QUAL;
@@ -1734,6 +1738,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);
@@ -2209,6 +2214,15 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
@@ -2769,7 +2783,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;
@@ -4768,6 +4783,58 @@ 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)
+{
+	/*
+	 * Coercion to the target type failed. Falling back to the DEFAULT
+	 * expression from the ON CONVERSION ERROR clause to finish.
+	 */
+	if (stcexpr->castexpr == NULL)
+	{
+		ExecInitExprRec(stcexpr->defexpr, state, resv, resnull);
+
+		return;
+	}
+	else
+	{
+		SafeTypeCastState *stcstate = palloc0_object(SafeTypeCastState);
+		ErrorSaveContext *saved_escontext = state->escontext;
+
+		stcstate->stcexpr = stcexpr;
+		stcstate->escontext.type = T_ErrorSaveContext;
+		state->escontext = &stcstate->escontext;
+
+		/* evaluate argument expression into step's result area */
+		ExecInitExprRec(stcexpr->castexpr, state, resv, resnull);
+		scratch->opcode = EEOP_SAFETYPE_CAST;
+		scratch->d.stcexpr.stcstate = stcstate;
+		ExprEvalPushStep(state, scratch);
+
+		/*
+		 * Evaluate the DEFAULT expression, skipping if it's a binary coercion
+		 * cast. state->escontext must be NULL, as this evaluation should not
+		 * be error-safe.
+		 */
+		if (!IsA(stcexpr->castexpr, RelabelType))
+		{
+			state->escontext = NULL;
+
+			ExecInitExprRec(stcstate->stcexpr->defexpr, state, resv, resnull);
+		}
+
+		state->escontext = saved_escontext;
+
+		stcstate->jump_end = state->steps_len;
+	}
+}
+
 /*
  * 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 69c17e024f2..7ac1e63c510 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -568,6 +568,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,
@@ -1926,6 +1927,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+			if (!SOFT_ERROR_OCCURRED(&stcstate->escontext))
 
 /*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 650f1d42a93..ad4b74b1ac8 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,33 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
+			case EEOP_SAFETYPE_CAST:
+				{
+					SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+					if (!SOFT_ERROR_OCCURRED(&stcstate->escontext))
+						LLVMBuildBr(b, opblocks[stcstate->jump_end]);
+					else
+					{
+						/*
+						 * Reset for next use such as for catching errors when
+						 * coercing a expression.
+						 */
+						stcstate->escontext.error_occurred = false;
+						stcstate->escontext.details_wanted = false;
+
+						/* set resnull to true */
+						LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+
+						/* reset resvalue */
+						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/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 199ed27995f..4f38dfa0c31 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -206,6 +206,9 @@ exprType(const Node *expr)
 		case T_RowCompareExpr:
 			type = BOOLOID;
 			break;
+		case T_SafeTypeCastExpr:
+			type = ((const SafeTypeCastExpr *) expr)->resulttype;
+			break;
 		case T_CoalesceExpr:
 			type = ((const CoalesceExpr *) expr)->coalescetype;
 			break;
@@ -450,6 +453,8 @@ exprTypmod(const Node *expr)
 				return typmod;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			return ((const SafeTypeCastExpr *) expr)->resulttypmod;
 		case T_CoalesceExpr:
 			{
 				/*
@@ -965,6 +970,9 @@ 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_CoalesceExpr:
 			coll = ((const CoalesceExpr *) expr)->coalescecollid;
 			break;
@@ -1232,6 +1240,9 @@ 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_CoalesceExpr:
 			((CoalesceExpr *) expr)->coalescecollid = collation;
 			break;
@@ -1550,6 +1561,9 @@ 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_CoalesceExpr:
 			/* COALESCE keyword should always be the first thing */
 			loc = ((const CoalesceExpr *) expr)->location;
@@ -2324,6 +2338,18 @@ expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				if (WALK(stcexpr->source))
+					return true;
+				if (WALK(stcexpr->castexpr))
+					return true;
+				if (WALK(stcexpr->defexpr))
+					return true;
+			}
+			break;
 		case T_CoalesceExpr:
 			return WALK(((CoalesceExpr *) node)->args);
 		case T_MinMaxExpr:
@@ -3333,6 +3359,19 @@ 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->source, stcexpr->source, Expr *);
+				MUTATE(newnode->castexpr, stcexpr->castexpr, Expr *);
+				MUTATE(newnode->defexpr, stcexpr->defexpr, Expr *);
+
+				return (Node *) newnode;
+			}
+			break;
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
@@ -4465,6 +4504,20 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 				if (WALK(tc->typeName))
 					return true;
+				if (WALK(tc->defexpr))
+					return true;
+			}
+			break;
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				if (WALK(stcexpr->source))
+					return true;
+				if (WALK(stcexpr->castexpr))
+					return true;
+				if (WALK(stcexpr->defexpr))
+					return true;
 			}
 			break;
 		case T_CollateClause:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9fb266d089d..e2b4218edc6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2675,7 +2675,8 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 	((Node *) evaluate_expr((Expr *) (node), \
 							exprType((Node *) (node)), \
 							exprTypmod((Node *) (node)), \
-							exprCollation((Node *) (node))))
+							exprCollation((Node *) (node)), \
+							NULL))
 
 /*
  * Recursive guts of eval_const_expressions/estimate_expression_value
@@ -3267,6 +3268,36 @@ eval_const_expressions_mutator(Node *node,
 												  copyObject(jve->format));
 			}
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *newexpr;
+
+				SafeTypeCastExpr *stc = castNode(SafeTypeCastExpr, node);
+
+				Node	   *source = (Node *) stc->source;
+				Node	   *defexpr = (Node *) stc->defexpr;
+
+				source = eval_const_expressions_mutator(source,
+														context);
+				defexpr = eval_const_expressions_mutator(defexpr,
+														 context);
+
+				/*
+				 * Defer constant folding for castexpr. Evaluating
+				 * recognizable constants at this stage is not error-safe and
+				 * may lead to premature errors.
+				 */
+				newexpr = makeNode(SafeTypeCastExpr);
+				newexpr->source = (Expr *) source;
+				newexpr->castexpr = (Expr *) stc->castexpr;
+				newexpr->defexpr = (Expr *) defexpr;
+				newexpr->resulttype = stc->resulttype;
+				newexpr->resulttypmod = stc->resulttypmod;
+				newexpr->resultcollid = stc->resultcollid;
+
+				return (Node *) newexpr;
+			}
+
 		case T_SubPlan:
 		case T_AlternativeSubPlan:
 
@@ -3698,7 +3729,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);
 			}
@@ -5229,7 +5261,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	newexpr->location = -1;
 
+						 result_collid, NULL);
 }
 
 /*
@@ -5683,10 +5715,13 @@ 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.
+ *
+ * When escontext is non-NULL, safely evaluates the constant expression.
+ * Returns NULL on failure rather than throwing an error.
  */
 Expr *
 evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
-			  Oid result_collation)
+			  Oid result_collation, Node *escontext)
 {
 	EState	   *estate;
 	ExprState  *exprstate;
@@ -5711,7 +5746,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.
@@ -5731,6 +5766,13 @@ 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 NULL;
+	}
+
 	/*
 	 * 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 f01f5734fe9..7612b884ae3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -158,6 +158,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 *makeTypeCastWithDefault(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);
@@ -16191,6 +16193,12 @@ 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 NULL_P ON CONVERSION_P ERROR_P ')'
+				{ $$ = makeTypeCastWithDefault($3, $5, makeNullAConst(-1), @1); }
+			| CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+				{ $$ = makeTypeCastWithDefault($3, $5, $7, @1); }
 			| EXTRACT '(' extract_list ')'
 				{
 					$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -19124,10 +19132,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 *
+makeTypeCastWithDefault(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_agg.c b/src/backend/parser/parse_agg.c
index 33fd2cccae5..a18d20dbfdb 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -491,6 +491,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in check constraints");
 
 			break;
 
@@ -984,6 +990,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("window functions are not allowed in check constraints");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("window functions are not allowed in CAST DEFAULT expressions");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 913ca53666f..01264d5e0cb 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -81,6 +81,28 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
 					  CoercionContext ccontext,
 					  CoercionForm cformat,
 					  int location)
+{
+	return coerce_to_target_type_extended(pstate, expr, exprtype,
+										  targettype, targettypmod,
+										  ccontext,
+										  cformat,
+										  location,
+										  NULL);
+}
+
+/*
+ * escontext: If non-NULL, safely coerces 'expr' to the target type
+ * without raising an error. Returns NULL if the coercion fails.
+ *
+ * See 'coerce_to_target_type' above for details on other parameters.
+ */
+Node *
+coerce_to_target_type_extended(ParseState *pstate, Node *expr, Oid exprtype,
+							   Oid targettype, int32 targettypmod,
+							   CoercionContext ccontext,
+							   CoercionForm cformat,
+							   int location,
+							   Node *escontext)
 {
 	Node	   *result;
 	Node	   *origexpr;
@@ -102,9 +124,15 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
 	while (expr && IsA(expr, CollateExpr))
 		expr = (Node *) ((CollateExpr *) expr)->arg;
 
+	result = coerce_type_extended(pstate, expr, exprtype,
 
 	/*
 	 * If the target is a fixed-length type, it may need a length coercion as
@@ -158,6 +186,18 @@ Node *
 coerce_type(ParseState *pstate, Node *node,
 			Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
 			CoercionContext ccontext, CoercionForm cformat, int location)
+{
+	return coerce_type_extended(pstate, node,
+								inputTypeId, targetTypeId, targetTypeMod,
+								ccontext, cformat, location,
+								NULL);
+}
+
+Node *
+coerce_type_extended(ParseState *pstate, Node *node,
+					 Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+					 CoercionContext ccontext, CoercionForm cformat, int location,
+					 Node *escontext)
 {
 	Node	   *result;
 	CoercionPathType pathtype;
@@ -256,6 +296,7 @@ coerce_type(ParseState *pstate, Node *node,
 		int32		inputTypeMod;
 		Type		baseType;
 		ParseCallbackState pcbstate;
+		char	   *string = NULL;
 
 		/*
 		 * If the target type is a domain, we want to call its base type's
@@ -309,13 +350,21 @@ 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);
+
+		if (!stringTypeDatumSafe(baseType,
+								 string,
+								 inputTypeMod,
+								 escontext,
+								 &newcon->constvalue))
+		{
+			/* UNKNOWN Const cannot coerce to targetType, exit now */
+			cancel_parser_errposition_callback(&pcbstate);
+
+			ReleaseSysCache(baseType);
+
+			return NULL;
+		}
 
 		/*
index 96991cae764..997cc87649d 100644
 
 #include "utils/typcache.h"
@@ -61,7 +63,8 @@ static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
+								bool *is_coercible);
@@ -77,6 +80,8 @@ static Node *transformWholeRowRef(ParseState *pstate,
+								   Node *source, Oid inputType, Oid targetType);
@@ -165,7 +170,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
@@ -565,6 +570,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_VALUES_SINGLE:
 		case EXPR_KIND_CHECK_CONSTRAINT:
 		case EXPR_KIND_DOMAIN_CHECK:
+		case EXPR_KIND_CAST_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 		case EXPR_KIND_INDEX_EXPRESSION:
 		case EXPR_KIND_INDEX_PREDICATE:
@@ -1834,6 +1840,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("cannot use subquery in check constraint");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("cannot use subquery in CAST DEFAULT expression");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
@@ -2021,17 +2030,23 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
  * If the caller specifies the target type, the resulting array will
  * be of exactly that type.  Otherwise we try to infer a common type
  * for the elements using select_common_type().
+ *
+ * is_coercible is typically NULL, except during parse analysis for
+ * CAST(... DEFAULT ... ON CONVERSION ERROR). When provided, it defaults
+ * to true but will be set to false if coercing array elements fails.
  */
 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 *is_coercible)
 {
 	ArrayExpr  *newa = makeNode(ArrayExpr);
 	List	   *newelems = NIL;
 	List	   *newcoercedelems = NIL;
 	ListCell   *element;
 	Oid			coerce_type;
+	Oid			coerce_type_coll;
 	bool		coerce_hard;
+	ErrorSaveContext escontext = {T_ErrorSaveContext};
 
 	/*
 	 * Transform the element expressions
@@ -2055,9 +2070,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  is_coercible);
 			/* we certainly have an array here */
-			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			Assert(is_coercible || array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
 		}
 		else
@@ -2098,6 +2114,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	}
 	else
 	{
+		Assert(is_coercible == NULL);
+
 		/* Can't handle an empty array without a target type */
 		if (newelems == NIL)
 			ereport(ERROR,
@@ -2135,6 +2153,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		coerce_hard = false;
 	}
 
+	coerce_type_coll = get_typcollation(coerce_type);
+
 	/*
 	 * Coerce elements to target type
 	 *
@@ -2144,28 +2164,79 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	 * If the array's type was merely derived from the common type of its
 	 * elements, then the elements are implicitly coerced to the common type.
 	 * This is consistent with other uses of select_common_type().
+	 *
+	 * If is_coercible is not NULL, check whether each element can be coerced
+	 * to the target type, similar to what is done in transformTypeCast.
 	 */
 	foreach(element, newelems)
 	{
 		Node	   *e = (Node *) lfirst(element);
-		Node	   *newe;
+		Node	   *newe = NULL;
 
 		if (coerce_hard)
 		{
-			newe = coerce_to_target_type(pstate, e,
-										 exprType(e),
-										 coerce_type,
-										 typmod,
-										 COERCION_EXPLICIT,
-										 COERCE_EXPLICIT_CAST,
-										 -1);
-			if (newe == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_CANNOT_COERCE),
-						 errmsg("cannot cast type %s to %s",
-								format_type_be(exprType(e)),
-								format_type_be(coerce_type)),
-						 parser_errposition(pstate, exprLocation(e))));
+			/*
+			 * Cannot coerce, just append the transformed element expression
+			 * to the list.
+			 */
+			if (is_coercible && (!*is_coercible))
+				newe = e;
+			else
+			{
+				Node	   *ecopy = NULL;
+
+				if (is_coercible)
+					ecopy = copyObject(e);
+
+				newe = coerce_to_target_type_extended(pstate, e,
+													  exprType(e),
+													  coerce_type,
+													  typmod,
+													  COERCION_EXPLICIT,
+													  COERCE_EXPLICIT_CAST,
+													  -1,
+													  (Node *) &escontext);
+				if (newe == NULL)
+				{
+					/*
+					 * Cannot coerce. Raise an error or append the transformed
+					 * element to the list.
+					 */
+					if (!is_coercible)
+						ereport(ERROR,
+								(errcode(ERRCODE_CANNOT_COERCE),
+								 errmsg("cannot cast type %s to %s",
+										format_type_be(exprType(e)),
+										format_type_be(coerce_type)),
+								 parser_errposition(pstate, exprLocation(e))));
+					else
+					{
+						newe = ecopy;
+						*is_coercible = false;
+					}
+				}
+				else if (is_coercible && IsA(ecopy, Const) && IsA(newe, FuncExpr))
+				{
+					Node	   *newexpr = copyObject(newe);
+
+					/*
+					 * Pre-evaluate simple constant cast expressions in a way
+					 * that tolerate errors.
+					 */
+					assign_expr_collations(pstate, newexpr);
+
+					newexpr = (Node *) evaluate_expr((Expr *) newexpr,
+													 coerce_type,
+													 typmod,
+													 coerce_type_coll,
+													 (Node *) &escontext);
+					if (newexpr == NULL)
+					{
+						newe = ecopy;
+						*is_coercible = false;
+					}
+				}
+			}
 		}
 		else
 			newe = coerce_to_common_type(pstate, e,
@@ -2706,7 +2777,8 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
 }
 
 /*
- * Handle an explicit CAST construct.
+ * Handle an explicit CAST construct or
+ * CAST(... DEFAULT ... ON CONVERSION ERROR) construct.
  *
  * Transform the argument, look up the type name, and apply any necessary
  * coercion function(s).
@@ -2714,17 +2786,63 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
 static Node *
 transformTypeCast(ParseState *pstate, TypeCast *tc)
 {
-	Node	   *result;
+	SafeTypeCastExpr *stc;
+	Node	   *castexpr = NULL;
+	Node	   *defexpr = NULL;
 	Node	   *arg = tc->arg;
+	Node	   *source;
 	Node	   *expr;
 	Oid			inputType;
 	Oid			targetType;
+	Oid			targetTypecoll;
 	int32		targetTypmod;
 	int			location;
+	bool		is_coercible = true;
+	ErrorSaveContext escontext = {T_ErrorSaveContext};
 
 	/* Look up the type name first */
 	typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod);
 
+	targetTypecoll = get_typcollation(targetType);
+
+	/* looking at DEFAULT expression */
+	if (tc->defexpr)
+	{
+		Oid			defColl;
+
+		defexpr = transformExpr(pstate, tc->defexpr, EXPR_KIND_CAST_DEFAULT);
+
+		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));
+
+		assign_expr_collations(pstate, defexpr);
+
+		/*
+		 * The collation of DEFAULT expression must match the collation of the
+		 * target type.
+		 */
+		defColl = exprCollation(defexpr);
+
+		if (targetTypecoll != defColl)
+			ereport(ERROR,
+					errcode(ERRCODE_DATATYPE_MISMATCH),
+					errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+					errdetail("\"%s\" versus \"%s\"",
+							  get_collation_name(targetTypecoll),
+							  get_collation_name(defColl)),
+					parser_errposition(pstate, exprLocation(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
@@ -2753,7 +2871,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 									  (A_ArrayExpr *) arg,
 									  targetBaseType,
 									  elementType,
-									  targetBaseTypmod);
+									  targetBaseTypmod,
+									  defexpr ? &is_coercible : NULL);
 		}
 		else
 			expr = transformExprRecurse(pstate, arg);
@@ -2774,20 +2893,200 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	if (location < 0)
 		location = tc->typeName->location;
 
-	result = coerce_to_target_type(pstate, expr, inputType,
-								   targetType, targetTypmod,
-								   COERCION_EXPLICIT,
-								   COERCE_EXPLICIT_CAST,
-								   location);
-	if (result == NULL)
+	/*
+	 * coerce_to_target_type_extended is unlikely to modify the source
+	 * expression, but we still create a copy beforehand. This allows
+	 * SafeTypeCastExpr to receive the transformed source expression
+	 * unchanged.
+	 */
+	source = copyObject(expr);
+
+	if (is_coercible)
+	{
+		castexpr = coerce_to_target_type_extended(pstate, expr, inputType,
+												  targetType, targetTypmod,
+												  COERCION_EXPLICIT,
+												  COERCE_EXPLICIT_CAST,
+												  location,
+												  defexpr ? (Node *) &escontext : NULL);
+
+		/*
+		 * Cannot coerce. Set is_coercible to false or raise an error for a
+		 * simple CAST.
+		 */
+		if (!castexpr)
+		{
+			is_coercible = false;
+
+			if (!defexpr)
+				ereport(ERROR,
+						(errcode(ERRCODE_CANNOT_COERCE),
+						 errmsg("cannot cast type %s to %s",
+								format_type_be(inputType),
+								format_type_be(targetType)),
+						 parser_coercion_errposition(pstate, location, expr)));
+
+		}
+
+		/*
+		 * Simple CAST construct, return the cast expression now
+		 */
+		if (!defexpr)
+			return castexpr;
+	}
+
+	/* Further check CAST(... DEFAULT ... ON CONVERSION ERROR) construct */
+	CoercionErrorSafeCheck(pstate, castexpr, source, inputType,
+						   targetType);
+
+	if (IsA(source, Const) && castexpr && IsA(castexpr, FuncExpr))
+	{
+		Node	   *newexpr = copyObject(castexpr);
+
+		assign_expr_collations(pstate, newexpr);
+
+		/*
+		 * Pre-evaluate simple constant cast expressions in an error-safe
+		 * manner via evaluate_expr.
+		 *
+		 * Rationale: 1. Later operations, such as deparsing safe-cast
+		 * expressions, may invoke eval_const_expressions(). Because that
+		 * function cannot handle errors gracefully, we preemptively perform
+		 * the evaluation here using the error-safe evaluate_expr() instead.
+		 *
+		 * 2. Even if the FuncExpr (for castfunc) has three arguments, the
+		 * second and third arguments are guaranteed to be constants (see
+		 * coerce_to_target_type). Evaluating a FuncExpr where all arguments
+		 * are Const nodes is safe in evaluate_expr.
+		 */
+		newexpr = (Node *) evaluate_expr((Expr *) newexpr,
+										 targetType,
+										 targetTypmod,
+										 targetTypecoll,
+										 (Node *) &escontext);
+		if (newexpr == NULL)
+		{
+			/* Cannot coerce, set is_coercible to false */
+			is_coercible = false;
+			castexpr = NULL;
+		}
+	}
+
+	Assert(is_coercible || castexpr == NULL);
+
+	stc = makeNode(SafeTypeCastExpr);
+	stc->source = (Expr *) source;
+	stc->castexpr = (Expr *) castexpr;
+	stc->defexpr = (Expr *) defexpr;
+	stc->resulttype = targetType;
+	stc->resulttypmod = targetTypmod;
+	stc->resultcollid = targetTypecoll;
+	stc->location = location;
+
+	return (Node *) stc;
+}
+
+/*
+ * Check type coercion is error safe or not.  If not then report error
+ */
+static void
+CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
+					   Oid inputType, Oid targetType)
+{
+	bool		errorsafe_coercion = true;
+	bool		userdefined = false;
+	Oid			inputBaseType;
+	Oid			targetBaseType;
+	Oid			inputElementType;
+	Oid			inputElementBaseType;
+	Oid			targetElementType;
+	Oid			targetElementBaseType;
+
+	/*
+	 * Binary coercion cast is equivalent to no cast at all.  CoerceViaIO can
+	 * also be evaluated in an error-safe manner. Skip these two cases.
+	 */
+	if (castexpr == NULL || IsA(castexpr, RelabelType) ||
+		IsA(castexpr, CoerceViaIO))
+		return;
+
+	/*
+	 * Type cast involving with some types is not error safe, do the check
+	 * now. We need consider domain over array and array over domain scarenio.
+	 */
+	inputElementType = get_element_type(inputType);
+
+	if (OidIsValid(inputElementType))
+		inputElementBaseType = getBaseType(inputElementType);
+	else
+	{
+		inputBaseType = getBaseType(inputType);
+		inputElementBaseType = get_element_type(inputBaseType);
+
+		if (!OidIsValid(inputElementBaseType))
+			inputElementBaseType = inputBaseType;
+	}
+
+	targetElementType = get_element_type(targetType);
+
+	if (OidIsValid(targetElementType))
+		targetElementBaseType = getBaseType(targetElementType);
+	else
+	{
+		targetBaseType = getBaseType(targetType);
+		targetElementBaseType = get_element_type(targetBaseType);
+
+		if (!OidIsValid(targetElementBaseType))
+			targetElementBaseType = targetBaseType;
+	}
+
+	if (inputElementBaseType == MONEYOID ||
+		targetElementBaseType == MONEYOID ||
+		(inputElementBaseType == CIRCLEOID &&
+		 targetElementBaseType == POLYGONOID))
+	{
+		/*
+		 * Casts involving type MONEY are not error safe.  Type CIRCLE to type
+		 * POLYGON cast is unsafe because it relies on a SQL-language
+		 * function; only C-language functions are currently supported for
+		 * error safe casts.
+		 */
+		errorsafe_coercion = false;
+	}
+
+	if (errorsafe_coercion)
+	{
+		/* Look in pg_cast */
+		HeapTuple	tuple = SearchSysCache2(CASTSOURCETARGET,
+											ObjectIdGetDatum(inputElementBaseType),
+											ObjectIdGetDatum(targetElementBaseType));
+
+		if (HeapTupleIsValid(tuple))
+		{
+			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+			if (castForm->oid > FirstUnpinnedObjectId)
+			{
+				errorsafe_coercion = false;
+				userdefined = true;
+			}
+
+			ReleaseSysCache(tuple);
+		}
+	}
+
+	if (!errorsafe_coercion)
 		ereport(ERROR,
-				(errcode(ERRCODE_CANNOT_COERCE),
-				 errmsg("cannot cast type %s to %s",
-						format_type_be(inputType),
-						format_type_be(targetType)),
-				 parser_coercion_errposition(pstate, location, expr)));
-
-	return result;
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+					   format_type_be(inputType),
+					   format_type_be(targetType),
+					   "DEFAULT",
+					   "CAST ... ON CONVERSION ERROR"),
+				userdefined
+				? errhint("Safe type cast for user-defined types are not yet supported.")
+				: errhint("Explicit cast is defined but definition is not error safe."),
+				parser_errposition(pstate, exprLocation(source)));
 }
 
 /*
@@ -3203,6 +3502,8 @@ ParseExprKindName(ParseExprKind exprKind)
index 24f6745923b..590a8a785c9 100644
index bb7eccde9fd..283dfc2ee99 100644
@@ -660,6 +660,24 @@ stringTypeDatum(Type tp, char *string, int32 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, char *string, int32 atttypmod,
+					Node *escontext, Datum *result)
+{
+	Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+	Oid			typinput = typform->typinput;
+	Oid			typioparam = getTypeIOParam(tp);
+
+	return OidInputFunctionCallSafe(typinput, 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 cc244c49e9e..4a5082b3565 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4950,7 +4950,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 734e5fea45e..dbfe5f4a79e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3294,6 +3294,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 6298a37f88e..78292a6c96a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10584,6 +10584,31 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				/*
+				 * We cannot just deparsing castexpr directly here, since
+				 * transformTypeCast may have already constant-folding it into
+				 * a NULL. Instead, we use source and defexpr to reconstruct
+				 * the CAST DEFAULT clause.
+				 */
+				appendStringInfoString(buf, "CAST(");
+				get_rule_expr((Node *) stcexpr->source, 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_JsonExpr:
 			{
 				JsonExpr   *jexpr = (JsonExpr *) node;
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 4e26df7c63a..c6707e8c35c 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1759,6 +1759,19 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
 	return InputFunctionCall(&flinfo, str, typioparam, typmod);
 }
 
index aa9b361fa31..fa9db8fd687 100644
 
+		/* for EEOP_SAFETYPE_CAST */
+		struct
+		{
+			struct SafeTypeCastState *stcstate;
+		}			stcexpr;
+
 		/* for EEOP_JSONEXPR_PATH */
 		struct
 		{
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 10d02bdb79f..8e3e51dc228 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -752,6 +752,9 @@ extern bool DirectInputFunctionCallSafe(PGFunction func, char *str,
 										Datum *result);
 extern Datum OidInputFunctionCall(Oid functionId, char *str,
 								  Oid typioparam, int32 typmod);
+extern bool OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+									 int32 typmod, Node *escontext,
+									 Datum *result);
 extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
 extern char *OidOutputFunctionCall(Oid functionId, Datum val);
 extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 63c067d5aae..08e75e70968 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1062,6 +1062,27 @@ typedef struct DomainConstraintState
 	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;
 
index f3d32ef0188..8b0ed3aefa5 100644
@@ -397,6 +397,7 @@ typedef struct TypeCast
+	Node	   *defexpr;		/* DEFAULT expression */
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 384df50c80a..fc963a6072b 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -770,6 +770,31 @@ 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)
+ * 		CAST(expr AS typename NULL ON CONVERSION ERROR)
+ */
+typedef struct SafeTypeCastExpr
+{
+	Expr		xpr;
+	/* transformed source expression from parser */
+	Expr	   *source;
+	/* transformed cast expression, NULL means cannot coerce to target type */
+	Expr	   *castexpr pg_node_attr(query_jumble_ignore);
+	/* 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;
+
 /*
  * FuncExpr - expression node for a function call
  *
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index e8b409afb7f..b686be049b9 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);
index aabacd49b65..ee939eac556 100644
@@ -43,11 +43,24 @@ extern Node *coerce_to_target_type(ParseState *pstate,
+extern Node *coerce_type_extended(ParseState *pstate, Node *node,
index f23e21f318b..2dd0c095cef 100644
+	EXPR_KIND_CAST_DEFAULT,		/* default expression in CAST DEFAULT ON
+								 * CONVERSION ERROR */
index a335807b0b0..e303a1073c1 100644
 
index 00000000000..63e0d1c2846
@@ -0,0 +1,809 @@
+-- source expression is a unknown const
+VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); -- error
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); -- error
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); -- error
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); -- error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); -- error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); -- error
+-- the default expression’s collation should match target type collation
+-- source expression contain subquery
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); -- error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); -- error
+-- DEFAULT expression cannot be set-returning
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; -- error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); -- 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('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); -- error
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); -- error
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); -- error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); -- error
+-- cast from type circle to type polygon is implemented as an SQL function, which cannot be error-safe
+HINT:  Explicit cast is defined but definition is not error safe.
+--- safe cast from/to money type is not supported, all the following would result error
+HINT:  Explicit cast is defined but definition is not error safe.
+HINT:  Explicit cast is defined but definition is not error safe.
+HINT:  Explicit cast is defined but definition is not error safe.
+HINT:  Explicit cast is defined but definition is not error safe.
+HINT:  Explicit cast is defined but definition is not error safe.
+-- safe cast from bytea type to other data types
+-- safe cast from bit type to other data types
+--- safe cast from text type to other data types
+--- safe cast betweeen range data types
+-- safe cast betweeen numeric data types
+-- safe type cast for type date/timestamp/timestamptz/interval
+('-infinity', 'infinity', 'infinity', 'infinity', '11:59:59.99 PM', '11:59:59.99 PM PDT');
+-- test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); -- error
+LINE 1: CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as ...
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR))); -- error
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('cast_error_idx'::regclass);
+--------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX cast_error_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
index 0e69644bca2..68e69d1c584 100644
 
+HINT:  Safe type cast for user-defined types are not yet supported.
index ad8ab294ff6..e3f8a2807f9 100644
+-- int8alias2 binary-coercible to int8, this should not fail
index 549e9b2d7be..bc8c02b53e0 100644
 
 # ----------
index 00000000000..515052d9e99
@@ -0,0 +1,347 @@
+-- source expression is a unknown const
+VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); -- error
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); -- error
+SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); -- error
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); -- error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); -- error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); -- error
+-- the default expression’s collation should match target type collation
+-- source expression contain subquery
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); -- error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); -- error
+-- DEFAULT expression cannot be set-returning
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; -- error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); -- 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('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); -- error
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); -- error
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); -- error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); -- error
+-- cast from type circle to type polygon is implemented as an SQL function, which cannot be error-safe
+--- safe cast from/to money type is not supported, all the following would result error
+-- safe cast from bytea type to other data types
+-- safe cast from bit type to other data types
+--- safe cast from text type to other data types
+--- safe cast betweeen range data types
+-- safe cast betweeen numeric data types
+-- safe type cast for type date/timestamp/timestamptz/interval
+('-infinity', 'infinity', 'infinity', 'infinity', '11:59:59.99 PM', '11:59:59.99 PM PDT');
+-- test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); -- error
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR))); -- error
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('cast_error_idx'::regclass);

index 7fc2159349b..7ada4bb5504 100644
 
+-- int8alias2 binary-coercible to int8, this should not fail
+select cast('1'::int8 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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 0de55183793..307027f109d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2705,6 +2705,8 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

From 61806d4f8bad05ee2f7f4432f31396c649f16b6d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 14:16:25 +0800
Subject: [PATCH v19 21/23] error safe for casting geometry data type

already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center.

This patch make these functions error safe: lseg_center, path_poly, box_center,
box_circle, poly_center, poly_circle, circle_box.

Function that casting type circle to type polygon cannot be error safe, because
it's a SQL function.

Discussion: https://postgr.es/m/CACJufxHCMzrHOW=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/geo_ops.c | 199 +++++++++++++++++++++++++-------
 1 file changed, 157 insertions(+), 42 deletions(-)

index c655b015c14..5b156074797 100644

 /* Routines for boxes */
+static bool box_cn(Point *center, BOX *box, Node *escontext);

-	box_cn(&a, box1);
-	box_cn(&b, box2);
+	(void) box_cn(&a, box1, fcinfo->context);
+	(void) box_cn(&b, box2, fcinfo->context);
 
 	PG_RETURN_FLOAT8(point_dt(&a, &b, fcinfo->context));
 }
@@ -851,7 +852,8 @@ box_center(PG_FUNCTION_ARGS)
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	Point	   *result = palloc_object(Point);
 
-	box_cn(result, box);
+	if (!box_cn(result, box, fcinfo->context))
+		PG_RETURN_NULL();
 
 	PG_RETURN_POINT_P(result);
 }
@@ -868,14 +870,31 @@ box_ar(BOX *box)

-
 /*		box_wd	-		returns the width (length) of the box
  *								  (horizontal magnitude).
  */
@@ -2329,13 +2348,31 @@ lseg_center(PG_FUNCTION_ARGS)
 {
 	LSEG	   *lseg = PG_GETARG_LSEG_P(0);
 	Point	   *result;
+	float8		x;
+	float8		y;
 
 	result = palloc_object(Point);

@@ -3290,7 +3327,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
 
 	if (result != NULL)
 	{
-		box_cn(&point, box);
+		(void) box_cn(&point, box, NULL);
 		lseg_closept_point(result, lseg, &point);
 	}
 
@@ -4121,11 +4158,20 @@ construct_point(PG_FUNCTION_ARGS)

 static inline void
 
 Datum
@@ -4137,7 +4183,7 @@ point_add(PG_FUNCTION_ARGS)
 
 	result = palloc_object(Point);
 
-	point_add_point(result, p1, p2);
+	point_add_point(result, p1, p2, fcinfo->context);
 
 	PG_RETURN_POINT_P(result);
 }
@@ -4249,8 +4295,8 @@ box_add(PG_FUNCTION_ARGS)
 
 	result = palloc_object(BOX);
 
-	point_add_point(&result->high, &box->high, p);
-	point_add_point(&result->low, &box->low, p);
+	point_add_point(&result->high, &box->high, p, fcinfo->context);
+	point_add_point(&result->low, &box->low, p, fcinfo->context);
 
 	PG_RETURN_BOX_P(result);
 }
@@ -4413,7 +4459,7 @@ path_add_pt(PG_FUNCTION_ARGS)
 	int			i;
 
 	for (i = 0; i < path->npts; i++)
-		point_add_point(&path->p[i], &path->p[i], point);
+		point_add_point(&path->p[i], &path->p[i], point, fcinfo->context);
 
 	PG_RETURN_PATH_P(path);
 }
@@ -4471,7 +4517,7 @@ path_poly(PG_FUNCTION_ARGS)

@@ -4521,7 +4567,9 @@ poly_center(PG_FUNCTION_ARGS)
 
 	result = palloc_object(Point);

 	PG_RETURN_POINT_P(result);
@@ -4983,7 +5031,7 @@ circle_add_pt(PG_FUNCTION_ARGS)
 
 	result = palloc_object(CIRCLE);
 
-	point_add_point(&result->center, &circle->center, point);
+	point_add_point(&result->center, &circle->center, point, fcinfo->context);
 	result->radius = circle->radius;
 
 	PG_RETURN_CIRCLE_P(result);
@@ -5204,14 +5252,30 @@ circle_box(PG_FUNCTION_ARGS)
 
 	box = palloc_object(BOX);

 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()
@@ -5222,15 +5286,35 @@ box_circle(PG_FUNCTION_ARGS)
 {
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	CIRCLE	   *circle;
+	float8		x;
+	float8		y;
 
 	circle = palloc_object(CIRCLE);

 	circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;

@@ -5289,15 +5373,16 @@ circle_poly(PG_FUNCTION_ARGS)
 /*
  * Convert polygon to circle
  *
- * The result must be preallocated.
+ * The parameter "result" must be preallocated.
  *
  * XXX This algorithm should use weighted means of line segments
  *	rather than straight average values of points - tgl 97/01/21.
  */
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext)
 {
 	int			i;
+	float8		x;
 
 	Assert(poly->npts > 0);
 
@@ -5306,14 +5391,43 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
 	result->radius = 0;

 
 Datum
@@ -5324,7 +5438,8 @@ poly_circle(PG_FUNCTION_ARGS)
 
 	result = palloc_object(CIRCLE);

From 84b13cb92e3644b4016803c6e91f55ed43edab45 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 9 Dec 2025 14:36:39 +0800
Subject: [PATCH v19 18/23] refactor
 float_overflow_error,float_underflow_error,float_zero_divide_error

index c076918fd48..fa6b3852f92 100644
 
 	r = a - b;
 
index d7386e885a2..79719fdb725 100644
 
 	r = arg1 - arg2;

 	r = a - b;
 
index 979280e1478..b86a2b097a8 100644
 
 pg_noinline void
 
 pg_noinline void
 
 	result = sqrt(arg1);

 	result = cbrt(arg1);

 
 
 	result = log(arg1);

 	result = log10(arg1);

 	result = acos(arg1);

 	result = asin(arg1);

 

 

 

 

 

 

 

 

@@ -2879,7 +2879,7 @@ dlgamma(PG_FUNCTION_ARGS)
 
@@ -3019,7 +3019,7 @@ float8_combine(PG_FUNCTION_ARGS)
 
 	/*
@@ -3086,7 +3086,7 @@ float8_accum(PG_FUNCTION_ARGS)
 
 			Sxx = get_float8_nan();
 		}
@@ -3169,7 +3169,7 @@ float4_accum(PG_FUNCTION_ARGS)
 
 			Sxx = get_float8_nan();
 		}
@@ -3436,7 +3436,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
 				(isinf(Sxy) &&
 
 			if (isinf(Sxx))
 				Sxx = get_float8_nan();
@@ -3609,15 +3609,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
index b340678ca92..d2e989960a5 100644
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }

 	return result;
 }

 	return result;
 }
-- 
2.34.1

From 103bf705197419ea5a820c42909fc7590d7e8161 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 2 Jan 2026 16:14:28 +0800
Subject: [PATCH v19 19/23] introduce float8 safe function

index d2e989960a5..8723b4c00b0 100644
 
+static inline float8
 
 static inline float4
 
+static inline float8
 
 static inline float4
 
 static inline float8
 
 	result = val1 * val2;
 
 	return result;
 }
 
+static inline float8
 
 static inline float8

 	return result;
 }
 
+static inline float8

From 4e82e0f0afb94d08c835dfbe3c43e7e42167ddf2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v19 17/23] error safe for casting jsonb to other types per
 pg_cast

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 0a3a77ee786..8ea9fa1c79d 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1818,7 +1818,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
  * Emit correct, translatable cast error message
  */
 static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
 {
 	static const struct
 	{
@@ -1839,7 +1839,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)

@@ -1854,7 +1854,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	JsonbValue	v;

 	if (v.type == jbvNull)
 	{
@@ -1863,7 +1866,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);
 
@@ -1878,7 +1884,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	Numeric		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1887,7 +1896,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	}

 	/*
 	 * v.val.numeric points into jsonb body, so we need to make a copy to
@@ -1908,7 +1920,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1917,7 +1932,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int2,
 								   NumericGetDatum(v.val.numeric));
@@ -1935,7 +1953,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1944,7 +1965,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int4,
 								   NumericGetDatum(v.val.numeric));
@@ -1962,7 +1986,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1971,7 +1998,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int8,
 								   NumericGetDatum(v.val.numeric));
@@ -1989,7 +2019,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1998,7 +2031,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_float4,
 								   NumericGetDatum(v.val.numeric));
@@ -2016,7 +2052,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -2025,7 +2064,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	}

From 270e1b1e52aa4494ced360d8c2226486c7e7b1af Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v19 16/23] error safe for casting timestamp to other types per
 pg_cast

index be52777d88d..a057471099d 100644

 
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0487f8457f2..74df13c61c8 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -350,7 +350,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
 
 	result = timestamp;

 	PG_RETURN_TIMESTAMP(result);
 }
@@ -6430,8 +6431,13 @@ Datum
 timestamp_timestamptz(PG_FUNCTION_ARGS)
 {
 	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);
+	TimestampTz result;

 /*
-- 
2.34.1

From e5bba159b5b5614766be247c7094f75ec4e4302b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v19 15/23] error safe for casting timestamptz to other types
 per pg_cast

index bc4c67775dd..be52777d88d 100644

 

 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 8cd97cca926..0487f8457f2 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -873,7 +873,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
 
 	result = timestamp;

 	PG_RETURN_TIMESTAMPTZ(result);
 }
@@ -6492,8 +6493,13 @@ Datum
 timestamptz_timestamp(PG_FUNCTION_ARGS)
 {
 	TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	result;

 /*
-- 
2.34.1

From 4c2a43343b5bbcfaca7faf3e5e458803eb0b5473 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v19 12/23] error safe for casting float8 to other types per
 pg_cast

index f6633ded092..979280e1478 100644
 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */
 
index 8fd551bd52b..19bb30f2d0f 100644
 
 	/* Range check */
 
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 916053007ea..5c2e12fe78e 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4559,7 +4559,8 @@ float8_numeric(PG_FUNCTION_ARGS)
 	init_var(&result);

 
-- 
2.34.1

From c296d5e1f4e679fa09e89233e62e10e7ae579fc7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 12 Dec 2025 15:05:26 +0800
Subject: [PATCH v19 14/23] error safe for casting interval to other types per
 pg_cast

index e8dc8d276bf..bc4c67775dd 100644

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 6f8cf29c910..8cd97cca926 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1332,7 +1332,8 @@ interval_scale(PG_FUNCTION_ARGS)
 	result = palloc_object(Interval);
 	*result = *interval;

From 3db8e38b581249bfceef304aa2999b5d6f6ed06f Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v19 13/23] error safe for casting date to other types per
 pg_cast

index 621b9175c12..e8dc8d276bf 100644
 
-/*

---

From: Corey Huinker <[email-masked]>
Date: Thu, 12 Mar 2026 23:59:17 -0400

> I did one more round of comment refactoring and variable renaming.

Can you explain this bit below?

@@ -595,13 +605,23 @@ RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode,
  {
  int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;

---

From: jian he <[email-masked]>
Date: Fri, 13 Mar 2026 13:48:26 +0800

> + ereturn(escontext, InvalidOid,

This is for supporting casting text to regclass error safe, for example:
SELECT CAST('abc'::text as regclass default NULL on conversion error);

To do that, we need to refactor the RangeVarGetRelidExtended function
to make it error-safe.
We can also change it as:

if (relation->schemaname)
{
    if (elevel == DEBUG1)
        ereport(DEBUG1,
                (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
                errmsg("could not obtain lock on relation \"%s.%s\"",
                        relation->schemaname, relation->relname)));
    else
        ereturn(escontext, InvalidOid,
                errcode(ERRCODE_LOCK_NOT_AVAILABLE),
                errmsg("could not obtain lock on relation \"%s.%s\"",
                    relation->schemaname, relation->relname));
}
else
{
    if (elevel == DEBUG1)
        ereport(DEBUG1,
                errcode(ERRCODE_LOCK_NOT_AVAILABLE),
                errmsg("could not obtain lock on relation \"%s\"",
                    relation->relname));
    else
        ereturn(escontext, InvalidOid,
                (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
                errmsg("could not obtain lock on relation \"%s\"",
                        relation->relname)));
}

---

From: Corey Huinker <[email-masked]>
Date: Fri, 13 Mar 2026 02:10:13 -0400

> We can also change it as:

Ok, thanks.

Hmm, seems we've got a new compile error:

FAILED: [code=1] src/backend/postgres_lib.a.p/executor_execExpr.c.o
cc -Isrc/backend/postgres_lib.a.p -Isrc/include -I../src/include
-I/usr/include/libxml2 -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64
-Wall -Winvalid-pch -O2 -g -fno-strict-aliasing -fwrapv
-fexcess-precision=standard -D_GNU_SOURCE -Wmissing-prototypes
-Wpointer-arith -Werror=vla -Wendif-labels -Wmissing-format-attribute
-Wcast-function-type -Wshadow=compatible-local -Wformat-security
-Wimplicit-fallthrough=5 -Wdeclaration-after-statement
-Wmissing-variable-declarations -Wno-format-truncation
-Wno-stringop-truncation -fPIC -DWITH_GZFILEOP -pthread -DBUILDING_DLL -MD
-MQ src/backend/postgres_lib.a.p/executor_execExpr.c.o -MF
src/backend/postgres_lib.a.p/executor_execExpr.c.o.d -o
src/backend/postgres_lib.a.p/executor_execExpr.c.o -c
../src/backend/executor/execExpr.c
../src/backend/executor/execExpr.c: In function ‘ExecInitQual’:
../src/backend/executor/execExpr.c:269:49: error: ‘escontext’ undeclared
(first use in this function); did you mean ‘errcontext’?
  269 |         state->escontext = (ErrorSaveContext *) escontext;
      |                                                 ^~~~~~~~~
      |                                                 errcontext
../src/backend/executor/execExpr.c:269:49: note: each undeclared identifier
is reported only once for each function it appears in

---

From: jian he <[email-masked]>
Date: Fri, 13 Mar 2026 14:19:14 +0800

Hi.
please check the attached v20.

--
jian
https://www.enterprisedb.com/
From b4017055cc84878e132c294980e75b255da8f518 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 5 Jan 2026 13:33:51 +0800
Subject: [PATCH v20 20/23] refactor point_dt

 

 

 
 static inline float8
 
 Datum

 
 
 Datum

 Datum

 Datum

 
 
 Datum

 Datum
 
 Datum

 

 

 

 

 

 
-- 
2.34.1

From ff578a48bc4cab6186b1436dce8eeba480eb6c02 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 14:16:25 +0800
Subject: [PATCH v20 21/23] error safe for casting geometry data type

 
 
 /* Routines for boxes */

-	box_cn(&a, box1);
-	box_cn(&b, box2);

-	box_cn(result, box);

 
-

 

 	if (result != NULL)
 	{
-		box_cn(&point, box);

 static inline void
 
 Datum

 

 
 	PG_RETURN_BOX_P(result);
 }

 	PG_RETURN_PATH_P(path);
 }

 

 

 

 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()

 

 
 	Assert(poly->npts > 0);

 
 
 Datum

 

From fbbdf762cece2156808b5f4423a8f9fec72a1bbe Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 13 Mar 2026 14:14:58 +0800
Subject: [PATCH v20 22/23] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Example:

 src/backend/executor/execExpr.c               |  68 +-
 38 files changed, 2042 insertions(+), 56 deletions(-)

 

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
 
    <para>
 
index bd46b75e498..c924e7b123f 100644
@@ -1734,6 +1737,7 @@ ExecInitExprRec(Expr *node, ExprState *state,

 				ExecInitExprRec(acoerce->elemexpr, elemstate,
 								&elemstate->resvalue, &elemstate->resnull);
@@ -2209,6 +2213,15 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
@@ -2769,7 +2782,8 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4768,6 +4782,58 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*

 /*

 /*

 
 
 /*
 
 	/*
 	 * And evaluate it.

 	n->arg = arg;
 	n->typeName = typename;
+	n->defexpr = NULL;
 	n->location = location;
 	return (Node *) n;
 }
 
+static Node *
 
 			break;

 
 	/*
 
 		/*
 
 		/*

 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
 	/*

 		if (coerce_hard)
 		{
 
 /*

 
 
 /*
 
+/*

 

 
 
+/*
+ * SafeTypeCastExpr -

 

 # ----------

 

From 18e3eab1a6777a7fc220c71bb538fce9d6aa9239 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 17:10:45 +0800
Subject: [PATCH v20 23/23] error safe for user defined CREATE CAST

 

 

 

 
 
 -- Explicit casts.

 

 

 

 

 

 		case 1:
 
 		case 2:
 
 		default:
-			ereport(ERROR,

 

+    <varlistentry>

     <para>

 
+		if (stmt->safe &&

 

 
 					n->sourcetype = $4;
 					n->targettype = $6;
-					n->func = $10;
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;

 
 
 			ReleaseSysCache(tuple);
 		}

 

@@ -316,57 +316,57 @@

 
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 END_CATALOG_STRUCT
 
 #endif							/* PG_CAST_H */

 
 
 SELECT c.*

 SELECT c.*

From da026036843349f732bdc4d62ac10fd88d7c4b54 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 09:38:53 +0800
Subject: [PATCH v20 04/23] error safe for casting text to other types per
 pg_cast

 

 

 
 
 	if (namelist == NIL)
-		ereport(ERROR,

From d36cc9c0256b9857070af7d5f7171de64dce065a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 2 Jan 2026 16:14:28 +0800
Subject: [PATCH v20 19/23] introduce float8 safe function

 
+static inline float8
 
 static inline float4
 
+static inline float8
 
 static inline float4
 
 static inline float8
 
 	result = val1 * val2;
 
 	return result;
 }
 
+static inline float8
 
 static inline float8

 	return result;
 }
 
+static inline float8

From 89621bc186c328b2b72957941ab51461e6b14bd2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v20 17/23] error safe for casting jsonb to other types per
 pg_cast

 

 
 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From 92811233aa92ea1680d1f1be2e39fe900123a7a3 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 9 Dec 2025 14:36:39 +0800
Subject: [PATCH v20 18/23] refactor
 float_overflow_error,float_underflow_error,float_zero_divide_error

 
 	r = a - b;

 	r = arg1 - arg2;

 	r = a - b;

 pg_noinline void
 
 pg_noinline void
 
 	result = sqrt(arg1);

 	result = cbrt(arg1);

 
 
 	result = log(arg1);

 	result = log10(arg1);

 	result = acos(arg1);

 	result = asin(arg1);

 

 

 

 

 

 

 

 

 
 
 	/*
 
 			Sxx = get_float8_nan();
 		}
 
 			Sxx = get_float8_nan();
 		}
 
 			if (isinf(Sxx))
 				Sxx = get_float8_nan();
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }

 	return result;
 }

 	return result;
 }
-- 
2.34.1

From 5e5ac83632b8a26fbef5da58e919eb4e6ec8aceb Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v20 16/23] error safe for casting timestamp to other types per
 pg_cast

 

 
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 2302e77c5b8710f1034ccf841989bf64e9c00f10 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v20 15/23] error safe for casting timestamptz to other types
 per pg_cast

 

 

 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From d1138e88b7ba45007fd12d74035269389bf8a2f2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 12 Dec 2025 15:05:26 +0800
Subject: [PATCH v20 14/23] error safe for casting interval to other types per
 pg_cast

 

 

From 34abcaffd7a369bdb1f25c42121e7b4fdff15ef5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v20 12/23] error safe for casting float8 to other types per
 pg_cast

 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From fcea30bdc36dad85b8443f810ae541242b021be0 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v20 13/23] error safe for casting date to other types per
 pg_cast

 
-/*

 

From c06108cc68b681a8944d62dcf50c771b52dfc8a4 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v20 11/23] error safe for casting float4 to other types per
 pg_cast

 
 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From bf5024b73466563fa03c6401e31fd89c87e8e25b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v20 10/23] error safe for casting numeric to other types per
 pg_cast

 

 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

 
 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 471f9efbe363c1592106c315c2148434f4649034 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v20 09/23] error safe for casting bigint to other types per
 pg_cast

 

 

-- 
2.34.1

From ea90a055e0c5627d8d4954e34710cefb820e18bc Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 12 Dec 2025 15:03:06 +0800
Subject: [PATCH v20 07/23] error safe for casting macaddr8 to other types per
 pg_cast

 

From 6b889e89af956f857bbc87db489ac700d500d9e7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v20 06/23] error safe for casting inet to other types per
 pg_cast

 
 
-- 
2.34.1

From 02105b2efe576cde59953a983a4e2e8130575cd6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v20 08/23] error safe for casting integer to other types per
 pg_cast

 

 
-- 
2.34.1

From 89eb1e58f75378c884f9f9d67da98062163ce9e8 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v20 05/23] error safe for casting character varying to other
 types per pg_cast

From d0e50a47d538b1cd62222efd2dccc275c010c3ea Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 09:36:05 +0800
Subject: [PATCH v20 03/23] error safe for casting character to other types per
 pg_cast

 

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;

From 0257422d47958d9a3ba1a5202fb62002d8e44455 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v20 01/23] error safe for casting bytea to other types per
 pg_cast

 

 

-- 
2.34.1

From b2a7ba63cb07cf27a2d8840b8e6296d6d0346c12 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v20 02/23] error safe for casting bit/varbit to other types
 per pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
 
 	if (!isExplicit)
-		ereport(ERROR,

 
 
-- 
2.34.1

---

From: Corey Huinker <[email-masked]>
Date: Sun, 15 Mar 2026 23:48:47 -0400

> please check the attached v20.

LGTM.

---

From: jian he <[email-masked]>
Date: Tue, 17 Mar 2026 13:51:15 +0800

> LGTM.

Thanks for the review.
For the context of
v20-0022-CAST-expr-AS-newtype-DEFAULT-expr-ON-CONVERSION-ERROR.patch

+/*

Inspired by https://commitfest.postgresql.org/patch/5759/
I found out the above code is wrong, because it failed to imagine
cases like domain over a domain array.
for example:
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;
select typname,typtype, typbasetype::regtype, typelem::regtype
from pg_type
where typname in ('d_int42', 'd_int42arr', 'd_int42arr1', '_d_int42');

Therefore we need a little bit of recursive to get the base elements type,
please check new function: CoercionErrorSafe_Internal in v21-0022.

Given the interaction with array and domain coercion, we need to think more
about error-safe type casts for user-defined range, multirange, and composite
types.  For now, we should disallow error-safe casting for these types.

Subject: [PATCH v21 03/23] error safe for casting character to other types per
 pg_cast

 

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;

From 4177fc3be8f3608d71d0b689721d849787445c39 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 17 Mar 2026 11:51:52 +0800
Subject: [PATCH v21 22/23] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Example:

 src/test/regress/expected/cast.out            | 860 ++++++++++++++++++
 src/test/regress/sql/cast.sql                 | 366 ++++++++
 38 files changed, 2157 insertions(+), 55 deletions(-)

 

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
 
    <para>

 

 
 
+/*

 /*

 /*

 
 
 /*
 
 	/*
 	 * And evaluate it.

 	n->arg = arg;
 	n->typeName = typename;
+	n->defexpr = NULL;
 	n->location = location;
 	return (Node *) n;
 }
 
+static Node *
 
 			break;

 
 	/*
 
 		/*
 
 		/*
index 96991cae764..3e2f2e2d7ff 100644
 
@@ -77,6 +80,11 @@ static Node *transformWholeRowRef(ParseState *pstate,
+static void CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
+									   bool *errorsafe_coercion,
+									   bool *userdefined);
@@ -165,7 +173,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
@@ -565,6 +573,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
@@ -1834,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -2021,17 +2033,23 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 
 	/*
 	 * Transform the element expressions
@@ -2055,9 +2073,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  is_coercible);
 			/* we certainly have an array here */
-			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			Assert(is_coercible || array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
 		}
 		else
@@ -2098,6 +2117,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	}
 	else
 	{
+		Assert(is_coercible == NULL);
+
 		/* Can't handle an empty array without a target type */
 		if (newelems == NIL)
 			ereport(ERROR,
@@ -2135,6 +2156,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		coerce_hard = false;
 	}
 
@@ -2144,28 +2167,79 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
 		if (coerce_hard)
 		{
@@ -2706,7 +2780,8 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
 }
 
 /*
@@ -2714,17 +2789,63 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,

@@ -2753,7 +2874,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
@@ -2774,20 +2896,243 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 
+		/* Cannot coerce. Set is_coercible to false or raise an error */
+		/* No DEFAULT ... ON CONVERSION ERROR, exit now */
+	/* Further check for CAST(... DEFAULT ... ON CONVERSION ERROR) */
+		/* Cannot coerce */
+	 * Binary coercion cast is error-safe, CoerceViaIO can also be evaluated
+	 * in an error-safe manner. Skip these cases.
+	if (castexpr == NULL ||
+		IsBinaryCoercible(inputType, targetType) ||
+	CoercionErrorSafe_Internal(inputType, targetType,
+							   &errorsafe_coercion,
+							   &userdefined);
 
-	return result;
+static void
+CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
+						   bool *errorsafe_coercion, bool *userdefined)
+{
+	HeapTuple	tuple;
+
+	char		input_typtype = get_typtype(inputType);
+	char		target_typtype = get_typtype(targetType);
+
+	/* since this function recurses, it could be driven to stack overflow */
+	check_stack_depth();
+
+	Assert(errorsafe_coercion != NULL);
+	Assert(userdefined != NULL);
+
+	if (!(*errorsafe_coercion))
+		return;
+
+	if (input_typtype == TYPTYPE_DOMAIN &&
+		target_typtype == TYPTYPE_DOMAIN)
+	{
+		CoercionErrorSafe_Internal(getBaseType(inputType),
+								   getBaseType(targetType),
+								   errorsafe_coercion,
+								   userdefined);
+		return;
+	}
+	else if (input_typtype == TYPTYPE_DOMAIN)
+	{
+		CoercionErrorSafe_Internal(getBaseType(inputType),
+								   targetType,
+								   errorsafe_coercion,
+								   userdefined);
+		return;
+	}
+	else if (target_typtype == TYPTYPE_DOMAIN)
+	{
+		CoercionErrorSafe_Internal(inputType,
+								   getBaseType(targetType),
+								   errorsafe_coercion,
+								   userdefined);
+		return;
+	}
+	else if ((input_typtype != TYPTYPE_BASE && inputType > FirstUnpinnedObjectId)
+			 || (target_typtype != TYPTYPE_BASE && targetType > FirstUnpinnedObjectId))
+	{
+		/*
+		 * Composite-to-composite casting is not implemented, so error-safe
+		 * cast between them should not be possible; we forbid it here.
+		 *
+		 * Our type system does not automatically cast a user-defined range
+		 * type to an built-in range type, even if their base element types
+		 * are the same. There may be complex edge cases to consider, safer to
+		 * simply disallow error-safe casting for them now.
+		 */
+		*errorsafe_coercion = false;
+
+		return;
+	}
+	else
+	{
+		Oid			input_typelem = get_element_type(inputType);
+		Oid			target_typelem = get_element_type(targetType);
+
+		if (OidIsValid(input_typelem) && OidIsValid(target_typelem))
+		{
+			/* recurse into array element type */
+			CoercionErrorSafe_Internal(input_typelem,
+									   target_typelem,
+									   errorsafe_coercion,
+									   userdefined);
+			return;
+		}
+	}
+
+	/*
+	 * Casts involving type MONEY are not error safe.  Type CIRCLE to type
+	 * POLYGON cast is unsafe because it relies on a SQL-language function;
+	 * only C-language functions are currently supported for error safe casts.
+	 */
+	if (inputType == MONEYOID ||
+		targetType == MONEYOID ||
+		(inputType == CIRCLEOID && targetType == POLYGONOID))
+	{
+		*errorsafe_coercion = false;
+		return;
+	}
+
+	if (inputType != targetType)
+	{
+		tuple = SearchSysCache2(CASTSOURCETARGET,
+								ObjectIdGetDatum(inputType),
+								ObjectIdGetDatum(targetType));
+
+		/*
+		 * A pg_cast entry might not exist for this specific cast; for
+		 * example, when using CoerceViaIO.
+		 */
+		if (HeapTupleIsValid(tuple))
+		{
+			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+			if (castForm->oid > FirstUnpinnedObjectId &&
+				castForm->castmethod != COERCION_METHOD_BINARY)
+			{
+				*errorsafe_coercion = false;
+				*userdefined = true;
+			}
+			ReleaseSysCache(tuple);
+		}
+		else if (inputType > FirstUnpinnedObjectId && targetType > FirstUnpinnedObjectId)
+			*errorsafe_coercion = false;
+	}
 }
 
 /*
@@ -3203,6 +3548,8 @@ ParseExprKindName(ParseExprKind exprKind)
 
+/*

 

 
 
+/*
+ * SafeTypeCastExpr -

 
index 00000000000..c6ac9597625
@@ -0,0 +1,860 @@
+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[];
+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);
+ {42}
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr DEFAULT '{1}' ON CONVERSION ERROR);
+ {42}
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42, 41]::d_int42arr1 AS d_int8arr DEFAULT '{1,2,3}' ON CONVERSION ERROR);
+ {1,2,3}
+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);
+ {"{42,42}"}
+DROP DOMAIN d_int8arr;
+DROP DOMAIN d_int8;
+DROP DOMAIN d_int42arr1;
+DROP DOMAIN d_int42arr;

 # ----------
index 00000000000..62d49b94ebb
@@ -0,0 +1,366 @@
+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[];
+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]::d_int42arr1 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);
+DROP DOMAIN d_int8arr;
+DROP DOMAIN d_int8;
+DROP DOMAIN d_int42arr1;
+DROP DOMAIN d_int42arr;

 

From ff578a48bc4cab6186b1436dce8eeba480eb6c02 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 14:16:25 +0800
Subject: [PATCH v21 21/23] error safe for casting geometry data type

 
 
 /* Routines for boxes */

-	box_cn(&a, box1);
-	box_cn(&b, box2);

-	box_cn(result, box);

 
-

 

 	if (result != NULL)
 	{
-		box_cn(&point, box);

 static inline void
 
 Datum

 

 
 	PG_RETURN_BOX_P(result);
 }

 	PG_RETURN_PATH_P(path);
 }

 

 

 

 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()

 

 
 	Assert(poly->npts > 0);

 
 
 Datum

 

From de8413ecda0d55bde1629f81c38c8de153ea7209 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 17 Mar 2026 11:45:03 +0800
Subject: [PATCH v21 23/23] error safe for user defined CREATE CAST

 src/backend/parser/parse_expr.c           |  33 +--
 24 files changed, 361 insertions(+), 233 deletions(-)

 

 

 

 
 -- Explicit casts.

 

 

 

 

 

 		case 1:
 
 		case 2:
 
 		default:
-			ereport(ERROR,

 

+    <varlistentry>

     <para>

 
+		if (stmt->safe &&

 

 
 					n->sourcetype = $4;
 					n->targettype = $6;
-					n->func = $10;
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;

 DropCastStmt: DROP CAST opt_if_exists '(' Typename AS Typename ')' opt_drop_behavior
 				{
@@ -18274,6 +18280,7 @@ unreserved_keyword:
 			| ROUTINES
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
@@ -18913,6 +18920,7 @@ bare_label_keyword:
 			| ROW
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 3e2f2e2d7ff..122c655fff9 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -83,8 +83,7 @@ static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
 								   Node *source, Oid inputType, Oid targetType);
 static void CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
-									   bool *errorsafe_coercion,
-									   bool *userdefined);
+									   bool *errorsafe_coercion);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
 											JsonObjectConstructor *ctor);
@@ -2991,7 +2990,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 					   Oid inputType, Oid targetType)
 {
 	bool		errorsafe_coercion = true;
-	bool		userdefined = false;
 
 	/*
 	 * Binary coercion cast is error-safe, CoerceViaIO can also be evaluated
@@ -3003,8 +3001,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 		return;
 
 	CoercionErrorSafe_Internal(inputType, targetType,
-							   &errorsafe_coercion,
-							   &userdefined);
+							   &errorsafe_coercion);
 
 	if (!errorsafe_coercion)
 		ereport(ERROR,
@@ -3014,15 +3011,13 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 
 static void
 CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
-						   bool *errorsafe_coercion, bool *userdefined)
+						   bool *errorsafe_coercion)
 {
 	HeapTuple	tuple;
 
@@ -3033,7 +3028,6 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 	check_stack_depth();
 
 	Assert(errorsafe_coercion != NULL);
-	Assert(userdefined != NULL);
 
 	if (!(*errorsafe_coercion))
 		return;
@@ -3043,24 +3037,21 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 	{
 		CoercionErrorSafe_Internal(getBaseType(inputType),
 								   getBaseType(targetType),
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if (input_typtype == TYPTYPE_DOMAIN)
 	{
 		CoercionErrorSafe_Internal(getBaseType(inputType),
 								   targetType,
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if (target_typtype == TYPTYPE_DOMAIN)
 	{
 		CoercionErrorSafe_Internal(inputType,
 								   getBaseType(targetType),
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if ((input_typtype != TYPTYPE_BASE && inputType > FirstUnpinnedObjectId)
@@ -3089,8 +3080,7 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 			/* recurse into array element type */
 			CoercionErrorSafe_Internal(input_typelem,
 									   target_typelem,
-									   errorsafe_coercion,
-									   userdefined);
+									   errorsafe_coercion);
 			return;
 		}
 	}
@@ -3122,16 +3112,13 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 		{
 			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
 
-			if (castForm->oid > FirstUnpinnedObjectId &&
-				castForm->castmethod != COERCION_METHOD_BINARY)
+			if (castForm->castmethod != COERCION_METHOD_BINARY &&
+				!castForm->casterrorsafe)
 			{
 				*errorsafe_coercion = false;
-				*userdefined = true;
 			}
 			ReleaseSysCache(tuple);
 		}
-		else if (inputType > FirstUnpinnedObjectId && targetType > FirstUnpinnedObjectId)
-			*errorsafe_coercion = false;
 	}
 }

 

@@ -316,57 +316,57 @@

 
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 END_CATALOG_STRUCT
 
 #endif							/* PG_CAST_H */

 
 
 SELECT c.*

 SELECT c.*

From 2302e77c5b8710f1034ccf841989bf64e9c00f10 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v21 15/23] error safe for casting timestamptz to other types
 per pg_cast

 

 

 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From b4017055cc84878e132c294980e75b255da8f518 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 5 Jan 2026 13:33:51 +0800
Subject: [PATCH v21 20/23] refactor point_dt

 

 

 
 static inline float8
 
 Datum

 
 
 Datum

 Datum

 Datum

 
 
 Datum

 Datum
 
 Datum

 

 

 

 

 

 
-- 
2.34.1

From d36cc9c0256b9857070af7d5f7171de64dce065a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 2 Jan 2026 16:14:28 +0800
Subject: [PATCH v21 19/23] introduce float8 safe function

 
+static inline float8
 
 static inline float4
 
+static inline float8
 
 static inline float4
 
 static inline float8
 
 	result = val1 * val2;
 
 	return result;
 }
 
+static inline float8
 
 static inline float8

 	return result;
 }
 
+static inline float8

From 89621bc186c328b2b72957941ab51461e6b14bd2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v21 17/23] error safe for casting jsonb to other types per
 pg_cast

 

 
 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From 5e5ac83632b8a26fbef5da58e919eb4e6ec8aceb Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v21 16/23] error safe for casting timestamp to other types per
 pg_cast

 

 
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

Subject: [PATCH v21 18/23] refactor

 
 	r = a - b;

 	r = arg1 - arg2;

 	r = a - b;

 pg_noinline void
 
 pg_noinline void
 
 	result = sqrt(arg1);

 	result = cbrt(arg1);

 
 
 	result = log(arg1);

 	result = log10(arg1);

 	result = acos(arg1);

 	result = asin(arg1);

 

 

 

 

 

 

 

 

 
 
 	/*
 
 			Sxx = get_float8_nan();
 		}
 
 			Sxx = get_float8_nan();
 		}
 
 			if (isinf(Sxx))
 				Sxx = get_float8_nan();
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }

 	return result;
 }

 	return result;
 }
-- 
2.34.1

From d1138e88b7ba45007fd12d74035269389bf8a2f2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 12 Dec 2025 15:05:26 +0800
Subject: [PATCH v21 14/23] error safe for casting interval to other types per
 pg_cast

 

 

From 34abcaffd7a369bdb1f25c42121e7b4fdff15ef5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v21 12/23] error safe for casting float8 to other types per
 pg_cast

 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From fcea30bdc36dad85b8443f810ae541242b021be0 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v21 13/23] error safe for casting date to other types per
 pg_cast

 
-/*

 

From c06108cc68b681a8944d62dcf50c771b52dfc8a4 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v21 11/23] error safe for casting float4 to other types per
 pg_cast

 
 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From bf5024b73466563fa03c6401e31fd89c87e8e25b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v21 10/23] error safe for casting numeric to other types per
 pg_cast

 

 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

 
 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 471f9efbe363c1592106c315c2148434f4649034 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v21 09/23] error safe for casting bigint to other types per
 pg_cast

 

 

-- 
2.34.1

From 02105b2efe576cde59953a983a4e2e8130575cd6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v21 08/23] error safe for casting integer to other types per
 pg_cast

 

 
-- 
2.34.1

From 89eb1e58f75378c884f9f9d67da98062163ce9e8 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v21 05/23] error safe for casting character varying to other
 types per pg_cast

From ea90a055e0c5627d8d4954e34710cefb820e18bc Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 12 Dec 2025 15:03:06 +0800
Subject: [PATCH v21 07/23] error safe for casting macaddr8 to other types per
 pg_cast

 

From 6b889e89af956f857bbc87db489ac700d500d9e7 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v21 06/23] error safe for casting inet to other types per
 pg_cast

 
 
-- 
2.34.1

From b2a7ba63cb07cf27a2d8840b8e6296d6d0346c12 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v21 02/23] error safe for casting bit/varbit to other types
 per pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
 
 	if (!isExplicit)
-		ereport(ERROR,

 
 
-- 
2.34.1

From da026036843349f732bdc4d62ac10fd88d7c4b54 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 12 Mar 2026 09:38:53 +0800
Subject: [PATCH v21 04/23] error safe for casting text to other types per
 pg_cast

 

 

 
 
 	if (namelist == NIL)
-		ereport(ERROR,

From 0257422d47958d9a3ba1a5202fb62002d8e44455 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v21 01/23] error safe for casting bytea to other types per
 pg_cast

 

 

-- 
2.34.1

---

From: Corey Huinker <[email-masked]>
Date: Tue, 17 Mar 2026 23:11:18 -0400

> + errorsafe_coercion = false;

What if we just reject cast functions with a non-null prosqlbody?

> types.  For now, we should disallow error-safe casting for these types.

I agree. It's far better to get this working for the majority of cases, and
we can chip away at more obscure types later. There will *always* be user
defined types that we cannot account for.

---

From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 16:15:12 +0800

> What if we just reject cast functions with a non-null prosqlbody?

src/include/access/transam.h
 *        OIDs 12000-16383 are reserved for unpinned objects created by initdb's
 *        post-bootstrap processing.  initdb forces the OID generator up to
 *        12000 as soon as it's made the pinned objects it's responsible for.
Based on the above comments, I think comparing FirstUnpinnedObjectId
should be enough.

Rebased because of commit:
https://git.postgresql.org/cgit/postgresql.git/commit/?id=ba21f5bf8aff277aa1659a51d26109e0914df182
which add casting between bytea and uuid.

ExecInitFunc now always sets fcinfo->context from state->escontext
```
InitFunctionCallInfoData(*fcinfo, flinfo,
                         nargs, inputcollid,
                         (Node *) state->escontext, NULL);
```
This change in `ExecInitFunc` means **all** FuncExpr evaluations within a
SafeTypeCastExpr context will have their `fcinfo->context` set to the
`ErrorSaveContext`. This is by design for cast functions, but could
accidentally affect non-cast functions that appear in the cast expression
tree and happen to check `fcinfo->context`.

FunctionCallInfoBaseData->context is a generic poiner, it canbe other
than ErrorSaveContext.
For example, in ExecCallTriggerFunc we have:
```
    InitFunctionCallInfoData(*fcinfo, finfo, 0,
                             InvalidOid, (Node *) trigdata, NULL);
```
Because of this, we must differentiate our FuncExpr nodes. Soft-error evaluation
should be only for FuncExpr nodes produced by build_coercion_expression.

We can add a boolean (errorsafe) flag directly to the FuncExpr struct.
This flag to indicate whether this specific expression should be evaluated in an
error-safe manner. In the future, we can also use this flag for

CAST(expr AS type FORMAT 'template' DEFAULT defexpr ON CONVERSION ERROR)

In build_coercion_expression, we set FuncExpr->errorsafe if the specific cast
function needs to be evaluated in an error-safe manner.
build_coercion_expression is also
used in other callers, such as coerce_type_typmod, coerce_to_domain,
that means more refactoring in parse_coerce.c.

V21-0022 JIT related change is totally wrong.
Matheus Alcantara <[email-masked]> helped me resolve the JIT issue.
Previously, I wasn't using SET jit_above_cost = 0; to properly
exercise the JIT-related tests.

You mentioned refactoring RangeVarGetRelidExtended in [1],
With a hint from Andrew Dunstan, we can actually reduce the code
duplication, see v22-0004.
[1] https://postgr.es/m/CADkLM=[email-masked]

As mentioned before, FunctionCallInfoBaseData->context can be other
Node other than ErrorSaveContext.
Therefore for geometry error safe refactoring, we must pass NULL, not
fcinfo->context, for non-cast realted function.
For example, function point_add_point within box_add, the third
parameter should be NULL, not fcinfo->context.

Handling SafeTypeCastExpr->castexpr within eval_const_expressions_mutator is
tricky. However, if it is a FuncExpr node, we can attempt to fold its arguments
(FuncExpr->args).
(Not doing anything for SafeTypeCastExpr->castexpr is easier).
From 5e30be6cfb15a193e0c50dfac5995bafad3587b5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:18:01 +0800
Subject: [PATCH v22 20/23] refactor point_dt

Refactor point_dt to be error-safe for an upcoming patch use.  Note for future
work: lseg_center, path_poly, box_center, box_circle, poly_center, poly_circle,
and circle_box will need error-safe

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Andrew Dunstan <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/geo_ops.c | 79 +++++++++++++++++++--------------
 1 file changed, 46 insertions(+), 33 deletions(-)

index bfb4859b4cb..350e86f6377 100644

 
-		result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i]));
+		result = float8_pl(result,
+						   point_dt(&path->p[iprev], &path->p[i], NULL));
 	}

 
 static inline float8
 
 Datum

 
 
 Datum

 Datum

 Datum

 
 
 Datum

 Datum
 
 Datum
+					 point_dt(&path->p[i], &path->p[i + 1], NULL)))

 

 

 

 

 

 
-- 
2.34.1

From 4c3b7c85e7f6b3e0b31f7594640e4008a1f9372e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 14:12:01 +0800
Subject: [PATCH v22 01/23] error safe for casting bytea to other types per
 pg_cast

 bytea      | uuid       |     9881 | e           | f          | bytea_uuid | uuid
(4 rows)

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/bytea.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 39ed04ddae8..f6e3266ac32 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1256,7 +1256,7 @@ bytea_int2(PG_FUNCTION_ARGS)

@@ -1281,7 +1281,7 @@ bytea_int4(PG_FUNCTION_ARGS)

@@ -1306,7 +1306,7 @@ bytea_int8(PG_FUNCTION_ARGS)

@@ -1351,7 +1351,7 @@ bytea_uuid(PG_FUNCTION_ARGS)
 	pg_uuid_t  *uuid;
 
 	if (len != UUID_LEN)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
 				 errmsg("invalid input length for type %s", "uuid"),
 				 errdetail("Expected %d bytes, got %d.", UUID_LEN, len)));
-- 
2.34.1

From ec035412aa96fe817859b635a0a22036dc0458a4 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:05:57 +0800
Subject: [PATCH v22 10/23] error safe for casting numeric to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
 1 file changed, 43 insertions(+), 15 deletions(-)

 
 
 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

 
 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 0aa59529e49c0468a2e3b1a654b7c748f4335a27 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:13:17 +0800
Subject: [PATCH v22 16/23] error safe for casting timestamp to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/date.c      |  7 +++++--
 src/backend/utils/adt/timestamp.c | 10 ++++++++--
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index e052e5b2e85..2e826a06e51 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1322,7 +1322,10 @@ timestamp_date(PG_FUNCTION_ARGS)
 	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);
 	DateADT		result;

@@ -2000,7 +2003,7 @@ timestamp_time(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index deabf85955c..288d696be77 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -343,7 +343,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
 
 	result = timestamp;

 	PG_RETURN_TIMESTAMP(result);
 }
@@ -6423,8 +6424,13 @@ Datum
 timestamp_timestamptz(PG_FUNCTION_ARGS)
 {
 	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);
+	TimestampTz result;

 /*
-- 
2.34.1

From c01cb4d504ee637b1164f9e84b95cbacc40a81d4 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 14:14:31 +0800
Subject: [PATCH v22 23/23] error safe for user defined CREATE CAST

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Andrew Dunstan <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 contrib/citext/citext--1.4.sql            |   6 +-
 contrib/citext/expected/citext.out        |  24 +-
 contrib/citext/expected/citext_1.out      |  24 +-
 contrib/citext/sql/citext.sql             |   5 +-
 contrib/hstore/expected/hstore.out        |  37 +++
 contrib/hstore/hstore--1.2--1.3.sql       |   2 +-
 contrib/hstore/hstore--1.4.sql            |   6 +-
 contrib/hstore/hstore_io.c                |   8 +-
 contrib/hstore/sql/hstore.sql             |  11 +
 doc/src/sgml/catalogs.sgml                |  15 +
 doc/src/sgml/ref/create_cast.sgml         |  14 +-
 doc/src/sgml/syntax.sgml                  |   3 +-
 src/backend/catalog/pg_cast.c             |   4 +-
 src/backend/commands/functioncmds.c       |  16 +-
 src/backend/commands/typecmds.c           |   1 +
 src/backend/parser/gram.y                 |  18 +-
 src/backend/parser/parse_expr.c           |  32 +--
 src/include/catalog/pg_cast.dat           | 332 +++++++++++-----------
 src/include/catalog/pg_cast.h             |   5 +
 src/include/nodes/parsenodes.h            |   1 +
 src/include/parser/kwlist.h               |   1 +
 src/test/regress/expected/create_cast.out |   8 +-
 src/test/regress/expected/opr_sanity.out  |  24 +-
 src/test/regress/sql/create_cast.sql      |   5 +
 24 files changed, 369 insertions(+), 233 deletions(-)

 

 

 

 
 -- Explicit casts.

 

 

 

 

 

 		case 1:
 
 		case 2:
 
 		default:
-			ereport(ERROR,

 
index 61ffe804ee8..fef0e72fe5f 100644
@@ -1874,6 +1874,21 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l

+    <varlistentry>
 
index aa0fc1ef81c..6edcb9569e8 100644
 
     <para>

diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 3afd762e9dc..106e9fc2195 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1668,6 +1668,20 @@ CreateCast(CreateCastStmt *stmt)
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("cast function must not return a set")));
 
+		if (stmt->safe)
+		{
+			if (procstruct->prolang != INTERNALlanguageId &&
+				procstruct->prolang != ClanguageId)
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("safe type cast functions are only supported for C and internal languages"));
+
+			if (sourcetyptype != TYPTYPE_BASE || targettyptype != TYPTYPE_BASE)
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("safe type cast is allowed for base type only are only"));
+		}
+
 		ReleaseSysCache(tuple);
 	}
 	else
@@ -1796,7 +1810,7 @@ CreateCast(CreateCastStmt *stmt)
 	}

 
index 4d692274d55..531da18ed21 100644
@@ -358,7 +358,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -813,7 +813,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -9403,14 +9403,15 @@ dostmt_opt_item:

 					n->sourcetype = $4;
 					n->targettype = $6;
-					n->func = $10;
-					n->context = (CoercionContext) $11;
+					n->safe = $9;
+					n->func = $11;
+					n->context = (CoercionContext) $12;
 					n->inout = false;
 					$$ = (Node *) n;
 				}
@@ -9421,6 +9422,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = false;
@@ -9433,6 +9435,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = true;
@@ -9445,6 +9448,9 @@ cast_context:  AS IMPLICIT_P					{ $$ = COERCION_IMPLICIT; }
 		| /*EMPTY*/								{ $$ = COERCION_EXPLICIT; }
 		;

 DropCastStmt: DROP CAST opt_if_exists '(' Typename AS Typename ')' opt_drop_behavior
 				{
@@ -18956,6 +18962,7 @@ unreserved_keyword:
 			| ROUTINES
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
@@ -19605,6 +19612,7 @@ bare_label_keyword:
 			| ROW
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index a1e2fdc9a1f..b832838ce16 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -84,8 +84,7 @@ static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
 								   Node *source, Oid inputType, Oid targetType);
 static void CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
-									   bool *errorsafe_coercion,
-									   bool *userdefined);
+									   bool *errorsafe_coercion);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
 											JsonObjectConstructor *ctor);
@@ -2999,7 +2998,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 					   Oid inputType, Oid targetType)
 {
 	bool		errorsafe_coercion = true;
-	bool		userdefined = false;
 
 	/*
 	 * Binary coercion cast is error-safe, CoerceViaIO can also be evaluated
@@ -3011,8 +3009,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 		return;

 	if (!errorsafe_coercion)
 		ereport(ERROR,
@@ -3022,15 +3019,13 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 
 static void
 
@@ -3041,7 +3036,6 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 	check_stack_depth();

 	if (!(*errorsafe_coercion))
 		return;
@@ -3051,24 +3045,21 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 	{
 		CoercionErrorSafe_Internal(getBaseType(inputType),
 								   getBaseType(targetType),
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if (input_typtype == TYPTYPE_DOMAIN)
 	{
 		CoercionErrorSafe_Internal(getBaseType(inputType),
 								   targetType,
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if (target_typtype == TYPTYPE_DOMAIN)
 	{
 		CoercionErrorSafe_Internal(inputType,
 								   getBaseType(targetType),
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if ((input_typtype != TYPTYPE_BASE && input_typtype != TYPTYPE_PSEUDO && inputType > FirstUnpinnedObjectId)
@@ -3102,8 +3093,7 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 			/* recurse into array element type */
 			CoercionErrorSafe_Internal(input_typelem,
 									   target_typelem,
-									   errorsafe_coercion,
-									   userdefined);
+									   errorsafe_coercion);
 			return;
 		}
 	}
@@ -3135,15 +3125,13 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 		{
 			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
 
-			if (castForm->castfunc > FirstUnpinnedObjectId)
 
index a7b6d812c5a..abb65ddd516 100644

 
 
@@ -316,63 +316,63 @@

 # Allow explicit coercions between bytea and uuid type
 { castsource => 'bytea', casttarget => 'uuid', castfunc => 'uuid(bytea)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'uuid', casttarget => 'bytea', castfunc => 'bytea(uuid)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },

@@ -398,73 +398,73 @@
 
 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category
@@ -474,13 +474,13 @@

@@ -491,68 +491,68 @@

 
 
 # json to/from jsonb
@@ -562,36 +562,36 @@

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 END_CATALOG_STRUCT
 
 #endif							/* PG_CAST_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b45c29d52f4..cedb1180c04 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4242,6 +4242,7 @@ typedef struct CreateCastStmt
 	ObjectWithArgs *func;
 	CoercionContext context;
 	bool		inout;
+	bool		safe;
 } CreateCastStmt;
 
index b7ded6e6088..816e277b67a 100644
@@ -406,6 +406,7 @@ PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD, BARE_LABEL)

 
 SELECT c.*

 SELECT c.*

From c0b9a8e97261c8645f69fcd39cf61f362ce4fa83 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:19:10 +0800
Subject: [PATCH v22 21/23] error safe for casting geometry data type

Function that casting type circle to type polygon cannot be error safe, because
it's a SQL language function.

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Andrew Dunstan <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/geo_ops.c | 203 +++++++++++++++++++++++++-------
 1 file changed, 160 insertions(+), 43 deletions(-)

index 350e86f6377..a59f1cabc44 100644

 /* Routines for boxes */

-	box_cn(&a, box1);
-	box_cn(&b, box2);
+	(void) box_cn(&a, box1, NULL);
+	(void) box_cn(&b, box2, NULL);

-	box_cn(result, box);

 
-

 

 	if (result != NULL)
 	{
-		box_cn(&point, box);

 static inline void
 
 Datum

 

 
 	PG_RETURN_BOX_P(result);
 }

 	PG_RETURN_PATH_P(path);
 }

 

 

 

 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()
@@ -5222,15 +5286,37 @@ box_circle(PG_FUNCTION_ARGS)
 {
 	BOX		   *box = PG_GETARG_BOX_P(0);
 	CIRCLE	   *circle;
+	float8		x;
+	float8		y;

 
+	circle->radius = point_dt(&circle->center, &box->high,

@@ -5289,15 +5375,16 @@ circle_poly(PG_FUNCTION_ARGS)
 /*
 
 	Assert(poly->npts > 0);
 
@@ -5306,14 +5393,43 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
 	result->radius = 0;

 
 Datum
@@ -5324,7 +5440,8 @@ poly_circle(PG_FUNCTION_ARGS)

 

From 28ac873449723de54663df4fb35263065af91e71 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:16:54 +0800
Subject: [PATCH v22 19/23] introduce float8 safe function

refactoring existing function is be too invasive.  thus add these new functions.
It's very similar to existing non-safe version functions.

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/include/utils/float.h | 79 ++++++++++++++++++++++++++++-----------
 1 file changed, 58 insertions(+), 21 deletions(-)

+static inline float8
 
 static inline float4
 
+static inline float8
 
 static inline float4
 
 static inline float8
 
 	result = val1 * val2;
 
 	return result;
 }
 
+static inline float8
 
 static inline float8

 	return result;
 }
 
+static inline float8

From e6cf258e3c32b48af58ded1efe5f9e00991da9fe Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:15:46 +0800
Subject: [PATCH v22 18/23] refactor
 float_overflow_error,float_underflow_error,float_zero_divide_error

make these function error-safe: float_overflow_error, float_underflow_error,
float_zero_divide_error.

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 contrib/btree_gist/btree_float4.c |   2 +-
 contrib/btree_gist/btree_float8.c |   4 +-
 src/backend/utils/adt/float.c     | 104 +++++++++++++++---------------
 src/include/utils/float.h         |  34 +++++-----
 4 files changed, 72 insertions(+), 72 deletions(-)

 	r = a - b;

 	r = arg1 - arg2;

 	r = a - b;
 
index 4423b724f9f..30b42205d5c 100644
@@ -100,25 +100,25 @@ static void init_degree_constants(void);
 
 pg_noinline void
 
 pg_noinline void
@@ -1477,9 +1477,9 @@ dsqrt(PG_FUNCTION_ARGS)
 
 	result = sqrt(arg1);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1496,9 +1496,9 @@ dcbrt(PG_FUNCTION_ARGS)
 
 	result = cbrt(arg1);
 
@@ -1634,24 +1634,24 @@ dpow(PG_FUNCTION_ARGS)
 
@@ -1691,14 +1691,14 @@ dexp(PG_FUNCTION_ARGS)
 
 	PG_RETURN_FLOAT8(result);
@@ -1729,9 +1729,9 @@ dlog1(PG_FUNCTION_ARGS)
 
 	result = log(arg1);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1762,9 +1762,9 @@ dlog10(PG_FUNCTION_ARGS)
 
 	result = log10(arg1);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1795,7 +1795,7 @@ dacos(PG_FUNCTION_ARGS)
 
 	result = acos(arg1);
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -1826,7 +1826,7 @@ dasin(PG_FUNCTION_ARGS)
 
 	result = asin(arg1);
 
@@ -1852,7 +1852,7 @@ datan(PG_FUNCTION_ARGS)
 
@@ -1878,7 +1878,7 @@ datan2(PG_FUNCTION_ARGS)
 
@@ -1919,7 +1919,7 @@ dcos(PG_FUNCTION_ARGS)
 
@@ -1974,7 +1974,7 @@ dsin(PG_FUNCTION_ARGS)
 
 	PG_RETURN_FLOAT8(result);
 }
@@ -2154,7 +2154,7 @@ dacosd(PG_FUNCTION_ARGS)
 		result = 90.0 + asind_q1(-arg1);

 	PG_RETURN_FLOAT8(result);
 }
@@ -2191,7 +2191,7 @@ dasind(PG_FUNCTION_ARGS)
 		result = -asind_q1(-arg1);

 	PG_RETURN_FLOAT8(result);
 }
@@ -2223,7 +2223,7 @@ datand(PG_FUNCTION_ARGS)
 	result = (atan_arg1 / atan_1_0) * 45.0;

 	PG_RETURN_FLOAT8(result);
 }
@@ -2259,7 +2259,7 @@ datan2d(PG_FUNCTION_ARGS)
 	result = (atan2_arg1_arg2 / atan_1_0) * 45.0;

 	PG_RETURN_FLOAT8(result);
 }
@@ -2382,7 +2382,7 @@ dcosd(PG_FUNCTION_ARGS)
 	result = sign * cosd_q1(arg1);

 	PG_RETURN_FLOAT8(result);
 }
@@ -2504,7 +2504,7 @@ dsind(PG_FUNCTION_ARGS)
 	result = sign * sind_q1(arg1);

 	PG_RETURN_FLOAT8(result);
 }
@@ -2662,7 +2662,7 @@ dcosh(PG_FUNCTION_ARGS)
 		result = get_float8_infinity();

 	PG_RETURN_FLOAT8(result);
 }
@@ -2682,7 +2682,7 @@ dtanh(PG_FUNCTION_ARGS)
 	result = tanh(arg1);

 	PG_RETURN_FLOAT8(result);
 }
@@ -2782,7 +2782,7 @@ derf(PG_FUNCTION_ARGS)
 	result = erf(arg1);

 	PG_RETURN_FLOAT8(result);
 }
@@ -2802,7 +2802,7 @@ derfc(PG_FUNCTION_ARGS)
 	result = erfc(arg1);

@@ -2831,7 +2831,7 @@ dgamma(PG_FUNCTION_ARGS)
@@ -2853,12 +2853,12 @@ dgamma(PG_FUNCTION_ARGS)
 
@@ -2896,7 +2896,7 @@ dlgamma(PG_FUNCTION_ARGS)
 
@@ -3036,7 +3036,7 @@ float8_combine(PG_FUNCTION_ARGS)
 
 	/*
@@ -3103,7 +3103,7 @@ float8_accum(PG_FUNCTION_ARGS)
 
 			Sxx = get_float8_nan();
 		}
@@ -3186,7 +3186,7 @@ float4_accum(PG_FUNCTION_ARGS)
 
 			Sxx = get_float8_nan();
 		}
@@ -3453,7 +3453,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
 				(isinf(Sxy) &&
 
 			if (isinf(Sxx))
 				Sxx = get_float8_nan();
@@ -3626,15 +3626,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }

 	return result;
 }

 	return result;
 }
-- 
2.34.1

From a64b615feb38090e779a869a8c45958cc118b21b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 16:10:33 +0800
Subject: [PATCH v22 22/23] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Example:

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Andrew Dunstan <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Reviewed-by: Kirill Reshke <[email-masked]>
Reviewed-by: Matheus Alcantara <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 contrib/citext/expected/citext.out            |   5 +
 contrib/citext/expected/citext_1.out          |   5 +
 contrib/citext/sql/citext.sql                 |   2 +
 .../pg_stat_statements/expected/select.out    |  24 +-
 contrib/pg_stat_statements/sql/select.sql     |   5 +
 doc/src/sgml/syntax.sgml                      |  32 +
 src/backend/executor/execExpr.c               |  72 +-
 src/backend/executor/execExprInterp.c         |  29 +
 src/backend/jit/llvm/llvmjit_expr.c           |  56 ++
 src/backend/nodes/makefuncs.c                 |   1 +
 src/backend/nodes/nodeFuncs.c                 |  53 +
 src/backend/optimizer/util/clauses.c          |  89 +-
 src/backend/parser/gram.y                     |  23 +
 src/backend/parser/parse_agg.c                |   9 +
 src/backend/parser/parse_coerce.c             | 145 ++-
 src/backend/parser/parse_expr.c               | 418 +++++++-
 src/backend/parser/parse_func.c               |   4 +
 src/backend/parser/parse_target.c             |   3 +-
 src/backend/parser/parse_type.c               |  18 +
 src/backend/parser/parse_utilcmd.c            |   2 +-
 src/backend/utils/adt/arrayfuncs.c            |   9 +
 src/backend/utils/adt/ruleutils.c             |  25 +
 src/backend/utils/fmgr/fmgr.c                 |  13 +
 src/include/executor/execExpr.h               |   7 +
 src/include/fmgr.h                            |   3 +
 src/include/nodes/execnodes.h                 |  21 +
 src/include/nodes/parsenodes.h                |   1 +
 src/include/nodes/primnodes.h                 |  27 +
 src/include/optimizer/optimizer.h             |   2 +-
 src/include/parser/parse_coerce.h             |  16 +-
 src/include/parser/parse_node.h               |   2 +
 src/include/parser/parse_type.h               |   2 +
 src/test/regress/expected/cast.out            | 942 ++++++++++++++++++
 src/test/regress/expected/create_cast.out     |   5 +
 src/test/regress/expected/equivclass.out      |   7 +
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/cast.sql                 | 392 ++++++++
 src/test/regress/sql/create_cast.sql          |   1 +
 src/test/regress/sql/equivclass.sql           |   3 +
 src/tools/pgindent/typedefs.list              |   2 +
 40 files changed, 2396 insertions(+), 81 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

 

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
index 67482996861..aa0fc1ef81c 100644
+    The default behavior when no <literal>ON CONVERSION ERROR</literal> clause is specified is equivalent to:
 
    <para>
 
index 77229141b38..6521c7fe2e3 100644

 
@@ -2736,6 +2749,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 	FunctionCallInfo fcinfo;
 	int			argno;
 	ListCell   *lc;
+	Node	   *context = NULL;
 
 	/* Check permission to call function */
 	aclresult = object_aclcheck(ProcedureRelationId, funcid, GetUserId(), ACL_EXECUTE);
@@ -2743,6 +2757,9 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 		aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(funcid));
 	InvokeFunctionExecuteHook(funcid);
 
+	if (IsA(node, FuncExpr) && ((FuncExpr *) node)->errorsafe)
+		context = (Node *) state->escontext;
+
 	/*
 	 * Safety check on nargs.  Under normal circumstances this should never
 	 * fail, as parser should check sooner.  But possibly it might fail if
@@ -2769,7 +2786,8 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 
+							 context, NULL);
 
 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4770,6 +4788,58 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
index 43116431edf..79ebe0254fd 100644
@@ -570,6 +570,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
@@ -1928,6 +1929,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
@@ -3646,6 +3669,12 @@ ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 
 /*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 650f1d42a93..233bd2ae27b 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,62 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
+			case EEOP_SAFETYPE_CAST:
+				{
+					SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+					LLVMBasicBlockRef b_noerror;
+					LLVMBasicBlockRef b_error;
+					LLVMValueRef v_error_occurred_p;
+					LLVMValueRef v_details_wanted_p;
+					LLVMValueRef v_error_occurred;
+
+					b_noerror = l_bb_before_v(opblocks[opno + 1],
+											  "op.%d.noerror", opno);
+					b_error = l_bb_before_v(opblocks[opno + 1],
+											"op.%d.error", opno);
+
+					/* Get pointer to error_occurred field */
+					v_error_occurred_p = l_ptr_const(&stcstate->escontext.error_occurred,
+													 l_ptr(TypeStorageBool));
+
+					/* Load error_occurred at runtime */
+					v_error_occurred = l_load(b, TypeStorageBool, v_error_occurred_p, "");
+
+					/* Branch based on error_occurred: no error -> jump_end */
+					LLVMBuildCondBr(b,
+									LLVMBuildICmp(b, LLVMIntEQ, v_error_occurred,
+												  l_sbool_const(0), ""),
+									b_noerror,
+									b_error);
+
+					/* No error: jump to end */
+					LLVMPositionBuilderAtEnd(b, b_noerror);
+					LLVMBuildBr(b, opblocks[stcstate->jump_end]);
+
+					/* Error occurred: set null, reset flags, evaluate default */
+					LLVMPositionBuilderAtEnd(b, b_error);
+
+					/* set resnull to true */
+					LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+
+					/* reset resvalue */
+					LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+					/*
+					 * Reset for next use such as for catching errors when
+					 * coercing a expression.
+					 */
+					LLVMBuildStore(b, l_sbool_const(0), v_error_occurred_p);
+
+					v_details_wanted_p = l_ptr_const(&stcstate->escontext.details_wanted,
+													 l_ptr(TypeStorageBool));
+					LLVMBuildStore(b, l_sbool_const(0), v_details_wanted_p);
+
+					LLVMBuildBr(b, opblocks[opno + 1]);
+
+					break;
+				}
+
 			case EEOP_JSONEXPR_PATH:
 				{
 					JsonExprState *jsestate = op->d.jsonexpr.jsestate;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 3cd35c5c457..5a1091d0d48 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -605,6 +605,7 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args,
 	funcexpr->funccollid = funccollid;
 	funcexpr->inputcollid = inputcollid;
 	funcexpr->args = args;
+	funcexpr->errorsafe = false;
 	funcexpr->location = -1;
 
 	return funcexpr;
index 6a850349cf7..b5e89b7f60c 100644
@@ -453,6 +456,8 @@ exprTypmod(const Node *expr)
@@ -970,6 +975,9 @@ exprCollation(const Node *expr)
@@ -1240,6 +1248,9 @@ exprSetCollation(Node *expr, Oid collation)
@@ -1558,6 +1569,9 @@ exprLocation(const Node *expr)
@@ -2333,6 +2347,18 @@ expression_tree_walker_impl(Node *node,
@@ -3368,6 +3394,19 @@ expression_tree_mutator_impl(Node *node,
@@ -4504,6 +4543,20 @@ raw_expression_tree_walker_impl(Node *node,
index 9fb266d089d..54cf3e7ef87 100644
 
 /*
  * Recursive guts of eval_const_expressions/estimate_expression_value
@@ -2861,6 +2862,7 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->funccollid = expr->funccollid;
 				newexpr->inputcollid = expr->inputcollid;
 				newexpr->args = args;
+				newexpr->errorsafe = expr->errorsafe;
 				newexpr->location = expr->location;
 				return (Node *) newexpr;
 			}
@@ -3267,6 +3269,69 @@ eval_const_expressions_mutator(Node *node,
 												  copyObject(jve->format));
 			}
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *newexpr = makeNode(SafeTypeCastExpr);
+
+				SafeTypeCastExpr *stc = castNode(SafeTypeCastExpr, node);
+
+				Node	   *source = (Node *) stc->source;
+				Node	   *defexpr = (Node *) stc->defexpr;
+				Node	   *castexpr = (Node *) stc->castexpr;
+
+				source = eval_const_expressions_mutator(source,
+														context);
+
+				defexpr = eval_const_expressions_mutator(defexpr,
+														 context);
+
+				/*
+				 * Defer constant folding for castexpr in most cases to avoid
+				 * potential premature evaluation failure. For example,
+				 * CAST('def'::text AS integer DEFAULT NULL ON CONVERSION
+				 * ERROR), the castexpr is a CoerceViaIO node that would error
+				 * out, but we do not want it to fail here.
+				 *
+				 * Folding the whole FuncExpr node maybe have premature
+				 * evaluation failure, however it is safe to constant-fold its
+				 * arguments (FuncExpr->args).
+				 */
+				if (castexpr && IsA(castexpr, FuncExpr))
+				{
+					FuncExpr   *fexpr = (FuncExpr *) castexpr;
+
+					FuncExpr   *funcexpr = makeFuncExpr(fexpr->funcid,
+														fexpr->funcresulttype,
+														NIL,
+														fexpr->funccollid,
+														fexpr->inputcollid,
+														fexpr->funcformat);
+
+					if (!fexpr->errorsafe)
+						elog(ERROR, "unexpected errorsafe value for error safe type cast function");
+
+					if (fexpr->funcvariadic)
+						elog(ERROR, "unexpected funcvariadic value for error safe type cast function");
+
+					if (fexpr->funcretset)
+						elog(ERROR, "error safe type cast function should must return a set");
+
+					funcexpr->args = (List *) eval_const_expressions_mutator((Node *) fexpr->args,
+																			 context);
+
+					castexpr = (Node *) funcexpr;
+				}
+
+				newexpr->source = (Expr *) source;
+				newexpr->castexpr = (Expr *) stc->castexpr;
+				newexpr->defexpr = (Expr *) defexpr;
+				newexpr->resulttype = stc->resulttype;
+				newexpr->resulttypmod = stc->resulttypmod;
+				newexpr->resultcollid = stc->resultcollid;
+
+				return (Node *) newexpr;
+			}
+
 		case T_SubPlan:
 		case T_AlternativeSubPlan:
 
@@ -3698,7 +3763,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);
 			}
@@ -4546,6 +4612,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 		fexpr.funccollid = result_collid;
 		fexpr.inputcollid = input_collid;
 		fexpr.args = args;
+		fexpr.errorsafe = false;
 		fexpr.location = -1;
 
 		req.type = T_SupportRequestSimplify;
@@ -5226,10 +5293,11 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	newexpr->funccollid = result_collid;	/* doesn't matter */
 	newexpr->inputcollid = input_collid;
 	newexpr->args = args;
+	newexpr->errorsafe = false;
 	newexpr->location = -1;

 /*
@@ -5338,6 +5406,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 	fexpr->funccollid = result_collid;	/* doesn't matter */
 	fexpr->inputcollid = input_collid;
 	fexpr->args = args;
+	fexpr->errorsafe = false;
 	fexpr->location = -1;
 
 	/* Fetch the function body */
@@ -5683,10 +5752,13 @@ 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.
+ *
+ * When escontext is non-NULL, safely evaluates the constant expression.
+ * Returns NULL on failure rather than throwing an error.
  */
 Expr *
 evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
-			  Oid result_collation)
+			  Oid result_collation, Node *escontext)
 {
 	EState	   *estate;
 	ExprState  *exprstate;
@@ -5711,7 +5783,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.
@@ -5731,6 +5803,13 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);
 
index 0b4a4911370..4d692274d55 100644
@@ -16656,6 +16658,12 @@ func_expr_common_subexpr:
@@ -19817,10 +19825,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 *
index 6076e9373c1..07c73521df2 100644
 
 			break;
 
@@ -992,6 +998,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
index 913ca53666f..cdd62044a60 100644
@@ -37,14 +37,16 @@ static Node *coerce_type_typmod(Node *node,
-									   int location);
@@ -81,6 +83,28 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
@@ -102,9 +126,15 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,

 	/*
 	 * If the target is a fixed-length type, it may need a length coercion as
@@ -114,7 +144,11 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
 	result = coerce_type_typmod(result,
 								targettype, targettypmod,
 								ccontext, cformat, location,
-								(result != expr && !IsA(result, Const)));
+								(result != expr && !IsA(result, Const)),
+								escontext);
+
+	if (result == NULL)			/* shouldn't happen */
+		elog(ERROR, "failed to coerce type modifier as expected");
 
@@ -158,6 +192,18 @@ Node *
@@ -256,6 +302,7 @@ coerce_type(ParseState *pstate, Node *node,
 
 		/*
@@ -309,13 +356,21 @@ coerce_type(ParseState *pstate, Node *node,
 
 		/*
 		 * If it's a varlena value, force it to be in non-expanded
@@ -364,7 +419,8 @@ coerce_type(ParseState *pstate, Node *node,
 									  baseTypeId, baseTypeMod,
 									  targetTypeId,
 									  ccontext, cformat, location,
-									  false);
+									  false,
+									  escontext);

@@ -397,9 +453,11 @@ coerce_type(ParseState *pstate, Node *node,
 		 */
 		CollateExpr *coll = (CollateExpr *) node;
 
+		result = coerce_type_extended(pstate, (Node *) coll->arg,
@@ -432,7 +490,8 @@ coerce_type(ParseState *pstate, Node *node,
 
 			/*
 			 * If domain, coerce to the domain type and relabel with domain
@@ -442,7 +501,8 @@ coerce_type(ParseState *pstate, Node *node,
 				result = coerce_to_domain(result, baseTypeId, baseTypeMod,
 										  targetTypeId,
 										  ccontext, cformat, location,
-										  true);
+										  true,
+										  escontext);
 		}
 		else
 		{
@@ -458,7 +518,9 @@ coerce_type(ParseState *pstate, Node *node,
 			result = coerce_to_domain(node, baseTypeId, baseTypeMod,
 									  targetTypeId,
 									  ccontext, cformat, location,
-									  false);
+									  false,
+									  escontext);
+
 			if (result == node)
 			{
 				/*
@@ -675,7 +737,8 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
 Node *
 coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
 				 CoercionContext ccontext, CoercionForm cformat, int location,
-				 bool hideInputCoercion)
+				 bool hideInputCoercion,
+				 Node *escontext)
 {
 	CoerceToDomain *result;
 
@@ -705,7 +768,10 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
 	 */
 	arg = coerce_type_typmod(arg, baseTypeId, baseTypeMod,
 							 ccontext, COERCE_IMPLICIT_CAST, location,
-							 false);
+							 false,
+							 escontext);
+	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
@@ -752,7 +818,8 @@ static Node *
 coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
 				   CoercionContext ccontext, CoercionForm cformat,
 				   int location,
-				   bool hideInputCoercion)
+				   bool hideInputCoercion,
+				   Node *escontext)
 {
 	CoercionPathType pathtype;
 	Oid			funcId;
@@ -779,7 +846,8 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
 	{
 		node = build_coercion_expression(node, pathtype, funcId,
 										 targetTypeId, targetTypMod,
-										 ccontext, cformat, location);
+										 ccontext, cformat, location,
+										 escontext);
 	}
 	else
 	{
@@ -840,7 +908,8 @@ build_coercion_expression(Node *node,
 						  Oid funcId,
 						  Oid targetTypeId, int32 targetTypMod,
 						  CoercionContext ccontext, CoercionForm cformat,
-						  int location)
+						  int location,
+						  Node *escontext)
 {
 	int			nargs = 0;
 
@@ -913,6 +982,9 @@ build_coercion_expression(Node *node,
 
 		fexpr = makeFuncExpr(funcId, targetTypeId, args,
 							 InvalidOid, InvalidOid, cformat);
+		if (escontext)
+			fexpr->errorsafe = true;
+
 		fexpr->location = location;
 		return (Node *) fexpr;
 	}
@@ -950,14 +1022,15 @@ build_coercion_expression(Node *node,
 		targetElementType = get_element_type(targetTypeId);
 		Assert(OidIsValid(targetElementType));
 
-		elemexpr = coerce_to_target_type(NULL,
-										 (Node *) ctest,
-										 ctest->typeId,
-										 targetElementType,
-										 targetTypMod,
-										 ccontext,
-										 cformat,
-										 location);
+		elemexpr = coerce_to_target_type_extended(NULL,
+												  (Node *) ctest,
+												  ctest->typeId,
+												  targetElementType,
+												  targetTypMod,
+												  ccontext,
+												  cformat,
+												  location,
+												  escontext);
 		if (elemexpr == NULL)	/* shouldn't happen */
 			elog(ERROR, "failed to coerce array element type as expected");
 
@@ -1140,7 +1213,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 								baseTypeId, baseTypeMod,
 								targetTypeId,
 								ccontext, cformat, location,
-								false);
+								false,
+								NULL);
 	}
 
 	return (Node *) rowexpr;
@@ -1295,7 +1369,8 @@ coerce_null_to_domain(Oid typid, int32 typmod, Oid collation,
 								  COERCION_IMPLICIT,
 								  COERCE_IMPLICIT_CAST,
 								  -1,
-								  false);
+								  false,
+								  NULL);
 	return result;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 312dfdc182a..a1e2fdc9a1f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@
 
@@ -38,6 +39,7 @@
@@ -62,7 +64,8 @@ static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
@@ -78,6 +81,11 @@ static Node *transformWholeRowRef(ParseState *pstate,
@@ -166,7 +174,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 
 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
@@ -566,6 +574,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
@@ -1840,6 +1849,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -2030,17 +2042,23 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 
 	/*
 	 * Transform the element expressions
@@ -2064,9 +2082,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  is_coercible);
 			/* we certainly have an array here */
-			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			Assert(is_coercible || array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
 		}
 		else
@@ -2107,6 +2126,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	}
 	else
 	{
+		Assert(is_coercible == NULL);
+
 		/* Can't handle an empty array without a target type */
 		if (newelems == NIL)
 			ereport(ERROR,
@@ -2144,6 +2165,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 		coerce_hard = false;
 	}
 
@@ -2153,28 +2176,79 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
 		if (coerce_hard)
 		{
@@ -2723,17 +2797,63 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,

+					errmsg("collation of CAST DEFAULT expression conflicts with target type collation"),
+							  get_collation_name(defColl),
+							  get_collation_name(targetTypecoll)),
@@ -2762,7 +2882,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
@@ -2783,20 +2904,247 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)

-	return result;
+static void
+	else if ((input_typtype != TYPTYPE_BASE && input_typtype != TYPTYPE_PSEUDO && inputType > FirstUnpinnedObjectId)
+		 * Input expression can be UNKNOWN Const, and UNKNOWN is typtype is
+		 * TYPTYPE_PSEUDO
+			if (castForm->castfunc > FirstUnpinnedObjectId)
 
 /*
@@ -3212,6 +3560,8 @@ ParseExprKindName(ParseExprKind exprKind)
 		case EXPR_KIND_CHECK_CONSTRAINT:
 		case EXPR_KIND_DOMAIN_CHECK:
 			return "CHECK";
+		case EXPR_KIND_CAST_DEFAULT:
+			return "CAST DEFAULT";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 8dbd41a3548..81a4d5c58ef 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -768,6 +768,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 		funcexpr->funcformat = funcformat;
 		/* funccollid and inputcollid will be set by parse_collate.c */
 		funcexpr->args = fargs;
+		funcexpr->errorsafe = false;
 		funcexpr->location = location;
 
 		retval = (Node *) funcexpr;
@@ -2743,6 +2744,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("set-returning functions are not allowed in check constraints");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 541fef5f183..7cd1970bc99 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -845,7 +845,8 @@ transformAssignmentIndirection(ParseState *pstate,
 										COERCION_IMPLICIT,
 										COERCE_IMPLICIT_CAST,
 										location,
-										false);
+										false,
+										NULL);
 
 			return (Node *) fstore;
 		}
 
+/*
index 9a918e14aa7..10318fc4990 100644
@@ -4951,7 +4951,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
 
+		/* 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 7bc12589e40..d3fcb41410f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -11106,6 +11106,31 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
index bfeceb7a92f..90686ae20f9 100644
@@ -1760,6 +1760,19 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)

index 2162ff56c38..d2c79b9f29e 100644
@@ -1071,6 +1071,27 @@ typedef struct DomainConstraintState
 
+	 * Address to jump to when skipping all the steps to evaluate the default
index 4e1ea9d1e8e..b45c29d52f4 100644
 
index b67e56e6c5a..3397c3d4c5c 100644
 
+/*
+ * SafeTypeCastExpr -
+ *		Transformed representation of
+ * 		CAST(expr AS typename DEFAULT expr ON CONVERSION ERROR)
+ * 		CAST(expr AS typename NULL ON CONVERSION ERROR)
+ */
+typedef struct SafeTypeCastExpr
+{
+	Expr		xpr;
+	/* transformed source expression from parser */
+	Expr	   *source;
+	/* transformed cast expression, NULL means cannot coerce to target type */
+	Expr	   *castexpr pg_node_attr(query_jumble_ignore);
+	/* 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;
+
 /*
  * FuncExpr - expression node for a function call
  *
@@ -799,6 +824,8 @@ typedef struct FuncExpr
 	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the function */
 	List	   *args;
+	/* True if the function supports soft-error evaluation */
+	bool		errorsafe;
 	/* token location, or -1 if unknown */
 	ParseLoc	location;
 } FuncExpr;
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index e8b409afb7f..b686be049b9 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);

index aabacd49b65..12644f146bf 100644
@@ -43,15 +43,29 @@ extern Node *coerce_to_target_type(ParseState *pstate,
 
index fc2cbeb2083..726c1fcd0e2 100644
 
index 00000000000..e7ed26cf1df
@@ -0,0 +1,942 @@
+SELECT CAST('def'::text AS integer DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(65536 AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  collation of CAST DEFAULT expression conflicts with target type collation
+DETAIL:  "C" versus "default"
+CREATE TABLE tcast0(a INT, b int);
+INSERT INTO tcast0 VALUES(1,2), (65536,3);
+SET jit_above_cost = 0;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0;
+SET jit_above_cost TO DEFAULT;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0;
+SELECT CAST(ROW(1,2) AS tcast0 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0 as t;
+ERROR:  cannot cast type record to tcast0 when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(ROW(1,2) AS tcast0 DEFAULT NULL ON CONVERSION ER...
+DROP TABLE tcast0;
+SELECT CAST(12.111 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale;
+ to_numeric_scale 
+------------------
+             12.1
+CREATE DOMAIN d_numeric as numeric(4,4);
+SELECT CAST(1.0::float4 AS d_numeric DEFAULT NULL ON CONVERSION ERROR);
+ d_numeric 
+SELECT CAST('{1.0}' AS numeric(4,1)[] DEFAULT NULL ON CONVERSION ERROR);
+ {1.0}
+SELECT CAST('{0.1, 1.0}' AS d_numeric[] DEFAULT NULL ON CONVERSION ERROR);
+ d_numeric 
+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 when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT ...
+-- safe cast with geometry data type
+-- safe cast from/to money type is not supported, all the following would result error
+SELECT CAST('\x1234567890abcdef'::bytea AS uuid DEFAULT NULL ON CONVERSION ERROR);
+ uuid 
+-- 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
+-- safe cast from text type to other data types
+-- safe cast betweeen range data types
+DROP VIEW safecastview;
+DROP VIEW safecastview1;
+DROP DOMAIN d_numeric;
 
index 734da057c34..c82b4b31423 100644
 
 # ----------
index 00000000000..03533b43cb9
@@ -0,0 +1,392 @@
+SELECT CAST('def'::text AS integer DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(65536 AS int2 DEFAULT NULL ON CONVERSION ERROR);
+CREATE TABLE tcast0(a INT, b int);
+INSERT INTO tcast0 VALUES(1,2), (65536,3);
+SET jit_above_cost = 0;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0;
+SET jit_above_cost TO DEFAULT;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0;
+SELECT CAST(ROW(1,2) AS tcast0 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0 as t;
+DROP TABLE tcast0;
+SELECT CAST(12.111 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale;
+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(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); -- error
+-- safe cast with geometry data type
+-- safe cast from/to money type is not supported, all the following would result 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 from text type to other data types
+-- safe cast betweeen range data types
+DROP VIEW safecastview;
+DROP VIEW safecastview1;
+DROP DOMAIN d_numeric;

 
index a4a2ed07816..bf121561847 100644
@@ -2730,6 +2730,8 @@ STRLEN

From a05b33cdb49d7b03980594729491527e452c747b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:14:07 +0800
Subject: [PATCH v22 17/23] error safe for casting jsonb to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
 1 file changed, 58 insertions(+), 16 deletions(-)

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 1b1a8f301f2..39ae59e9772 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1786,7 +1786,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
  * Emit correct, translatable cast error message
  */
 static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
 {
 	static const struct
 	{
@@ -1807,7 +1807,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)

@@ -1822,7 +1822,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	JsonbValue	v;

 	if (v.type == jbvNull)
 	{
@@ -1831,7 +1834,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);
 
@@ -1846,7 +1852,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	Numeric		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1855,7 +1864,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	}

 	/*
 	 * v.val.numeric points into jsonb body, so we need to make a copy to
@@ -1876,7 +1888,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1885,7 +1900,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int2,
 								   NumericGetDatum(v.val.numeric));
@@ -1903,7 +1921,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1912,7 +1933,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int4,
 								   NumericGetDatum(v.val.numeric));
@@ -1930,7 +1954,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1939,7 +1966,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int8,
 								   NumericGetDatum(v.val.numeric));
@@ -1957,7 +1987,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1966,7 +1999,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_float4,
 								   NumericGetDatum(v.val.numeric));
@@ -1984,7 +2020,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1993,7 +2032,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	}

From cecfb37055d94fd1de53ecff5fb4d6fbf89e821f Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:12:29 +0800
Subject: [PATCH v22 15/23] error safe for casting timestamptz to other types
 per pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/date.c      |  9 ++++++---
 src/backend/utils/adt/timestamp.c | 10 ++++++++--
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 487214b85f6..e052e5b2e85 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1398,7 +1398,10 @@ timestamptz_date(PG_FUNCTION_ARGS)
 	TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
 	DateADT		result;

@@ -2028,7 +2031,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();

@@ -2947,7 +2950,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3b09c2f9849..deabf85955c 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -866,7 +866,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
 
 	result = timestamp;

 	PG_RETURN_TIMESTAMPTZ(result);
 }
@@ -6485,8 +6486,13 @@ Datum
 timestamptz_timestamp(PG_FUNCTION_ARGS)
 {
 	TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	result;

 /*
-- 
2.34.1

From 1ff8f131bf6dcfccf4d1b7cb5fcbef4c068ebcf6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:10:12 +0800
Subject: [PATCH v22 13/23] error safe for casting date to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/date.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 87c063fa0cd..3c05abd2c14 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -722,15 +722,6 @@ date2timestamptz_safe(DateADT dateVal, Node *escontext)
 	return result;
 }
 
-/*
@@ -1315,7 +1306,9 @@ date_timestamp(PG_FUNCTION_ARGS)

 	PG_RETURN_TIMESTAMP(result);
 }
@@ -1388,7 +1381,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
 	TimestampTz result;

From f103d5efc467c2a2b2db315305a587c09673509c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:09:17 +0800
Subject: [PATCH v22 12/23] error safe for casting float8 to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/float.c   | 13 +++++++++----
 src/backend/utils/adt/int8.c    |  2 +-
 src/backend/utils/adt/numeric.c |  3 ++-
 3 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 1b4006fd50a..4423b724f9f 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1216,9 +1216,14 @@ dtof(PG_FUNCTION_ARGS)
 
 	result = (float4) num;
 
 	PG_RETURN_FLOAT4(result);
 }
@@ -1241,7 +1246,7 @@ dtoi4(PG_FUNCTION_ARGS)
 
 	/* Range check */
 
@@ -1266,7 +1271,7 @@ dtoi2(PG_FUNCTION_ARGS)
 
 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From 11bde93de24c662cca3af6a0b745c5f957e16b95 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:11:13 +0800
Subject: [PATCH v22 14/23] error safe for casting interval to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/date.c      | 2 +-
 src/backend/utils/adt/timestamp.c | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 3c05abd2c14..487214b85f6 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2098,7 +2098,7 @@ interval_time(PG_FUNCTION_ARGS)
 	TimeADT		result;

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 50a73fd24ec..3b09c2f9849 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1325,7 +1325,8 @@ interval_scale(PG_FUNCTION_ARGS)
 	result = palloc_object(Interval);
 	*result = *interval;

From b4c409b329e06331bf3b877d163a9a1efe9069f6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:06:45 +0800
Subject: [PATCH v22 11/23] error safe for casting float4 to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/float.c   | 4 ++--
 src/backend/utils/adt/int8.c    | 2 +-
 src/backend/utils/adt/numeric.c | 3 ++-
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index f77f73820d7..1b4006fd50a 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1315,7 +1315,7 @@ ftoi4(PG_FUNCTION_ARGS)
 
 	/* Range check */
 
@@ -1340,7 +1340,7 @@ ftoi2(PG_FUNCTION_ARGS)
 
 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From 142716a7b213aa0c5b3f3e9d08af34181fb1c088 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:03:58 +0800
Subject: [PATCH v22 09/23] error safe for casting bigint to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/int8.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

 

 
 
-- 
2.34.1

From c0b9c8a1f4f2f53319d090859d0e8841db710089 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:57:01 +0800
Subject: [PATCH v22 06/23] error safe for casting inet to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/network.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

 
-- 
2.34.1

From 0f67fb59fad1570f447b6aee9236c88e9d771e01 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:57:49 +0800
Subject: [PATCH v22 07/23] error safe for casting macaddr8 to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/mac8.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

From 1b4c5dbc875826a468745f6e91fade5d673fe4fb Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:59:24 +0800
Subject: [PATCH v22 08/23] error safe for casting integer to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/char.c | 2 +-
 src/backend/utils/adt/int.c  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

 

-- 
2.34.1

From bbc08370f2c37bcaa6ab7de8e8d1d2717f7a0dd6 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:34:51 +0800
Subject: [PATCH v22 05/23] error safe for casting character varying to other
 types per pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/varchar.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

From d7f880dc94d5b2d2e080b2be63033a2565cac60a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:55:58 +0800
Subject: [PATCH v22 04/23] error safe for casting text to other types per
 pg_cast

select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp
on pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;

Already error safe: text_name, text_char, texttoxml.

Author: jian he <[email-masked]>
Reviewed-by: Andrew Dunstan <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/catalog/namespace.c | 66 +++++++++++++++++++++++----------
 src/backend/utils/adt/regproc.c | 13 +++++--
 src/backend/utils/adt/varlena.c | 10 ++++-
 src/include/catalog/namespace.h |  6 +++
 src/include/utils/varlena.h     |  1 +
 5 files changed, 72 insertions(+), 24 deletions(-)

index 56b87d878e8..061089f04c7 100644
@@ -595,16 +605,22 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 
-			if (relation->schemaname)
+			if (escontext == NULL)
 				ereport(elevel,
-						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-						 errmsg("could not obtain lock on relation \"%s.%s\"",
-								relation->schemaname, relation->relname)));
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						relation->schemaname ?
+						errmsg("could not obtain lock on relation \"%s.%s\"",
+							   relation->schemaname, relation->relname) :
+						errmsg("could not obtain lock on relation \"%s\"",
+							   relation->relname));
 			else
-				ereport(elevel,
-						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-						 errmsg("could not obtain lock on relation \"%s\"",
-								relation->relname)));
+				ereturn(escontext, InvalidOid,
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						relation->schemaname ?
+						errmsg("could not obtain lock on relation \"%s.%s\"",
+							   relation->schemaname, relation->relname) :
+						errmsg("could not obtain lock on relation \"%s\"",
+							   relation->relname));
 
 			return InvalidOid;
 		}
@@ -628,16 +644,22 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	{
 		int			elevel = missing_ok ? DEBUG1 : ERROR;
 
-		if (relation->schemaname)
+		if (escontext == NULL)
 			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_TABLE),
-					 errmsg("relation \"%s.%s\" does not exist",
-							relation->schemaname, relation->relname)));
+					errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					relation->schemaname ?
+					errmsg("relation \"%s.%s\" does not exist",
+						   relation->schemaname, relation->relname) :
+					errmsg("relation \"%s\" does not exist",
+						   relation->relname));
 		else
-			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_TABLE),
-					 errmsg("relation \"%s\" does not exist",
-							relation->relname)));
+			ereturn(escontext, InvalidOid,
+					errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					relation->schemaname ?
+					errmsg("relation \"%s.%s\" does not exist",
+						   relation->schemaname, relation->relname) :
+					errmsg("relation \"%s\" does not exist",
+						   relation->relname));
 	}
 	return relId;
 }
@@ -3624,6 +3646,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
  */
 RangeVar *
 makeRangeVarFromNameList(const List *names)
+{
+	return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
 {
 	RangeVar   *rel = makeRangeVar(NULL, NULL, -1);
 
@@ -3642,7 +3670,7 @@ makeRangeVarFromNameList(const List *names)

 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 7b1ee61bde6..56da3d7b440 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2717,6 +2717,12 @@ name_text(PG_FUNCTION_ARGS)
  */
 List *
 textToQualifiedNameList(text *textval)
+{
+	return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
 {
 	char	   *rawname;
 	List	   *result = NIL;
@@ -2728,12 +2734,12 @@ textToQualifiedNameList(text *textval)
 	rawname = text_to_cstring(textval);

 	if (namelist == NIL)
-		ereport(ERROR,

From e684bc23806c99ff5838e1fa40375ddf20bb822a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:53:29 +0800
Subject: [PATCH v22 03/23] error safe for casting character to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/executor/execExprInterp.c |  2 +-
 src/backend/utils/adt/varchar.c       |  2 +-
 src/backend/utils/adt/xml.c           | 18 ++++++++++++------
 src/include/utils/xml.h               |  2 +-
 4 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 53ae0205702..43116431edf 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4548,7 +4548,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;

From a5eb0e24ea234daefa4004aec0aef5d010606bd8 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:40:25 +0800
Subject: [PATCH v22 02/23] error safe for casting bit/varbit to other types
 per pg_cast

select castsource::regtype, casttarget::regtype, castfunc,
       castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype
or castsource::regtype ='varbit'::regtype)
order by castsource::regtype;

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/varbit.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

 	if (!isExplicit)
-		ereport(ERROR,
 
 	if (!isExplicit)
-		ereport(ERROR,

 
 
-- 
2.34.1

---

From: jian he <[email-masked]>
Date: Sat, 21 Mar 2026 16:16:07 +0800

> (Not doing anything for SafeTypeCastExpr->castexpr is easier).

Just found out that eval_const_expressions_mutator should either
return a Const or recurse to every node field.
If not, the node field may have a COLLATE expression, and
ExecInitExprRec cannot cope with T_CollateExpr. For example:

SELECT CAST('1' collate "C" || 'h' AS text DEFAULT 'fallback' ON
CONVERSION ERROR);
ERROR:  unrecognized node type: 32

Therefore in eval_const_expressions, we cannot skip constant folding
SafeTypeCastExpr->castexpr.
Since eval_const_expressions_mutator is not error-safe, an
ErrorSaveContext pointer was added to eval_const_expressions_context.
It's initialized to NULL, and it will be passed as
``makeNode(ErrorSaveContext)`` inside T_SafeTypeCastExpr.

Previously in [0], I mentioned that SafeTypeCastExpr->castexpr can be FuncExpr,
CollateExpr, CoerceToDomain, ArrayCoerceExpr, or CoerceViaIO or others.

Constant folding in eval_const_expressions for these nodes are
generally processed
through the simplify_function -> evaluate_function -> evaluate_expr.
evaluate_expr can process it in an error-safe manner because the escontext
(ErrorSaveContext) within eval_const_expressions_context is properly populated.

SafeTypeCastExpr->castexpr can also be any Node type if the parse transformed
source expression is the same type as the target type. See
transformTypeCast->coerce_to_target_type_extended->coerce_type_extended:
```
    if (targetTypeId == inputTypeId ||
        node == NULL)
    {
        /* no conversion needed */
        return node;
    }
```
However, if SafeTypeCastExpr->castexpr is the same as
SafeTypeCastExpr->source then
eval_const_expressions premature failure at "source" should be not a
big problem for us.

Since castexpr is evaluated safely in most cases during eval_const_expressions,
I am not too worried about a premature failure occurring with CAST ... DEFAULT."

[0]: https://postgr.es/m/CACJufxHw9Y3fvh+rZj4ukLo=[email-masked]

----------------------------------------------------------------
SELECT CAST(1 as date DEFAULT NULL ON CONVERSION ERROR) as a;

transformTypeCast->coerce_to_target_type->coerce_to_target_type_extended
will make SafeTypeCastExpr->castexpr as NULL.
To deparse the above, we need to save the transformed TypeCast->arg.
Therefore, SafeTypeCastExpr->source is necessary.

--
jian
https://www.enterprisedb.com/
From 41343726e158512f8e56b5a126064b6aacab7450 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:16:54 +0800
Subject: [PATCH v23 19/23] introduce float8 safe function

 
+static inline float8
 
 static inline float4
 
+static inline float8
 
 static inline float4
 
 static inline float8
 
 	result = val1 * val2;
 
 	return result;
 }
 
+static inline float8
 
 static inline float8

 	return result;
 }
 
+static inline float8

From bbdf831d123fe8b3243a261d21d34d3b8c5bc83f Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:55:58 +0800
Subject: [PATCH v23 04/23] error safe for casting text to other types per
 pg_cast

 
 
 			return InvalidOid;
 		}

 

 
 
 	if (namelist == NIL)
-		ereport(ERROR,

From c528bfd294609f14c51c5707993e1793f7e48745 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:14:07 +0800
Subject: [PATCH v23 17/23] error safe for casting jsonb to other types per
 pg_cast

 

 
 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From 053f7a1e22abc0dd99445890d78d0f49315d7f60 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:05:57 +0800
Subject: [PATCH v23 10/23] error safe for casting numeric to other types per
 pg_cast

 

 	free_var(&var);

 
 	PG_RETURN_NUMERIC(res);
 }

 /*

 

 
 
 	PG_RETURN_DATUM(result);
 }

 	pfree(tmp);
 
-- 
2.34.1

From 3a5958c615d29c0b0b3036cbf627ec4a6fab89ec Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:03:58 +0800
Subject: [PATCH v23 09/23] error safe for casting bigint to other types per
 pg_cast

 

 

-- 
2.34.1

From 213b2ed5b650ac2e173987d82d55f41783d92753 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:59:24 +0800
Subject: [PATCH v23 08/23] error safe for casting integer to other types per
 pg_cast

 

 
-- 
2.34.1

From c10ad2875cd53709bf26c2ba64b9858e1ae0038c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 14:12:01 +0800
Subject: [PATCH v23 01/23] error safe for casting bytea to other types per
 pg_cast

 

 

 
 	if (len != UUID_LEN)
-		ereport(ERROR,

From 6ac4b4fbef1a075246a474185bddc78cfa6fba7f Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:34:51 +0800
Subject: [PATCH v23 05/23] error safe for casting character varying to other
 types per pg_cast

From cabaa856a2e34882f056461d3a0e1c313ae6feab Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:53:29 +0800
Subject: [PATCH v23 03/23] error safe for casting character to other types per
 pg_cast

 

 xmltype *

 	return (xmltype *) data;
 #else
-	NO_XML_SUPPORT();
-	return NULL;

From 2e9a33cc6b39d994a00aaf2439f8dcf1d09a398a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:40:25 +0800
Subject: [PATCH v23 02/23] error safe for casting bit/varbit to other types
 per pg_cast

 
 	if (!isExplicit)
-		ereport(ERROR,
 
 	if (!isExplicit)
-		ereport(ERROR,

 
 
-- 
2.34.1

From 46e838992b019402339b338495e3f50588aa047b Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 14:14:31 +0800
Subject: [PATCH v23 23/23] error safe for user defined CREATE CAST

 

 

 

 
 
 -- Explicit casts.

 

 

 

 

 

 		case 1:
 
 		case 2:
 
 		default:
-			ereport(ERROR,

 

+    <varlistentry>

     <para>

 
+		if (stmt->safe)
+		{

 
index 2ac04565afc..314ab091f02 100644
 
@@ -9411,14 +9411,15 @@ dostmt_opt_item:

 					n->sourcetype = $4;
 					n->targettype = $6;
-					n->func = $10;
-					n->context = (CoercionContext) $11;
+					n->safe = $9;
+					n->func = $11;
+					n->context = (CoercionContext) $12;
 					n->inout = false;
 					$$ = (Node *) n;
 				}
@@ -9429,6 +9430,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = false;
@@ -9441,6 +9443,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = true;
@@ -9453,6 +9456,9 @@ cast_context:  AS IMPLICIT_P					{ $$ = COERCION_IMPLICIT; }
 		| /*EMPTY*/								{ $$ = COERCION_EXPLICIT; }
 		;

@@ -18987,6 +18993,7 @@ unreserved_keyword:
@@ -19636,6 +19643,7 @@ bare_label_keyword:
index 06cf1b770d2..bbc3bc4a97d 100644
@@ -2942,7 +2941,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 
 	/*
 	 * Binary coercion cast is error-safe, CoerceViaIO can also be evaluated
@@ -2954,8 +2952,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 		return;

 	if (!errorsafe_coercion)
 		ereport(ERROR,
@@ -2965,15 +2962,13 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 
 static void
 
@@ -2984,7 +2979,6 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 	check_stack_depth();

 	if (!(*errorsafe_coercion))
 		return;
@@ -2994,24 +2988,21 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 	{
 		CoercionErrorSafe_Internal(getBaseType(inputType),
 								   getBaseType(targetType),
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if (input_typtype == TYPTYPE_DOMAIN)
 	{
 		CoercionErrorSafe_Internal(getBaseType(inputType),
 								   targetType,
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if (target_typtype == TYPTYPE_DOMAIN)
 	{
 		CoercionErrorSafe_Internal(inputType,
 								   getBaseType(targetType),
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if ((input_typtype != TYPTYPE_BASE && input_typtype != TYPTYPE_PSEUDO && inputType > FirstUnpinnedObjectId)
@@ -3045,8 +3036,7 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 			/* recurse into array element type */
 			CoercionErrorSafe_Internal(input_typelem,
 									   target_typelem,
-									   errorsafe_coercion,
-									   userdefined);
+									   errorsafe_coercion);
 			return;
 		}
 	}
@@ -3078,15 +3068,13 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 		{
 			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);

 

 
@@ -316,63 +316,63 @@

 

 # Datetime category
 
 # Geometric category
 
 # MAC address category
 
 # INET category
 
 # BitString category

 

 
 # json to/from jsonb

 # range to multirange
 
 	/* cast method */
 	char		castmethod;
+
 
 END_CATALOG_STRUCT
 
 #endif							/* PG_CAST_H */
index ba27e0a714b..9ed6e29af03 100644

 
 
 SELECT c.*

 SELECT c.*

From 259f9b9d65fb7a53277bd2895cc535fc8625cb3a Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:19:10 +0800
Subject: [PATCH v23 21/23] error safe for casting geometry data type

 
 
 /* Routines for boxes */

-	box_cn(&a, box1);
-	box_cn(&b, box2);

-	box_cn(result, box);

 
-

 

 	if (result != NULL)
 	{
-		box_cn(&point, box);

 static inline void
 
 Datum

 

 
 	PG_RETURN_BOX_P(result);
 }

 	PG_RETURN_PATH_P(path);
 }

 

 

 

 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()

 

 
 	Assert(poly->npts > 0);

 
 
 Datum

 

From 11fb99aacc7d0c12eaa630afddf53f74402f9a76 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:18:01 +0800
Subject: [PATCH v23 20/23] refactor point_dt

 

 

 
 static inline float8
 
 Datum

 
 
 Datum

 Datum

 Datum

 
 
 Datum

 Datum
 
 Datum

 

 

 

 

 

 
-- 
2.34.1

From 5faef62b9e07bc5571b2e51a3dde6739b560320e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:15:46 +0800
Subject: [PATCH v23 18/23] refactor
 float_overflow_error,float_underflow_error,float_zero_divide_error

 
 	r = a - b;

 	r = arg1 - arg2;

 	r = a - b;

 pg_noinline void
 
 pg_noinline void
 
 	result = sqrt(arg1);

 	result = cbrt(arg1);

 
 
 	result = log(arg1);

 	result = log10(arg1);

 	result = acos(arg1);

 	result = asin(arg1);

 

 

 

 

 

 

 

 

 
 
 	/*
 
 			Sxx = get_float8_nan();
 		}
 
 			Sxx = get_float8_nan();
 		}
 
 			if (isinf(Sxx))
 				Sxx = get_float8_nan();
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 + val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 - val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }
 
 	result = val1 * val2;
 
 	return result;
 }

 	return result;
 }

 	return result;
 }
-- 
2.34.1

From 59075df87d49ef3258dc81f116a60bd867892f31 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 21 Mar 2026 13:19:21 +0800
Subject: [PATCH v23 22/23] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Example:

 src/backend/executor/execExpr.c               |  66 +-
 src/backend/jit/llvm/llvmjit_expr.c           |  56 +
 src/backend/optimizer/util/clauses.c          |  80 +-
 src/backend/parser/parse_expr.c               | 361 ++++++-
 src/include/nodes/primnodes.h                 |  38 +
 src/test/regress/expected/cast.out            | 971 ++++++++++++++++++
 src/test/regress/sql/cast.sql                 | 397 +++++++
 40 files changed, 2362 insertions(+), 88 deletions(-)

 

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
 
    <para>
 
index 77229141b38..c251179454a 100644

 

 
 
 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4770,6 +4788,52 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+		 * Evaluate the DEFAULT expression cast. state->escontext must be
+		 * NULL, as this evaluation should not be error-safe.

 /*

 	return funcexpr;
index 9fb266d089d..940bd155776 100644
@@ -69,6 +69,7 @@ typedef struct
 	List	   *active_fns;
 	Node	   *case_val;
 	bool		estimate;
 } eval_const_expressions_context;
 
 typedef struct
@@ -2507,6 +2508,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);
 }
 
@@ -2646,6 +2648,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);
 }
 
@@ -2671,11 +2674,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)), \
+							escontext))
 
 /*
  * Recursive guts of eval_const_expressions/estimate_expression_value
@@ -2861,6 +2865,7 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->funccollid = expr->funccollid;
 				newexpr->inputcollid = expr->inputcollid;
 				newexpr->args = args;
+				newexpr->errorsafe = expr->errorsafe;
 				newexpr->location = expr->location;
 				return (Node *) newexpr;
 			}
@@ -3136,7 +3141,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, (Node *) context->escontext);
 
 				return (Node *) expr;
 			}
@@ -3156,7 +3161,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, (Node *) context->escontext);
 				return (Node *) saop;
 			}
 		case T_BoolExpr:
@@ -3267,6 +3272,42 @@ eval_const_expressions_mutator(Node *node,
 												  copyObject(jve->format));
 			}
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stc = castNode(SafeTypeCastExpr, node);
+				SafeTypeCastExpr *newexpr = makeNode(SafeTypeCastExpr);
+
+				Node	   *castexpr = (Node *) stc->castexpr;
+				Node	   *defexpr = (Node *) stc->defexpr;
+
+				/*
+				 * No need to fold "source" to a constant. The executor does
+				 * not use it, see ExecInitSafeTypeCastExpr). Additionally,
+				 * castexpr expression tree may already contain the "source"
+				 * node.
+				 */
+				context->escontext = makeNode(ErrorSaveContext);
+				context->escontext->type = T_ErrorSaveContext;
+				context->escontext->error_occurred = false;
+
+				castexpr = eval_const_expressions_mutator(castexpr,
+														  context);
+				pfree(context->escontext);
+				context->escontext = NULL;
+
+				defexpr = eval_const_expressions_mutator(defexpr,
+														 context);
+
+				newexpr->source = (Expr *) stc->source;
+				newexpr->castexpr = (Expr *) castexpr;
+				newexpr->defexpr = (Expr *) defexpr;
+				newexpr->resulttype = stc->resulttype;
+				newexpr->resulttypmod = stc->resulttypmod;
+				newexpr->resultcollid = stc->resultcollid;
+
+				return (Node *) newexpr;
+			}
+
 		case T_SubPlan:
 		case T_AlternativeSubPlan:
 
@@ -3419,7 +3460,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, (Node *) context->escontext);
 
 				return (Node *) ac;
 			}
@@ -3615,7 +3656,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, (Node *) context->escontext);
 				return node;
 			}
 		case T_CoalesceExpr:
@@ -3698,7 +3739,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);
 			}
@@ -3795,7 +3837,7 @@ eval_const_expressions_mutator(Node *node,
 											  newfselect->resulttype,
 											  newfselect->resulttypmod,
 											  newfselect->resultcollid))
-						return ece_evaluate_expr(newfselect);
+						return ece_evaluate_expr(newfselect, (Node *) context->escontext);
 				}
 				return (Node *) newfselect;
 			}
@@ -4125,7 +4167,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, (Node *) context->escontext);
 				return (Node *) newcre;
 			}
 		default:
@@ -4546,6 +4588,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 		fexpr.funccollid = result_collid;
 		fexpr.inputcollid = input_collid;
 		fexpr.args = args;
+		fexpr.errorsafe = false;
 		fexpr.location = -1;
 
 		req.type = T_SupportRequestSimplify;
@@ -5226,10 +5269,11 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	newexpr->funccollid = result_collid;	/* doesn't matter */
 	newexpr->inputcollid = input_collid;
 	newexpr->args = args;
+	newexpr->errorsafe = context->escontext ? true : false;
 	newexpr->location = -1;
 
 	return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
-						 result_collid);
+						 result_collid, (Node *) context->escontext);
 }
 
 /*
@@ -5338,6 +5382,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 	fexpr->funccollid = result_collid;	/* doesn't matter */
 	fexpr->inputcollid = input_collid;
 	fexpr->args = args;
+	/* Cannot evaluate SQL language functions in an error-safe manner. */
+	fexpr->errorsafe = false;
 	fexpr->location = -1;
 
@@ -5683,10 +5729,13 @@ sql_inline_error_callback(void *arg)
@@ -5711,7 +5760,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 
 	/*
 	 * And evaluate it.
@@ -5731,6 +5780,13 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);
 
index 0fea726cdd5..2ac04565afc 100644
@@ -16687,6 +16689,12 @@ func_expr_common_subexpr:
@@ -19848,10 +19856,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 *
 
 			break;

 
 	/*

 		/*
 
 		/*

 
 
 			/*

 	/*

 

 	return (Node *) rowexpr;
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 312dfdc182a..06cf1b770d2 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@

 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
@@ -2030,10 +2042,14 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -2041,6 +2057,7 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
 	/*
@@ -2064,9 +2081,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
@@ -2107,6 +2125,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
@@ -2153,28 +2173,58 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
 		if (coerce_hard)
 		{
@@ -2723,17 +2773,63 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,

@@ -2762,7 +2858,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
@@ -2783,20 +2880,214 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)

-	return result;
+static void
 
 /*
@@ -3212,6 +3503,8 @@ ParseExprKindName(ParseExprKind exprKind)

 			return (Node *) fstore;
 		}
 
+/*
 
index 7bc12589e40..748586ac67c 100644
 
+				 * We cannot deparse castexpr directly, since
+				 * transformTypeCast may have already constant-folded it into
+				 * a Const node. Instead, we can use "source" and "defexpr" to
+				 * reconstruct the CAST DEFAULT clause.

 
index df431220ac5..ba27e0a714b 100644
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index b67e56e6c5a..2a8952a50c9 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -770,6 +770,42 @@ 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)
+ * 		CAST(expr AS typename NULL ON CONVERSION ERROR)
+ */
+typedef struct SafeTypeCastExpr
+{
+	Expr		xpr;
+
+	/*
+	 * Transformed source expression from the parser. Constant folding
+	 * (eval_const_expressions) is skipped because ExecInitSafeTypeCastExpr
+	 * does not use this node. Therefore, it may contain a CollateExpr node,
+	 * which ExecInitExprRec cannot handle. However, this is still required
+	 * for deparsing purposes.
+	 *
+	 * For example, "CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR)", the
+	 * castexpr is now NULL. To deparse it, we need this transformed "source"
+	 * expression.
+	 */
+	Expr	   *source;
+	/* transformed cast expression, NULL means cannot coerce to target type */
+	Expr	   *castexpr pg_node_attr(query_jumble_ignore);
+	/* 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;
+
 /*
  * FuncExpr - expression node for a function call
  *
@@ -799,6 +835,8 @@ typedef struct FuncExpr
 	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the function */
 	List	   *args;
+	/* True if the function supports soft-error evaluation */
+	bool		errorsafe;
 	/* token location, or -1 if unknown */
 	ParseLoc	location;
 } FuncExpr;
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index e8b409afb7f..b686be049b9 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);

 
 
index 00000000000..652e5fbc3bb
@@ -0,0 +1,971 @@
+SELECT CAST('2'::jsonb AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+ text 
+SELECT CAST('1' COLLATE "C" || 'h' AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+ text 
+ 1h
+SELECT CAST(concat('1' collate "C", 'h') AS int DEFAULT NULL ON CONVERSION ERROR);
+ concat 
+SELECT CAST(int8(65536) AS int2 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+-- error: ``three'::int`` will fail earlier
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);

 # ----------
index 00000000000..d992b369ea2
@@ -0,0 +1,397 @@
+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(int8(65536) AS int2 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+-- error: ``three'::int`` will fail earlier
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);

 
index 0042c33fa66..9cea384f2d3 100644
@@ -2734,6 +2734,8 @@ STRLEN

From a8a83c32451059babbb45890c1b3faab0d5835a5 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:13:17 +0800
Subject: [PATCH v23 16/23] error safe for casting timestamp to other types per
 pg_cast

 

 
 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From beba0dcbb40468751777c22b6d1766b0efa2f4db Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:10:12 +0800
Subject: [PATCH v23 13/23] error safe for casting date to other types per
 pg_cast

 
-/*

 

From f8d02ea1983657c06c74a9cb4e71166052414579 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:12:29 +0800
Subject: [PATCH v23 15/23] error safe for casting timestamptz to other types
 per pg_cast

 

 

 
 	result = timestamp;

 
 
 /*
-- 
2.34.1

From 2c06a8e0e739f419492e42edabd1085f74e80e5c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:09:17 +0800
Subject: [PATCH v23 12/23] error safe for casting float8 to other types per
 pg_cast

 
 	result = (float4) num;

 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From f2cb95fe45049b169c6d15ba9bb4b7b9de65c600 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:11:13 +0800
Subject: [PATCH v23 14/23] error safe for casting interval to other types per
 pg_cast

 

 

From 4e836a704595e8d26060377c951dd80488e40540 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:06:45 +0800
Subject: [PATCH v23 11/23] error safe for casting float4 to other types per
 pg_cast

 
 	/* Range check */

 	/* Range check */

 	/* Range check */

 
 
-- 
2.34.1

From 61705e341e91605550703cae7f8f8f770a03bbb4 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:57:49 +0800
Subject: [PATCH v23 07/23] error safe for casting macaddr8 to other types per
 pg_cast

 

From 7bdcd6c06860305c5e381c4f723be7ac23c964ef Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:57:01 +0800
Subject: [PATCH v23 06/23] error safe for casting inet to other types per
 pg_cast

 
 
-- 
2.34.1

---

From: Corey Huinker <[email-masked]>
Date: Tue, 24 Mar 2026 14:57:27 -0400

>
> In the comment for patch 0021 you write that the function casting type
> circle to type polygon cannot be error safe, because it's a SQL
> language function.  I suggest to convert this to a C function and make
> the required changes there.
>

> About the main feature patch: I'm not a fan of the CREATE CAST
> ... WITH SAFE FUNCTION syntax.  First, the CREATE CAST syntax is part
> of the SQL standard.  It would be weird if we needed a nonstandard
> syntax to make two standard features work together.  Second, we didn't
> do this when we introduced error-safe type input functions.  There is
> a note on the CREATE TYPE man page that use of ereturn() is
> encouraged, and then we left it to extension authors to do the right
> thing.  (And we should now put a similar note on the CREATE CAST man
> page.)  And third, requiring this would require a lot of churn in all
> affected extensions, requiring new extension SQL files and forcing
> upgrades.  The changes you did in patch 0023 don't do this correctly,
> for example.

This leaves us in a tricky situation if we can't tell ahead of time if a
cast function really can handle soft errors. We could just allow any and
all (CAST ... ON ERROR) calls even if the casting function can't actually
return soft errors. That's unfortunate, but it might be the simplest and
best way forward given that the alternative is to raise an error at planner
time guaranteeing a query failure when the values presented might not have
triggered the conversion errors.

> Tactical suggestion: Add an SQL-callable function, say,
> pg_cast_conversion_succeeds(srcvalue, desttype) that checks whether
> the cast would succeed.  This would be similar to the
> pg_input_is_valid() function that we added to test the type input
> functions.  (I did not call my proposal pg_cast_is_valid() because
> that might indicate merely that a casting path exists.)  With that,
> the higher-level functionality can be constructed by hand (CASE WHEN
> pg_cast_conversion_succeeds(...) THEN CAST(...) ELSE 'default value'
> END).  And then we can later work on building out the higher-level
> functionality and make more cast functions error safe.

You're describing the the "x IS CASTABLE AS y" syntax proposed in the same
standards proposal document :)

---

From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 21:13:49 +0800

On Tue, Mar 24, 2026 at 9:13 PM Peter Eisentraut <[email-masked]> wrote:
>
> In patch 0012, I did not apply the changes involving
> float_*flow_error() in dtof().  These should be considered together
> with the error handling changes in patch 0018 (about which below).
>
> Patch 0004 is probably ok, I just need to study a bit more, since it's
> a bit different from the other ones.
>
> In the comment for patch 0008 you write that supporting soft errors in
> int4_cash() is not easy.  I suppose this is because of the external
> function call to int8mul()?  Maybe that was necessary in ancient
> times, but int8mul() is nowadays just a fmgr wrapper around
> pg_mul_s64_overflow(), which you can call directly in int4_cash(), and
> then it should be easy.
>

cash_numeric is not easy to refactor safely,
To do this, we have to refactor (numeric_round, numeric_div), which
will require refactoring more functions.
So CoercionErrorSafe_Internal still checks whether the source
element/base type is MONEYOID.

> About patch 0017 (jsonb): Calling ereturn() in a function that returns
> void, well, you make that work, but it seems a bit weird.  Maybe
> cannotCastJsonbValue() could be changed to return a dummy datum, and
> then you could just call it like
>
>      return cannotCastJsonbValue(...);
>
ok.

> Similarly, about patch 0018 (refactor float error): I think this is a
> fragile API.  float_overflow_error() now will either ereport or just
> errsave.  But nothing enforces that callers do the right thing.  For
> example, in patch 0019, you change float_zero_divide_error(NULL) to
> float_zero_divide_error(escontext), but don't insert a return, so the
> code will continue to perform the division.  Similar to patch 0017,
> maybe these functions should be changed to have a non-void return
> value.  Or maybe make separate functions to be used in places where we
> want soft error handling.
>

Add argument to float_overflow_error, extern void
float_underflow_error, float_zero_divide_error
requires extensive refactoring.

So I have added
+extern float8 float_overflow_error_ext(struct Node *escontext);
+extern float8 float_underflow_error_ext(struct Node *escontext);
+extern float8 float_zero_divide_error_ext(struct Node *escontext);

> the required changes there.
Please see 007.
Now with this entire v24 patchset, the only built-in cast function
that is not error safe is numeric_cash.

I'll think about CREATE CAST ... WITH SAFE FUNCTION syntax, later.
Right now, I am still using CoercionErrorSafe_Internal to check if CAST DEFAULT
can be applied to this combination of source type and target type.

--
jian
https://www.enterprisedb.com/
From 11e8df6c393b886ac446f2fd9be35b6367c7b241 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 19:12:10 +0800
Subject: [PATCH v24 8/9] error safe for casting money data type

NOTE: casting from money data type is not error safe, since cash_numeric is not
error safe.

and casttarget::regtype = 'money'::regtype

------------+------------+----------+-------------+------------+--------------+---------

Author: jian he <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/cash.c | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 623f6eec056..f0487a60f00 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -24,6 +24,7 @@
 
 #include "common/int.h"
 #include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
 #include "utils/builtins.h"
 #include "utils/cash.h"
 #include "utils/float.h"
@@ -1106,12 +1107,12 @@ cash_numeric(PG_FUNCTION_ARGS)
 Datum
 numeric_cash(PG_FUNCTION_ARGS)
 {
-	Datum		amount = PG_GETARG_DATUM(0);
+	Numeric		amount = PG_GETARG_NUMERIC(0);
 	Cash		result;
 	int			fpoint;
 	int64		scale;
 	int			i;
-	Datum		numeric_scale;
+	Numeric		numeric_scale;
 	struct lconv *lconvert = PGLC_localeconv();
 
 	/* see comments about frac_digits in cash_in() */
@@ -1125,11 +1126,16 @@ numeric_cash(PG_FUNCTION_ARGS)
 		scale *= 10;
 
 	/* multiply the input amount by scale factor */
-	numeric_scale = NumericGetDatum(int64_to_numeric(scale));
-	amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale);
+	numeric_scale = int64_to_numeric(scale);
+
+	amount = numeric_mul_safe(amount, numeric_scale, fcinfo->context);
+	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+		PG_RETURN_NULL();
 
 	/* note that numeric_int8 will round to nearest integer for us */
-	result = DatumGetInt64(DirectFunctionCall1(numeric_int8, amount));
+	result = numeric_int8_safe(amount, fcinfo->context);
+	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+		PG_RETURN_NULL();
 
 	PG_RETURN_CASH(result);
 }
@@ -1158,8 +1164,10 @@ int4_cash(PG_FUNCTION_ARGS)
 		scale *= 10;
 
 	/* compute amount * scale, checking for overflow */
-	result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
-											   Int64GetDatum(scale)));
+	if (unlikely(pg_mul_s64_overflow(amount, scale, &result)))
+		ereturn(fcinfo->context, (Datum) 0,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("bigint out of range"));
 
 	PG_RETURN_CASH(result);
 }
@@ -1188,8 +1196,10 @@ int8_cash(PG_FUNCTION_ARGS)
 		scale *= 10;
 
 	/* compute amount * scale, checking for overflow */
-	result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
-											   Int64GetDatum(scale)));
+	if (unlikely(pg_mul_s64_overflow(amount, scale, &result)))
+		ereturn(fcinfo->context, (Datum) 0,
+				errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				errmsg("bigint out of range"));
 
 	PG_RETURN_CASH(result);
 }
-- 
2.34.1

From 345fa4afc88a7706c54a16a5047cb7aa777ecda2 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:18:01 +0800
Subject: [PATCH v24 5/9] refactor point_dt

 

 

 
 static inline float8
 
 Datum

 
 
 Datum

 Datum

 Datum

 
 
 Datum

 Datum
 
 Datum

 

 

 

 

 

 
-- 
2.34.1

From ef8320545ada7593a2e59a300c38032e4f4cb901 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 08:35:25 +0800
Subject: [PATCH v24 2/9] error safe for casting jsonb to other types per
 pg_cast

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 1b1a8f301f2..864c5ac1c85 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1785,8 +1785,8 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
 /*
  * Emit correct, translatable cast error message
  */
-static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+static Datum
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
 {
 	static const struct
 	{
@@ -1807,12 +1807,13 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)

 
 Datum
@@ -1822,7 +1823,7 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	JsonbValue	v;

 	if (v.type == jbvNull)
 	{
@@ -1831,7 +1832,7 @@ jsonb_bool(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);
 
@@ -1846,7 +1847,7 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	Numeric		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1855,7 +1856,7 @@ jsonb_numeric(PG_FUNCTION_ARGS)
 	}

 	/*
 	 * v.val.numeric points into jsonb body, so we need to make a copy to
@@ -1876,7 +1877,7 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1885,7 +1886,7 @@ jsonb_int2(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int2,
 								   NumericGetDatum(v.val.numeric));
@@ -1903,7 +1904,7 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1912,7 +1913,7 @@ jsonb_int4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int4,
 								   NumericGetDatum(v.val.numeric));
@@ -1930,7 +1931,7 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1939,7 +1940,7 @@ jsonb_int8(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_int8,
 								   NumericGetDatum(v.val.numeric));
@@ -1957,7 +1958,7 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1966,7 +1967,7 @@ jsonb_float4(PG_FUNCTION_ARGS)
 	}

 	retValue = DirectFunctionCall1(numeric_float4,
 								   NumericGetDatum(v.val.numeric));
@@ -1984,7 +1985,7 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	Datum		retValue;

 	if (v.type == jbvNull)
 	{
@@ -1993,7 +1994,7 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	}

From 01101c2f100ffbf66b9cd0e76fe66cbd18a4863f Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:19:10 +0800
Subject: [PATCH v24 6/9] error safe for casting geometry data type

 
 
 /* Routines for boxes */

-	box_cn(&a, box1);
-	box_cn(&b, box2);

-	box_cn(result, box);

 
-

 

 	if (result != NULL)
 	{
-		box_cn(&point, box);

 static inline void
 
 Datum

 

 
 	PG_RETURN_BOX_P(result);
 }

 	PG_RETURN_PATH_P(path);
 }

 

 

 

 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()

 

 
 	Assert(poly->npts > 0);

 
 
 Datum

 

From 884b32b9257ec5a45e35c84ddbdc2ac067343add Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 21:05:46 +0800
Subject: [PATCH v24 9/9] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Example:

 src/backend/parser/parse_expr.c               | 357 ++++++-
 src/test/regress/expected/cast.out            | 989 ++++++++++++++++++
 src/test/regress/sql/cast.sql                 | 401 +++++++
 40 files changed, 2380 insertions(+), 88 deletions(-)

 

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
 
    <para>

 

 

 
+/*

 /*

 	return funcexpr;
index 9fb266d089d..8a54ba26890 100644
 
 typedef struct

 
 
 /*

 				return (Node *) expr;
 			}
 
+				 * not use it, see ExecInitSafeTypeCastExpr. Additionally,

 				return (Node *) ac;
 			}

 
 
 /*

 	/*
 	 * And evaluate it.

 	n->arg = arg;
 	n->typeName = typename;
+	n->defexpr = NULL;
 	n->location = location;
 	return (Node *) n;
 }
 
+static Node *
 
 			break;

 
 	/*

 		/*
 
 		/*

 
 
 			/*

 	/*

 

 	return (Node *) rowexpr;
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 312dfdc182a..3d38d468c42 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@

 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
 	/*
 
 		if (coerce_hard)
 		{

@@ -2783,20 +2880,210 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 
+	source = defexpr ? expr : copyObject(expr);
+						parser_coercion_errposition(pstate, location, expr));
 
-	return result;
+static void
+		 * Input expression can be UNKNOWN Const, and UNKNOWN typtype is
+		 * TYPTYPE_PSEUDO.
+	 * Casts source data type as MONEY are not error safe.
+	if (inputType == MONEYOID)
+		else if (inputType > FirstUnpinnedObjectId || targetType > FirstUnpinnedObjectId)
 
 /*
@@ -3212,6 +3499,8 @@ ParseExprKindName(ParseExprKind exprKind)

 			return (Node *) fstore;
 		}
 
+/*

 
 
index 684e398f824..3ee039dacd4 100644

 
+/*
+ * SafeTypeCastExpr -

 
 
index 00000000000..7cfec864448
@@ -0,0 +1,989 @@
+SET lc_monetary TO "C";
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+-- error: `three'::int` will fail earlier
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+-- Safe type cast supports money as a target type, but not as a source type.
+SELECT CAST('123456789012345678'::numeric AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money 
+SELECT CAST('123456789012345678'::int8 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money 
+SELECT CAST('2147483647'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+       money       
+ $2,147,483,647.00
+SELECT CAST('-2147483648'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+       money        
+--------------------
+ -$2,147,483,648.00
+SELECT CAST('-0'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money 
+ $0.00
+RESET lc_monetary;

 # ----------
index 00000000000..21c1c5c3ce0
@@ -0,0 +1,401 @@
+SET lc_monetary TO "C";
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+-- error: `three'::int` will fail earlier
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+-- Safe type cast supports money as a target type, but not as a source type.
+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);
+RESET lc_monetary;

 
index dbbec84b222..ddfbd84c8d6 100644
@@ -2738,6 +2738,8 @@ STRLEN

From 55cc9b3722729a579cae880801b757caa62a9760 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 16:26:30 +0800
Subject: [PATCH v24 4/9] introduce float8 safe function

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/include/utils/float.h | 66 ++++++++++++++++++++++++++-------------
 1 file changed, 45 insertions(+), 21 deletions(-)

diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 5840bbf7dc8..ffa743d6273 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -112,16 +112,22 @@ float4_pl(const float4 val1, const float4 val2)
 	return result;
 }
 
+static inline float8
+		return float_overflow_error_ext(escontext);
 
 static inline float4
@@ -136,16 +142,22 @@ float4_mi(const float4 val1, const float4 val2)
 	return result;
 }
 
+static inline float8
+		return float_overflow_error_ext(escontext);
 
 static inline float4
@@ -163,19 +175,25 @@ float4_mul(const float4 val1, const float4 val2)
 }
 
 static inline float8
 
 	result = val1 * val2;
 	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
+		return float_overflow_error_ext(escontext);
 	if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
-		float_underflow_error();
+		return float_underflow_error_ext(escontext);
 
 	return result;
 }
 
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+	return float8_mul_safe(val1, val2, NULL);
+}
+
 static inline float4
 float4_div(const float4 val1, const float4 val2)
 {
@@ -193,21 +211,27 @@ float4_div(const float4 val1, const float4 val2)
 }
 
 static inline float8
 
 	if (unlikely(val2 == 0.0) && !isnan(val1))
-		float_zero_divide_error();
+		return float_zero_divide_error_ext(escontext);
 	result = val1 / val2;
 	if (unlikely(isinf(result)) && !isinf(val1))
-		float_overflow_error();
+		return float_overflow_error_ext(escontext);
 	if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
-		float_underflow_error();
+		return float_underflow_error_ext(escontext);
 
 	return result;
 }
 
+static inline float8

From 6137e9e9bf3049f808e4637acec48fce287b3830 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 17:19:18 +0800
Subject: [PATCH v24 7/9] error safe for casting circle to polygon

Previously:

Now refactor it as a C/internal function, so it can error-safe.

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/catalog/system_functions.sql |  6 ---
 src/backend/utils/adt/geo_ops.c          | 52 ++++++++++++++++++++----
 src/include/catalog/pg_proc.dat          |  4 +-
 3 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 1c5b6d6df05..c3c0a6e84ed 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -99,12 +99,6 @@ CREATE OR REPLACE FUNCTION path_contain_pt(path, point)
  IMMUTABLE PARALLEL SAFE STRICT COST 1
 RETURN on_ppath($2, $1);
 
-CREATE OR REPLACE FUNCTION polygon(circle)
- RETURNS polygon
- LANGUAGE sql
- IMMUTABLE PARALLEL SAFE STRICT COST 1
-RETURN polygon(12, $1);
-
 CREATE OR REPLACE FUNCTION age(timestamptz)
  RETURNS interval
  LANGUAGE sql
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index a59f1cabc44..4846fe10f9c 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -147,6 +147,7 @@ static bool path_decode(char *str, bool opentype, int npts, Point *p,
 						const char *type_name, const char *orig_string,
 						Node *escontext);
 static char *path_encode(enum path_delim path_delim, int npts, Point *pt);
+static Datum circle_poly_internal(int32 npts, CIRCLE *circle, FunctionCallInfo fcinfo);

 /*
@@ -5319,26 +5320,33 @@ fail:
 	PG_RETURN_NULL();
 }
 
-
 Datum
 circle_poly(PG_FUNCTION_ARGS)
 {
 	int32		npts = PG_GETARG_INT32(0);
 	CIRCLE	   *circle = PG_GETARG_CIRCLE_P(1);
+
+	PG_RETURN_DATUM(circle_poly_internal(npts, circle, fcinfo));
+}
+
+Datum
+circle_poly_internal(int32 npts, CIRCLE *circle, FunctionCallInfo fcinfo)
+{
 	POLYGON    *poly;
 	int			base_size,
 				size;
 	int			i;
 	float8		angle;
 	float8		anglestep;
+	float8		temp;

 	if (npts < 2)
-		ereport(ERROR,
 
@@ -5347,7 +5355,7 @@ circle_poly(PG_FUNCTION_ARGS)

@@ -5359,17 +5367,43 @@ circle_poly(PG_FUNCTION_ARGS)

-		poly->p[i].x = float8_mi(circle->center.x,
-								 float8_mul(circle->radius, cos(angle)));
-		poly->p[i].y = float8_pl(circle->center.y,
-								 float8_mul(circle->radius, sin(angle)));
+		temp = float8_mul_safe(circle->radius, cos(angle), fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			goto fail;
+
+		poly->p[i].x = float8_mi_safe(circle->center.x, temp, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			goto fail;
+
+		temp = float8_mul_safe(circle->radius, sin(angle), fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			goto fail;
+
+		poly->p[i].y = float8_pl_safe(circle->center.y, temp, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			goto fail;
 	}
 
 	make_bound_box(poly);
 
 	PG_RETURN_POLYGON_P(poly);
+
+fail:
+	PG_RETURN_NULL();
+}
+
+/* convert circle to 12-vertex polygon */
+Datum
+circle_to_poly(PG_FUNCTION_ARGS)
+{
+	int32		npts = 12;
+	CIRCLE	   *circle = PG_GETARG_CIRCLE_P(0);
+
+	PG_RETURN_DATUM(circle_poly_internal(npts, circle, fcinfo));
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0118e970dda..3579cec5744 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3390,8 +3390,8 @@
   proname => 'center', prorettype => 'point', proargtypes => 'circle',
   prosrc => 'circle_center' },
 { oid => '1544', descr => 'convert circle to 12-vertex polygon',
-  proname => 'polygon', prolang => 'sql', prorettype => 'polygon',
-  proargtypes => 'circle', prosrc => 'see system_functions.sql' },
+  proname => 'polygon', prorettype => 'polygon',
+  proargtypes => 'circle', prosrc => 'circle_to_poly' },
 { oid => '1545', descr => 'number of points',
   proname => 'npoints', prorettype => 'int4', proargtypes => 'path',
   prosrc => 'path_npoints' },
-- 
2.34.1

From c6f4e32727a742a21eaee3e546c92f92fe5bda54 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 16:19:39 +0800
Subject: [PATCH v24 3/9] extended function for float
 overflow/underflow/zero_divide

float_overflow_error_ext, float_underflow_error_ext,
float_zero_divide_error_ext.

Author: jian he <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/utils/adt/float.c | 24 ++++++++++++++++++++++++
 src/include/utils/float.h     |  3 +++
 2 files changed, 27 insertions(+)

diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index a06fef42901..7b1bdf1e574 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -123,6 +123,30 @@ float_zero_divide_error(void)
 			 errmsg("division by zero")));
 }
 
+float8
+float_overflow_error_ext(struct Node *escontext)
+{
+	ereturn(escontext, 0.0,
+			errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+			errmsg("value out of range: overflow"));
+}
+
+float8
+float_underflow_error_ext(struct Node *escontext)
+{
+	ereturn(escontext, 0.0,
+			errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+			errmsg("value out of range: underflow"));
+}
+
+float8
+float_zero_divide_error_ext(struct Node *escontext)
+{
+	ereturn(escontext, 0.0,
+			(errcode(ERRCODE_DIVISION_BY_ZERO),
+			 errmsg("division by zero")));
+}
+
 
 /*
  * Returns -1 if 'val' represents negative infinity, 1 if 'val'
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index b340678ca92..5840bbf7dc8 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -33,6 +33,9 @@ extern PGDLLIMPORT int extra_float_digits;
 pg_noreturn extern void float_overflow_error(void);
 pg_noreturn extern void float_underflow_error(void);
 pg_noreturn extern void float_zero_divide_error(void);
+extern float8 float_overflow_error_ext(struct Node *escontext);
+extern float8 float_underflow_error_ext(struct Node *escontext);
+extern float8 float_zero_divide_error_ext(struct Node *escontext);
 extern int	is_infinite(float8 val);
 extern float8 float8in_internal(char *num, char **endptr_p,
 								const char *type_name, const char *orig_string,
-- 
2.34.1

From 808dc0dbad828897e15c7e784f9b385b6b8ee38d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:55:58 +0800
Subject: [PATCH v24 1/9] error safe for casting text to other types per
 pg_cast

 
 
 			return InvalidOid;
 		}

 

 
 
 	if (namelist == NIL)
-		ereport(ERROR,
 
index 9453a3e4932..7f3141ba27d 100644

---

From: jian he <[email-masked]>
Date: Thu, 26 Mar 2026 16:59:56 +0800

Currently, when a valid ErrorSaveContext is passed to state->escontext
(ExprState->escontext),
ExecInitExprRec is designed to compile the entire expression tree
using soft errors.
Consider the following example:
    create table t1(a text, b text, c int, d int8);
    create domain d2 as text check (value <> 'a');
    insert into t1(a, d) values('a', 2147483648);
    select cast (cast(d as int) as text default null on conversion
error) from t1;  -- queryA
    select cast (cast(a as d2) as text default null on conversion
error) from t1;   -- queryB
    select cast (cast(a as int) as text default null on conversion
error) from t1;  -- queryC

With V24, for queryC, ExecInitExprRec will compile the inner ``cast(a as int)``
error-safe way. This is just a subexpression of the main outer
cast(... as text default ...) node,
we may want the inner cast to be evaluated not error-safe.

To resolve this, we need to differentiate whether the specific nodes produced by
coerce_to_target_type->build_coercion_expression/coerce_to_domain
should be error-safe.
This means the resulting nodes (FuncExpr, ArrayCoerceExpr,
CoerceViaIO, CoerceToDomain) need a boolean flag.

We have already solved this for FuncExpr in [1] by adding an errorsafe field. We
now need to add a similar errorsafe boolean to CoerceViaIO and CoerceToDomain.

Therefore, for safe error evaluation: 1. ExecInitExprRec, ExprState->escontext
must be a valid ErrorSaveContext 2. The expression node (need a booleaan) to
tell ExecInitExprRec that compile this in an error safe manner.

[1]: https://postgr.es/m/[email-masked]

Summary: Making the above queryA, queryB, and queryC return NULL is
straightforward; however, forcing all of them to throw an error is non-trivial
because it involves compiling/evaluating some subexpressions in an
error-safe manner while
others are not.

What do you think?

---

From: Corey Huinker <[email-masked]>
Date: Fri, 27 Mar 2026 16:59:49 -0400

> element/base type is MONEYOID.

What if we just...didn't? The MONEY has been deprecated since 7.4. If we
simply didn't implement this, and someone attempted to use this cast with
default, what bad would happen?

---

From: jian he <[email-masked]>
Date: Sat, 28 Mar 2026 08:37:07 +0800

> error) from t1;  -- queryC

In ATExecAddColumn, we have:

exprState = ExecPrepareExprWithContext(defval, estate, (Node *) &escontext);

The entire defval expression tree (including all the potential FuncExpr node
subexpression) must be marked as error-safe (all the
FuncExpr->errorsafe must be set to true).
If they aren't, ExecPrepareExprWithContext won't work.
In that scarenio, we need a walker function to set FuncExpr->errorsafe
to true for the above defval, overall feels very ugly.

Therefore, the previous approach (v24) of simply adding an errorsafe boolean to
FuncExpr will not work.
IMHO, it is not feasible to mark only part of an expression tree as error-safe.
Because of this, we should try to make queryA, queryB, queryC return NULL.

However, some inconsistency remains, for example:
SELECT CAST (CAST('a' AS int) AS text DEFAULT NULL ON CONVERSION
ERROR) FROM tcast1;  -- error
SELECT CAST (CAST('a'::text AS int) AS text DEFAULT NULL ON CONVERSION
ERROR) FROM tcast1; -- ok

V24: There is a segfault because of eval_const_expressions_mutator,
T_ArrayCoerceExpr handling,
You can invoke it via:
select cast (cast('{a}'::text[] as int[]) as text[] default null on
conversion error) from t2;

In V24, there were no nested type cast regress tests, now I have added some.

I removed the errorsafe boolean from FuncExpr. As mentioned previously, it is
difficult to make ExecInitExprRec compile expression partically eror safe.

refactoring on transformArrayExpr,  transformTypeCast, ExecInitSafeTypeCastExpr.

--
jian
https://www.enterprisedb.com/
From 4f1bac86869486a2756b55550a5fe5184745cd60 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 19:12:10 +0800
Subject: [PATCH v25 8/9] error safe for casting money data type

 
 #include "common/int.h"

 
 
 	PG_RETURN_CASH(result);
 }

 	PG_RETURN_CASH(result);
 }

 	PG_RETURN_CASH(result);
 }
-- 
2.34.1

From ff2ce117225ac46970ef251698c44f51e1839e9c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 28 Mar 2026 08:36:24 +0800
Subject: [PATCH v25 9/9] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Example:

 src/backend/optimizer/util/clauses.c          |   90 +-
 src/backend/parser/parse_coerce.c             |  142 ++-
 src/backend/parser/parse_expr.c               |  358 +++++-
 src/test/regress/expected/cast.out            | 1071 +++++++++++++++++
 src/test/regress/expected/xml.out             |    6 +
 src/test/regress/expected/xml_1.out           |    3 +
 src/test/regress/sql/cast.sql                 |  424 +++++++
 src/test/regress/sql/xml.sql                  |    2 +
 42 files changed, 2489 insertions(+), 94 deletions(-)

 

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
 
    <para>
 
index 77229141b38..51cfd5472e5 100644

 

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4770,6 +4784,51 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+		 * Evaluate the DEFAULT expression cast. state->escontext should be
+		 * the previous used one

 /*
 
index 9fb266d089d..ba77d4aecf8 100644
 
 typedef struct

 
 
 /*
  * Recursive guts of eval_const_expressions/estimate_expression_value
@@ -3136,7 +3140,7 @@ eval_const_expressions_mutator(Node *node,

 				return (Node *) expr;
 			}
@@ -3156,7 +3160,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, (Node *) context->escontext);
 				return (Node *) saop;
 			}
 		case T_BoolExpr:
@@ -3267,6 +3271,42 @@ eval_const_expressions_mutator(Node *node,
 												  copyObject(jve->format));
 			}

@@ -3384,6 +3424,7 @@ eval_const_expressions_mutator(Node *node,
 			{
 				ArrayCoerceExpr *ac = makeNode(ArrayCoerceExpr);
 				Node	   *save_case_val;
+				Expr	   *simple = NULL;
 
 				/*
 				 * Copy the node and const-simplify its arguments.  We can't
@@ -3391,9 +3432,10 @@ eval_const_expressions_mutator(Node *node,
 				 * with case_val only while processing the elemexpr.
 				 */
 				memcpy(ac, node, sizeof(ArrayCoerceExpr));
-				ac->arg = (Expr *)
-					eval_const_expressions_mutator((Node *) ac->arg,
-												   context);
+				simple = (Expr *) eval_const_expressions_mutator((Node *) ac->arg,
+																 context);
+				if (simple)
+					ac->arg = simple;
 
 				/*
 				 * Set up for the CaseTestExpr node contained in the elemexpr.
@@ -3402,9 +3444,10 @@ eval_const_expressions_mutator(Node *node,
 				save_case_val = context->case_val;
 				context->case_val = NULL;
 
-				ac->elemexpr = (Expr *)
-					eval_const_expressions_mutator((Node *) ac->elemexpr,
-												   context);
+				simple = (Expr *) eval_const_expressions_mutator((Node *) ac->elemexpr,
+																 context);
+				if (simple)
+					ac->elemexpr = simple;
 
 				context->case_val = save_case_val;
 
@@ -3419,7 +3462,7 @@ eval_const_expressions_mutator(Node *node,
 
 				return (Node *) ac;
 			}
@@ -3615,7 +3658,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, (Node *) context->escontext);
 				return node;
 			}
 		case T_CoalesceExpr:
@@ -3698,7 +3741,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);
 			}
@@ -3795,7 +3839,7 @@ eval_const_expressions_mutator(Node *node,
 											  newfselect->resulttype,
 											  newfselect->resulttypmod,
 											  newfselect->resultcollid))
-						return ece_evaluate_expr(newfselect);
+						return ece_evaluate_expr(newfselect, (Node *) context->escontext);
 				}
 				return (Node *) newfselect;
 			}
@@ -4125,7 +4169,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, (Node *) context->escontext);
 				return (Node *) newcre;
 			}
 		default:
@@ -5229,7 +5273,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	newexpr->location = -1;

 /*
@@ -5683,10 +5727,13 @@ sql_inline_error_callback(void *arg)
  *
@@ -5711,7 +5758,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 
 	/*
 	 * And evaluate it.
@@ -5731,6 +5778,13 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);

 	n->arg = arg;
 	n->typeName = typename;
+	n->defexpr = NULL;
 	n->location = location;
 	return (Node *) n;
 }
 
+static Node *
 
 			break;
 
index 913ca53666f..8bd79fd5bfc 100644

 	/*

 		/*
 
 		/*

 
 
 			/*

 	/*
 
@@ -950,14 +1019,15 @@ build_coercion_expression(Node *node,
 		targetElementType = get_element_type(targetTypeId);
 		Assert(OidIsValid(targetElementType));

@@ -1140,7 +1210,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 								baseTypeId, baseTypeMod,
 								targetTypeId,
 								ccontext, cformat, location,
-								false);
+								false,
+								NULL);
 	}
 
 	return (Node *) rowexpr;
@@ -1295,7 +1366,8 @@ coerce_null_to_domain(Oid typid, int32 typmod, Oid collation,
 								  COERCION_IMPLICIT,
 								  COERCE_IMPLICIT_CAST,
 								  -1,
-								  false);
+								  false,
+								  NULL);
 	return result;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 312dfdc182a..de60cfd6175 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@

 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
@@ -566,6 +574,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_VALUES_SINGLE:
 		case EXPR_KIND_CHECK_CONSTRAINT:
 		case EXPR_KIND_DOMAIN_CHECK:
+		case EXPR_KIND_CAST_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 		case EXPR_KIND_INDEX_EXPRESSION:
 		case EXPR_KIND_INDEX_PREDICATE:
@@ -1840,6 +1849,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("cannot use subquery in check constraint");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("cannot use subquery in CAST DEFAULT expression");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
@@ -2030,10 +2042,15 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
  * If the caller specifies the target type, the resulting array will
  * be of exactly that type.  Otherwise we try to infer a common type
  * for the elements using select_common_type().
+ *
+ * escontext (ErrorSaveContext *) is typically NULL, except during parse analysis for
+ * CAST(... DEFAULT ... ON CONVERSION ERROR). When provided, it should defaults
+ * ErrorSaveContext->error_occurred = false; error_occurred ise set
+ * to true if coercing array elements fails.
  */
 static Node *
 transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
-				   Oid array_type, Oid element_type, int32 typmod)
+				   Oid array_type, Oid element_type, int32 typmod, Node *escontext)
 {
 	ArrayExpr  *newa = makeNode(ArrayExpr);
 	List	   *newelems = NIL;
@@ -2064,9 +2081,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 									  (A_ArrayExpr *) e,
 									  array_type,
 									  element_type,
-									  typmod);
+									  typmod,
+									  escontext);
 			/* we certainly have an array here */
-			Assert(array_type == InvalidOid || array_type == exprType(newe));
+			Assert(escontext || array_type == InvalidOid || array_type == exprType(newe));
 			newa->multidims = true;
 		}
 		else
@@ -2107,6 +2125,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	}
 	else
 	{
+		Assert(escontext == NULL);
+
 		/* Can't handle an empty array without a target type */
 		if (newelems == NIL)
 			ereport(ERROR,
@@ -2153,28 +2173,62 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	 * If the array's type was merely derived from the common type of its
 	 * elements, then the elements are implicitly coerced to the common type.
 	 * This is consistent with other uses of select_common_type().
+	 *
+	 * If "escontext" is not NULL, check whether each element can be coerced
+	 * to the target type, similar to what is done in transformTypeCast.
 	 */
 	foreach(element, newelems)
 	{
 		Node	   *e = (Node *) lfirst(element);
-		Node	   *newe;
+		Node	   *newe = NULL;
 
 		if (coerce_hard)
 		{
-			newe = coerce_to_target_type(pstate, e,
-										 exprType(e),
-										 coerce_type,
-										 typmod,
-										 COERCION_EXPLICIT,
-										 COERCE_EXPLICIT_CAST,
-										 -1);
-			if (newe == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_CANNOT_COERCE),
-						 errmsg("cannot cast type %s to %s",
-								format_type_be(exprType(e)),
-								format_type_be(coerce_type)),
-						 parser_errposition(pstate, exprLocation(e))));
+			/*
+			 * Cannot coerce, just append the transformed element expression
+			 * to the list.
+			 */
+			if (SOFT_ERROR_OCCURRED(escontext))
+				newe = e;
+			else
+			{
+				Node	   *ecopy = NULL;
+
+				if (escontext)
+					ecopy = copyObject(e);
+
+				newe = coerce_to_target_type_extended(pstate, e,
+													  exprType(e),
+													  coerce_type,
+													  typmod,
+													  COERCION_EXPLICIT,
+													  COERCE_EXPLICIT_CAST,
+													  -1,
+													  escontext);
+				if (newe == NULL)
+				{
+					/*
+					 * Cannot coerce. Raise an error or append the transformed
+					 * element to the list. Also, set
+					 * escontext->error_occurred to true so the caller can
+					 * notice that array coercion failed.
+					 */
+					if (!escontext)
+						ereport(ERROR,
+								(errcode(ERRCODE_CANNOT_COERCE),
+								 errmsg("cannot cast type %s to %s",
+										format_type_be(exprType(e)),
+										format_type_be(coerce_type)),
+								 parser_errposition(pstate, exprLocation(e))));
+					else
+					{
+						ErrorSaveContext *context = castNode(ErrorSaveContext, escontext);
+
+						newe = ecopy;
+						context->error_occurred = true;
+					}
+				}
+			}
 		}
 		else
 			newe = coerce_to_common_type(pstate, e,
@@ -2723,17 +2777,66 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
 static Node *
 transformTypeCast(ParseState *pstate, TypeCast *tc)
 {
-	Node	   *result;
+	SafeTypeCastExpr *stc;
+	Node	   *castexpr = NULL;
+	Node	   *defexpr = NULL;
 	Node	   *arg = tc->arg;
+	Node	   *source;
 	Node	   *expr;
 	Oid			inputType;
 	Oid			targetType;
+	Oid			targetTypecoll;
 	int32		targetTypmod;
 	int			location;
+	ErrorSaveContext *escontext = makeNode(ErrorSaveContext);
+
+	escontext->type = T_ErrorSaveContext;
+	escontext->error_occurred = false;
+	escontext->details_wanted = false;

@@ -2762,7 +2865,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
+									  defexpr ? (Node *) escontext : NULL);
@@ -2783,20 +2887,204 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 
+	source = defexpr ? copyObject(expr) : expr;
+	if (!SOFT_ERROR_OCCURRED(escontext))
+												  defexpr ? (Node *) escontext : NULL);
+		/* Coercion failed. Error out if CAST DEFAULT is not available. */
+		if (!castexpr && !defexpr)
 
-	return result;
+static void
 
 /*
@@ -3212,6 +3500,8 @@ ParseExprKindName(ParseExprKind exprKind)
index 8dbd41a3548..359a694fe09 100644
 
 			return (Node *) fstore;
 		}
 
+/*

 

 
index f5b6b45664a..71e55ea1f8a 100644
 
+/*
+ * SafeTypeCastExpr -

 
 
index 00000000000..348ed4ced03
@@ -0,0 +1,1071 @@
+-- Test nested type casts; all expressions will be evaluated in an error-safe manner
+CREATE DOMAIN d_nota AS text CHECK (VALUE <> 'a');
+CREATE TABLE tcast1(a text, b text[], c int, d int8, e int8[]);
+INSERT INTO tcast1(a, b, d, e) values('a', '{a}', 2147483648, '{2147483648}');
+SELECT CAST (CAST(a AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST('a' AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;  -- error
+LINE 1: SELECT CAST (CAST('a' AS int) AS text DEFAULT NULL ON CONVER...
+SELECT CAST (CAST('a'::text AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; -- ok
+SELECT CAST (CAST(a AS d_nota) 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(d AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ d 
+SELECT CAST (CAST('2147483648'::int8 AS int) AS text 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 (CAST(b AS int[]) 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(e AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ e 
+-- test eval_const_expressions_mutator handle nested ArrayCoerceExpr
+SELECT CAST('{a}'::text[] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(CAST('{a}'::text[] AS date[]) AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+       CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name
+       text        | to_regclass | expect_true | to_char |      to_name      
+-------------------+-------------+-------------+---------+-------------------
+ <value>one</value |             | t           | <       | <value>one</value
+DROP TABLE tcast1;
+DROP DOMAIN d_nota;
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 103a22a3b1d..815a5e8899f 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -18,6 +18,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/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 73c411118a3..69aea833169 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -22,6 +22,9 @@ SELECT * FROM xmltest;
 ----+------
 (0 rows)
 
+SELECT CAST('<wrong'::text AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
 -- test non-throwing API, too
 SELECT pg_input_is_valid('<value>one</value>', 'xml');
 ERROR:  unsupported XML feature
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 734da057c34..c82b4b31423 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
-test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252
+test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252 cast
 
 # ----------
index 00000000000..d4643f9e2f2
@@ -0,0 +1,424 @@
+-- Test nested type casts; all expressions will be evaluated in an error-safe manner
+CREATE DOMAIN d_nota AS text CHECK (VALUE <> 'a');
+CREATE TABLE tcast1(a text, b text[], c int, d int8, e int8[]);
+INSERT INTO tcast1(a, b, d, e) values('a', '{a}', 2147483648, '{2147483648}');
+SELECT CAST (CAST(a AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST('a' AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;  -- error
+SELECT CAST (CAST('a'::text AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; -- ok
+SELECT CAST (CAST(a AS d_nota) 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(d AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST('2147483648'::int8 AS int) AS text 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 (CAST(b AS int[]) 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(e AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+-- test eval_const_expressions_mutator handle nested ArrayCoerceExpr
+SELECT CAST('{a}'::text[] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(CAST('{a}'::text[] AS date[]) AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+       CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name
+DROP TABLE tcast1;
+DROP DOMAIN d_nota;

 
+-- int8alias2 binary-coercible to int8, this should not fail
+select cast('1'::int8 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 0ea4f508837..df3a06f4d82 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -9,6 +9,8 @@ INSERT INTO xmltest VALUES (3, '<wrong');
 
 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 712d84128ca..7c9ea1ae47d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2739,6 +2739,8 @@ STRLEN
 SV
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
+SafeTypeCastExpr
+SafeTypeCastState
 SampleScan
 SampleScanGetSampleSize_function
 SampleScanState
-- 
2.34.1

From 7aca3c439cda733eb3f9abcd81336bfb3316e9f8 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:19:10 +0800
Subject: [PATCH v25 6/9] error safe for casting geometry data type

 
 
 /* Routines for boxes */

-	box_cn(&a, box1);
-	box_cn(&b, box2);

-	box_cn(result, box);

 
-

 

 	if (result != NULL)
 	{
-		box_cn(&point, box);

 static inline void
 
 Datum

 

 
 	PG_RETURN_BOX_P(result);
 }

 	PG_RETURN_PATH_P(path);
 }

 

 

 

 
 	PG_RETURN_BOX_P(box);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 /* box_circle()

 

 
 	Assert(poly->npts > 0);

 
 
 Datum

 

From bc037fcd82966fd9e1e5dd69c41fe035df951144 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 08:35:25 +0800
Subject: [PATCH v25 2/9] error safe for casting jsonb to other types per
 pg_cast

 

 Datum

 	if (v.type == jbvNull)
 	{
 
 	if (v.type != jbvBool)
 
 	PG_FREE_IF_COPY(in, 0);

 
 	if (v.type == jbvNull)
 	{

 	/*

 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

 
 
 	if (v.type == jbvNull)
 	{

From b99e27217a0aedcc4d45ee794bb1f276f0aebaad Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 17:19:18 +0800
Subject: [PATCH v25 7/9] error safe for casting circle to polygon

Previously:

 

 /*
 
-
 Datum

 	if (npts < 2)
-		ereport(ERROR,

 

 
 	make_bound_box(poly);

 /*

From dd67559918c5929a6da589abc7d287e0bc0b5797 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 09:55:58 +0800
Subject: [PATCH v25 1/9] error safe for casting text to other types per
 pg_cast

 
 
 			return InvalidOid;
 		}

 

 
 
 	if (namelist == NIL)
-		ereport(ERROR,

From ae964e316ba565febb4dd3bc8d1c5e32499aee51 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 16:19:39 +0800
Subject: [PATCH v25 3/9] extended function for float
 overflow/underflow/zero_divide

 
+float8
 
 /*

From d8bb3a3748c1f16878d8cd3a7134d509759c2984 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 20 Mar 2026 10:18:01 +0800
Subject: [PATCH v25 5/9] refactor point_dt

 

 

 
 static inline float8
 
 Datum

 
 
 Datum

 Datum

 Datum

 
 
 Datum

 Datum
 
 Datum

 

 

 

 

 

 
-- 
2.34.1

From 04808be3b435e48ddad6b98a3d86b4d79911e4bf Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Wed, 25 Mar 2026 16:26:30 +0800
Subject: [PATCH v25 4/9] introduce float8 safe function

 
+static inline float8
 
 static inline float4
 
+static inline float8
 
 static inline float4
 
 static inline float8
 
 	result = val1 * val2;
 
 	return result;
 }
 
+static inline float8
 
 static inline float8

 	return result;
 }
 
+static inline float8

---

From: Amul Sul <[email-masked]>
Date: Mon, 30 Mar 2026 18:39:32 +0530

On Mon, Mar 30, 2026 at 4:57 PM Peter Eisentraut <[email-masked]> wrote:
>
>  From the v25 I have committed everything except 0001 and 0009.  I will
> continue reviewing the rest.
>

I’ve had a glance over the remaining patches; here are a few comments:

V25-0001:

The error code changed here -- was this intentional?
--

v25-0009 :

+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR:  cannot cast type character to citext when DEFAULT expression
is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+                    ^
+HINT:  Safe type cast for user-defined types are not yet supported.

"Should the HINT be this DETAIL? I believe a hint should provide
guidance for the user to handle the error, but here the user cannot do
anything with the given information.
--

I think we can simply cast escontext to Node * here, instead of
having the caller do it.

Regards,
Amul

---

From: jian he <[email-masked]>
Date: Mon, 30 Mar 2026 22:47:32 +0800

> The error code changed here -- was this intentional?

it's a copy-paste error.

ok.

ok.

I found a segmentation fault case in v25, it can be produced by:

SELECT CAST(('{' || sum(1 + 2) || '}')::int[] AS int[] DEFAULT NULL ON
CONVERSION ERROR) FROM (VALUES (1), (2)) sub;

We need set SafeTypeCastExpr->source to NULL. We can do this, because
SafeTypeCastExpr->castexpr sure contain all the
information of SafeTypeCastExpr->source.
If we don't nullify it, preprocess_aggref ends up processing both fields, then
parts of the Aggref node being initialized twice, which can ultimately
lead to a segfault.

--
jian
https://www.enterprisedb.com/
From ba3ec79e34fb1ef114d97aee12a90bcce2fca864 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 30 Mar 2026 22:24:23 +0800
Subject: [PATCH v26 2/2] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Example:

 src/backend/executor/execExpr.c               |   58 +-
 src/backend/optimizer/util/clauses.c          |   94 +-
 src/backend/parser/parse_expr.c               |  353 +++++-
 src/test/regress/expected/cast.out            | 1084 +++++++++++++++++
 src/test/regress/sql/cast.sql                 |  426 +++++++
 src/test/regress/sql/sqljson_queryfuncs.sql   |    1 +
 43 files changed, 2503 insertions(+), 94 deletions(-)

index 8c0bf54f0f3..73a3f923b07 100644
 
+DETAIL:  Safe type cast for user-defined types are not yet supported.
index c5e5f180f2b..28788adf26d 100644
 
+DETAIL:  Safe type cast for user-defined types are not yet supported.

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
 
    <para>
 
index 77229141b38..012be9e2290 100644

 

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4770,6 +4784,48 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+		/* Evaluate the DEFAULT expression using the prior state->escontext */

 /*
 
index 9fb266d089d..0d167e2e81c 100644
 
 typedef struct
@@ -2492,6 +2493,10 @@ 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: If eval_const_expressions_context->escontext is not NULL, the
+ * expression is evaluated in an error-safe manner. In this case, the
+ * function will return NULL instead of throwing an error.
  *--------------------
  */
 Node *
@@ -2507,6 +2512,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);
 }
 
@@ -2646,6 +2652,7 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 
@@ -2671,11 +2678,12 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 	(!expression_tree_walker((Node *) (node), contain_non_const_walker, NULL))
 
+							(Node *) escontext))
 
 /*
  * Recursive guts of eval_const_expressions/estimate_expression_value
@@ -3136,7 +3144,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;
 			}
@@ -3156,7 +3164,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:
@@ -3267,6 +3275,42 @@ eval_const_expressions_mutator(Node *node,
 												  copyObject(jve->format));
 			}
 
+				newexpr->source = NULL;
 
@@ -3384,6 +3428,7 @@ eval_const_expressions_mutator(Node *node,
 			{
 				ArrayCoerceExpr *ac = makeNode(ArrayCoerceExpr);
 				Node	   *save_case_val;
+				Expr	   *simple = NULL;
 
 				/*
@@ -3391,9 +3436,10 @@ eval_const_expressions_mutator(Node *node,
 
 				/*
 				 * Set up for the CaseTestExpr node contained in the elemexpr.
@@ -3402,9 +3448,10 @@ eval_const_expressions_mutator(Node *node,
 				save_case_val = context->case_val;
 				context->case_val = NULL;

 
@@ -3419,7 +3466,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;
 			}
@@ -3615,7 +3662,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:
@@ -3698,7 +3745,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);
 			}
@@ -3795,7 +3843,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;
 			}
@@ -4125,7 +4173,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:
@@ -5229,7 +5277,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	newexpr->location = -1;

 /*
@@ -5683,10 +5731,13 @@ sql_inline_error_callback(void *arg)
  *
@@ -5711,7 +5762,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 
 	/*
 	 * And evaluate it.
@@ -5731,6 +5782,13 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);

 	n->arg = arg;
 	n->typeName = typename;
+	n->defexpr = NULL;
 	n->location = location;
 	return (Node *) n;
 }
 
+static Node *
 
 			break;

 
 	/*

 		/*
 
 		/*

 
 
 			/*

 	/*

 
 
 	return (Node *) rowexpr;
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 312dfdc182a..fb89fd79814 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@

 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
 
 		if (coerce_hard)
 		{

@@ -2783,20 +2887,199 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 
+		/* Coercion failed, error out if in case of no DEFAULT expression */
+		/* No DEFAULT expression, exit now */
+				? errdetail("Safe type cast for user-defined types are not yet supported.")
+				: errdetail("Explicit cast is defined but definition is not error safe."),
 
-	return result;
+static void
 
 /*
@@ -3212,6 +3495,8 @@ ParseExprKindName(ParseExprKind exprKind)
 
 			return (Node *) fstore;
 		}
 
+/*

 

 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f5b6b45664a..b65a8db0297 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -770,6 +770,44 @@ 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)
+ * 		CAST(expr AS typename NULL ON CONVERSION ERROR)
+ */
+typedef struct SafeTypeCastExpr
+{
+	Expr		xpr;
+
+	/*
+	 * The parsed and transformed source expression. The "castexpr" expression
+	 * may also included this "source" subexpression, so during the planning
+	 * stage, eval_const_expressions must nullify this field to avoid
+	 * erroneous duplication, such as collecting/process aggregate node data
+	 * multiple times. (e.g. preprocess_aggref)
+	 *
+	 * But this is still required for deparsing. In cases like "CAST(1 AS date
+	 * DEFAULT NULL ON CONVERSION ERROR)", the castexpr evaluates to NULL,
+	 * meaning the original query can only be reconstructed using this source
+	 * field.
+	 */
+	Expr	   *source;
+
+	/* transformed cast expression, NULL means cannot coerce to target type */
+	Expr	   *castexpr pg_node_attr(query_jumble_ignore);
+	/* 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;
+
 /*
  * FuncExpr - expression node for a function call
  *
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index e8b409afb7f..b686be049b9 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);

 
 
index 00000000000..f2af774edcf
@@ -0,0 +1,1084 @@
+DETAIL:  Explicit cast is defined but definition is not error safe.
+SELECT CAST(('{' || sum(1 + 2) || '}')::int[] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (2)) sub;
+ {6}
+SELECT CAST(('{' || sum(1 + 2) OVER () || '}')::int[] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (2)) sub;
+ {6}
+ {6}
+DETAIL:  Explicit cast is defined but definition is not error safe.
+DETAIL:  Explicit cast is defined but definition is not error safe.
+DETAIL:  Explicit cast is defined but definition is not error safe.
index 0e69644bca2..28c880b10e2 100644
 
+DETAIL:  Safe type cast for user-defined types are not yet supported.

 
 # ----------
index 00000000000..f4d3acf81bd
@@ -0,0 +1,426 @@
+SELECT CAST(('{' || sum(1 + 2) || '}')::int[] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (2)) sub;
+SELECT CAST(('{' || sum(1 + 2) OVER () || '}')::int[] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (2)) sub;

 
+-- int8alias2 binary-coercible to int8, this should not fail
+select cast('1'::int8 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/sqljson_queryfuncs.sql b/src/test/regress/sql/sqljson_queryfuncs.sql
index a5d5e256d7f..dd5afd14490 100644
--- a/src/test/regress/sql/sqljson_queryfuncs.sql
+++ b/src/test/regress/sql/sqljson_queryfuncs.sql
@@ -66,6 +66,7 @@ SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int);
 SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR);
 SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR);
 SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234;
+SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234;
 
 SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9;

 SELECT * FROM xmltest;
 
index e3c1007abdf..835499130ae 100644
@@ -2741,6 +2741,8 @@ STRLEN

From 4210ab408627c60160fae4c55b2b31f874af9518 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 30 Mar 2026 22:28:58 +0800
Subject: [PATCH v26 1/2] error safe for casting text to other types per
 pg_cast

Author: jian he <[email-masked]>
Reviewed-by: Andrew Dunstan <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/catalog/namespace.c | 46 ++++++++++++++++++++++++++-------
 src/backend/utils/adt/regproc.c | 13 +++++++---
 src/backend/utils/adt/varlena.c | 10 +++++--
 src/include/catalog/namespace.h |  6 +++++
 src/include/utils/varlena.h     |  1 +
 5 files changed, 62 insertions(+), 14 deletions(-)

index 56b87d878e8..0f63c826e80 100644
@@ -595,14 +605,20 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 
+								relation->relname)));
 
@@ -628,14 +644,20 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	{
 		int			elevel = missing_ok ? DEBUG1 : ERROR;
 
+							relation->relname)));

 

 
 	if (namelist == NIL)
-		ereport(ERROR,

---

From: jian he <[email-masked]>
Date: Tue, 31 Mar 2026 15:23:40 +0800

hi.

src4=# explain (verbose, costs off) SELECT CAST('a' AS int DEFAULT 2
ON CONVERSION ERROR);
                        QUERY PLAN
-----------------------------------------------------------
 Result
   Output: CAST( AS integer DEFAULT 2 ON CONVERSION ERROR)
 Query Identifier: 2101562107364949876
(3 rows)

The above v26 output is not good, the attached file fixes this issue.
I also polished the comments and more regression tests.

The main big change compare to v26 is in preprocess_aggrefs_walker.
--- a/src/backend/optimizer/prep/prepagg.c
+++ b/src/backend/optimizer/prep/prepagg.c
@@ -358,6 +358,17 @@ preprocess_aggrefs_walker(Node *node, PlannerInfo *root)
          */
         return false;
     }
+    if (IsA(node, SafeTypeCastExpr))
+    {
+        SafeTypeCastExpr *castexpr = (SafeTypeCastExpr *) node;
+
+        /*
+         * "source" may also contain an Aggref node, but since it is kept only
+         * for deparsing purposes, we must not recurse into it when processing
+         * aggregate nodes here.
+         */
+        return expression_tree_walker((Node *) castexpr->castexpr,
preprocess_aggrefs_walker, root);
+    }
     Assert(!IsA(node, SubLink));
     return expression_tree_walker(node, preprocess_aggrefs_walker, root);
 }

Subject: [PATCH v27 1/2] error safe for casting text to other types per
 pg_cast

 

 

 

 	if (namelist == NIL)
-		ereport(ERROR,

From 1cf1359aefd9a6e84da0e74b70f09a8d98b9066c Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 31 Mar 2026 15:13:16 +0800
Subject: [PATCH v27 2/2] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Example:

 src/backend/optimizer/prep/prepagg.c          |   11 +
 src/backend/optimizer/util/clauses.c          |   98 +-
 src/backend/parser/parse_coerce.c             |  142 +-
 src/backend/parser/parse_expr.c               |  353 ++++-
 src/test/regress/expected/cast.out            | 1172 +++++++++++++++++
 src/test/regress/expected/equivclass.out      |   19 +
 src/test/regress/sql/cast.sql                 |  442 +++++++
 src/test/regress/sql/equivclass.sql           |    5 +
 43 files changed, 2630 insertions(+), 94 deletions(-)

 

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
 
    <para>
 
index 77229141b38..84a48e8e3f6 100644

 

 
+/*
+		/* evaluate DEFAULT expression using the prior state->escontext */

 /*
 
diff --git a/src/backend/optimizer/prep/prepagg.c b/src/backend/optimizer/prep/prepagg.c
index 3737cc15ba1..0f2494147be 100644
--- a/src/backend/optimizer/prep/prepagg.c
+++ b/src/backend/optimizer/prep/prepagg.c
@@ -358,6 +358,17 @@ preprocess_aggrefs_walker(Node *node, PlannerInfo *root)
 		return false;
+	if (IsA(node, SafeTypeCastExpr))
+		SafeTypeCastExpr *castexpr = (SafeTypeCastExpr *) node;
+		 * "source" may also contain an Aggref node, but since it is kept only
+		 * for deparsing purposes, we must not recurse into it when processing
+		 * aggregate nodes here.
+		return expression_tree_walker((Node *) castexpr->castexpr, preprocess_aggrefs_walker, root);
 	Assert(!IsA(node, SubLink));
 	return expression_tree_walker(node, preprocess_aggrefs_walker, root);
index 9fb266d089d..d60e9c84b97 100644
 
 typedef struct

 
 
 /*

 				return (Node *) expr;
 			}
@@ -3267,6 +3275,43 @@ eval_const_expressions_mutator(Node *node,
 
+				newexpr->source = stc->source;
 
@@ -3384,6 +3429,7 @@ eval_const_expressions_mutator(Node *node,
 			{
 				ArrayCoerceExpr *ac = makeNode(ArrayCoerceExpr);
 				Node	   *save_case_val;
+				Expr	   *simple = NULL;
 
 				/*
@@ -3391,9 +3437,10 @@ eval_const_expressions_mutator(Node *node,
 
 				/*
 				 * Set up for the CaseTestExpr node contained in the elemexpr.
@@ -3402,9 +3449,10 @@ eval_const_expressions_mutator(Node *node,
 				save_case_val = context->case_val;
 				context->case_val = NULL;

 
@@ -3419,7 +3467,7 @@ eval_const_expressions_mutator(Node *node,
 
 				return (Node *) ac;
 			}
@@ -3615,7 +3663,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:
@@ -3698,7 +3746,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);
 			}
@@ -3795,7 +3844,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;
 			}
@@ -4125,7 +4174,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:
@@ -4525,6 +4574,9 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 								args, funcvariadic,
 								func_tuple, context);
 
+	if (SOFT_ERROR_OCCURRED(context->escontext) && newexpr != NULL)
+		elog(ERROR, "evaluate_function failed, but newexpr is not NULL");
+
 	if (!newexpr && allow_non_const && OidIsValid(func_form->prosupport))
 	{
 		/*
@@ -5229,7 +5281,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	newexpr->location = -1;

 /*
@@ -5683,10 +5735,13 @@ sql_inline_error_callback(void *arg)
  *
@@ -5711,7 +5766,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 
 	/*
 	 * And evaluate it.
@@ -5731,6 +5786,13 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);

 	n->arg = arg;
 	n->typeName = typename;
+	n->defexpr = NULL;
 	n->location = location;
 	return (Node *) n;
 }
 
+static Node *
 
 			break;

 
 	/*

 		/*
 
 		/*

 
 
 			/*

 	/*

 
 
 	return (Node *) rowexpr;
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 312dfdc182a..83adc598565 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@

 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
+ * CAST(... DEFAULT ... ON CONVERSION ERROR). When provided,
+ * ErrorSaveContext->error_occurred should be initialized to false. It is set
@@ -2157,24 +2177,55 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
 		if (coerce_hard)
 		{
@@ -2723,17 +2774,66 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,

@@ -2762,7 +2862,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
@@ -2783,20 +2884,202 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 
+		 * No DEFAULT expression, exit now or error out in case of coercion
+		 * failure
+			if (castexpr)
 
-	return result;
+static void
 
 /*
 
 			return (Node *) fstore;
 		}
 
+/*
 
index 7bc12589e40..f68977fc29e 100644
 
+				 * a Constant. Instead, we can use "source" and "defexpr" to

 
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f5b6b45664a..2a22e646045 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -770,6 +770,39 @@ typedef enum CoercionForm
 	COERCE_SQL_SYNTAX,			/* display with SQL-mandated special syntax */
 } CoercionForm;
 
+/*
+ * SafeTypeCastExpr -
+	 * The parsed and transformed source expression.
+	 * This is mainly used for deparsing. In cases like "CAST(1 AS date
+	 * DEFAULT NULL ON CONVERSION ERROR)", where the castexpr evaluates to
+	 * NULL, we need this field to reconstruct the original query.

 
 
index 00000000000..e5740d2f5bc
@@ -0,0 +1,1172 @@
+-- test nested ArrayCoerceExpr constant folding
+-- safe cast with money data type
+-- cast from money to other type is not error safe
+-- 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);
+    0
+SELECT CAST(false AS text DEFAULT NULL ON CONVERSION ERROR);
+ false
+-- safe cast related to network address data type
+-- safe cast related to oid data type
+SELECT CAST(12345678901::bigint AS oid DEFAULT NULL ON CONVERSION ERROR);
+ oid 
+-----
+SELECT CAST(12345678901::bigint AS regproc DEFAULT NULL ON CONVERSION ERROR);
+ regproc 
+SELECT CAST(12345678901::bigint AS regprocedure DEFAULT NULL ON CONVERSION ERROR);
+ regprocedure 
+SELECT CAST(12345678901::bigint AS regclass DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1234567890::bigint AS oid DEFAULT NULL ON CONVERSION ERROR);
+    oid     
+ 1234567890
+SELECT CAST(1234567890::bigint AS regrole DEFAULT NULL ON CONVERSION ERROR);
+  regrole   
+ 1234567890
+SELECT CAST(1234567890::bigint AS regnamespace DEFAULT NULL ON CONVERSION ERROR);
+ regnamespace 
+ 1234567890
+SELECT CAST(1234567890::bigint AS regdatabase DEFAULT NULL ON CONVERSION ERROR);
+ regdatabase 
+ 1234567890
+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"');
+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;
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); -- error
+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
+CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT NULL ON CONVERSION ERROR))); -- ok
+ CREATE INDEX cast_error_idx ON public.test_safecast0 USING btree ((CAST(col1 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+EXPLAIN (VERBOSE, COSTS OFF) SELECT CAST('1' || 'a' AS int DEFAULT 2 ON CONVERSION ERROR);
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Result
+   Output: CAST(('1'::text || 'a'::text) AS integer DEFAULT 2 ON CONVERSION ERROR)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Result
+   Output: CAST('a' AS integer DEFAULT 2 ON CONVERSION ERROR)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr DEFAULT NULL ON CONVERSION ERROR);
+                                                                         QUERY PLAN                                                                          
+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Result
+   Output: CAST(ARRAY[ARRAY[1, 2, (1.1)::integer], ARRAY['three', true, '01'::"bit"]] AS d_int_arr DEFAULT (NULL::integer[])::d_int_arr ON CONVERSION ERROR)
+DROP TABLE test_safecast0;
 
index ad8ab294ff6..3af737c220a 100644
@@ -95,6 +95,25 @@ create function int8alias1cmp(int8, int8alias1) returns int
+select cast('1'::int8alias2 as int8 default null on conversion error);
+select cast('a' as int8alias2 default null on conversion error);

+SELECT CAST('<wrong'::text AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
 -- test non-throwing API, too
 SELECT pg_input_is_valid('<value>one</value>', 'xml');
 ERROR:  unsupported XML feature
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 734da057c34..fdacfeae8ec 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
index 00000000000..0e28d173e94
@@ -0,0 +1,442 @@
+-- test nested ArrayCoerceExpr constant folding
+-- safe cast with money data type
+-- cast from money to other type is not error safe
+-- 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 related to network address data type
+-- 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);
+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"');
+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;
+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
+EXPLAIN (VERBOSE, COSTS OFF) SELECT CAST('1' || 'a' AS int DEFAULT 2 ON CONVERSION ERROR);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+EXPLAIN (VERBOSE, COSTS OFF) SELECT CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr DEFAULT NULL ON CONVERSION ERROR);
+DROP TABLE test_safecast0;

 -- 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);
 
+select cast('1'::int8alias2 as int8 default null on conversion error);
+select cast('a' as int8alias2 default null on conversion error);
 
 SELECT * FROM xmltest;

---

From: jian he <[email-masked]>
Date: Tue, 31 Mar 2026 22:47:37 +0800

Hi.
I've incorporated the changes you sent me off-list.
The attached v1 no-cfbot is your proposed change.
I also did some minor comments adjustments.

---

From: Corey Huinker <[email-masked]>
Date: Tue, 31 Mar 2026 16:02:22 -0400

> I also did some minor comments adjustments.

The remaining patches continue to work for me. I hope we can get all of
this into 19, but if we can't I think it would be ok to limit src/dest type
combinations to those that can be handled by CoerceViaIO, at least for 19.
I think the vast majority of the usage of this function will be mass ETLs
where data is first loaded into a table of all text.

---

From: jian he <[email-masked]>
Date: Wed, 1 Apr 2026 15:23:07 +0800

One more issue I found:
https://git.postgresql.org/cgit/postgresql.git/commit/?id=74c96699be3f53c058c8794c07952221b9625b4f
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING char(2)  DEFAULT '011' ON ERROR);
ERROR:  value too long for type character(2)

Similarly, we can
SELECT CAST(text '1234' as char(2) DEFAULT '111111' ON conversion ERROR);
ERROR:  value too long for type character(2)

Composite types respect typmod, for example:
CREATE TYPE comp AS (a char(3), b int);
SELECT CAST('(14,42)' AS comp DEFAULT '(1234,2)' ON CONVERSION ERROR); -- error

The regression tests are too large; we can order them by the cast
source type's pg_type.type category,
so we won't miss any tests.

What do you think?

---

From: Corey Huinker <[email-masked]>
Date: Wed, 1 Apr 2026 16:07:49 -0400

> ERROR:  value too long for type character(2)

Both of these are doomed when we hit the default, so I don't see why we
should make any accommodation for them.

> SELECT CAST('(14,42)' AS comp DEFAULT '(1234,2)' ON CONVERSION ERROR); --

This one is trickier, but I'd be willing to reject composite inputs in v19
and solve it in v20.

> so we won't miss any tests.

We can always remove redundant tests later.

---

From: Vik Fearing <[email-masked]>
Date: Thu, 2 Apr 2026 18:03:28 +0200

On 31/03/2026 22:02, Corey Huinker wrote:
> The remaining patches continue to work for me. I hope we can get all 
> of this into 19, but if we can't I think it would be ok to limit 
> src/dest type combinations to those that can be handled by 
> CoerceViaIO, at least for 19. I think the vast majority of the usage 
> of this function will be mass ETLs where data is first loaded into a 
> table of all text.

Did you see on Discord that I am submitting a bug fix for this paper? I 
originally took the syntax from json but it special-cases NULL and can't 
handle <empty specification>, so I want to remove `NULL ON CONVERSION 
ERROR` and switch the DEFAULT clause to <implicitly typed value 
specification>.

I am attaching it here in case you missed it.

-- 

Vik Fearing

---

From: jian he <[email-masked]>
Date: Fri, 3 Apr 2026 11:14:57 +0800

On Fri, Apr 3, 2026 at 12:03 AM Vik Fearing <[email-masked]> wrote:
>
> Did you see on Discord that I am submitting a bug fix for this paper? I originally took the syntax from json but it special-cases NULL and can't handle <empty specification>, so I want to remove `NULL ON CONVERSION ERROR` and switch the DEFAULT clause to <implicitly typed value specification>.
>

The attached v29 removed `NULL ON CONVERSION ERROR`.
Other than that, everything is the same as v28.

--
jian
https://www.enterprisedb.com/
From 7a62f3c96ef9fa7fd3a1f8c3334f9ef7508947a0 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 30 Mar 2026 22:28:58 +0800
Subject: [PATCH v29 1/2] error safe for casting text to other types per
 pg_cast

 

 
index 64f293f4e98..ad7a4d52d28 100644

 
index ecad6d62184..cc21312eb5b 100644

 	if (namelist == NIL)
-		ereport(ERROR,

From a338b5fd5e416e0ba41fd70231635132eeaa2195 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Fri, 3 Apr 2026 11:13:23 +0800
Subject: [PATCH v29 2/2] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Example:

 doc/src/sgml/syntax.sgml                      |   39 +
 src/backend/parser/gram.y                     |   21 +
 src/backend/parser/parse_expr.c               |  355 ++++-
 src/include/nodes/primnodes.h                 |   32 +
 43 files changed, 2636 insertions(+), 94 deletions(-)

index 8c0bf54f0f3..69649c2c5e2 100644
 
+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
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+                    ^
+DETAIL:  Safe type casts for user-defined types are not yet supported.
 -- 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..896939d35ce 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
 --------+---------
 (0 rows)
 
+ERROR:  cannot cast type character to citext with DEFAULT expression in CAST ... ON CONVERSION ERROR
+DETAIL:  Safe type casts for user-defined types are not yet supported.

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric(10) DEFAULT 2 ON CONVERSION ERROR);
+
 -- DISTINCT and ORDER BY patterns
 -- Try some query permutations which once produced identical query IDs
 SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 67482996861..86ca9384962 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2125,6 +2125,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 may occasionally fail at run time.  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>
+     With <literal>DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR</literal>,
+     the default <replaceable>expression</replaceable> is evaluated and
+     returned instead.  The <literal>ERROR ON CONVERSION ERROR</literal>
+     form raises an error on failure, which is also the default behavior
+     when no <literal>ON CONVERSION ERROR</literal> clause is specified.
+    </para>
+
+    <para>
+     At present, this only supports built-in type casts listed in
+     <xref linkend="catalog-pg-cast"/>.  User-defined type casts created
+     with <link linkend="sql-createcast">CREATE CAST</link> are not
+     supported.
+    </para>
+
+    <para>
+     Some examples:
+<screen>
+SELECT CAST('not-a-date' AS date DEFAULT NULL ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>NULL</computeroutput>
+SELECT CAST('not-a-date' AS date DEFAULT '1970-01-01' ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1970-01-01</computeroutput>
+SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
+</screen>
+    </para>
+   </sect3>
   </sect2>
 
index 77229141b38..59482fab56c 100644

 

 
+/*
+	 * If there's no cast expression (castexpr == NULL), only the DEFAULT
+	 * expression needs to be evaluated; set it up and return early.
index 3c4843cde86..653e8687c04 100644

 /*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 0e160b8502c..af93dd084ab 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2258,6 +2258,62 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
index c0b880ec233..03f09a32ee9 100644
@@ -3384,6 +3410,19 @@ expression_tree_mutator_impl(Node *node,
@@ -4537,6 +4576,20 @@ raw_expression_tree_walker_impl(Node *node,
 
 typedef struct

 
 
 /*

 				return (Node *) expr;
 			}

 
 				/*
 
 				/*

 
 
 				return (Node *) ac;
 			}

 
 
 /*
 
 	/*
 	 * And evaluate it.
 
index f4a08baa95a..2a5cd8088ec 100644
@@ -16784,6 +16786,10 @@ func_expr_common_subexpr:
@@ -19947,10 +19953,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 *
index acb933392de..7c0d490bff2 100644
 
 			break;
 
@@ -999,6 +1005,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,

 	/*

 		/*
 
 		/*

 
 
 			/*

 	/*

 
 
 	return (Node *) rowexpr;
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f535f3b9351..5b83ab0b644 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@

 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
@@ -1849,6 +1858,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -2042,10 +2054,15 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -2076,9 +2093,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
@@ -2119,6 +2137,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
@@ -2169,24 +2189,55 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 
 		if (coerce_hard)
 		{
@@ -2735,17 +2786,67 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,

+		escontext = makeNode(ErrorSaveContext);
+										COERCION_EXPLICIT, COERCE_EXPLICIT_CAST,
@@ -2774,7 +2875,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
@@ -2795,20 +2897,203 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 
+				errmsg("cannot cast type %s to %s with DEFAULT expression in CAST ... ON CONVERSION ERROR",
+				? errdetail("Safe type casts for user-defined types are not yet supported.")
 
-	return result;
+static void
+	 * Casts from MONEY source type are not error safe.
 
 /*
@@ -3224,6 +3509,8 @@ ParseExprKindName(ParseExprKind exprKind)
index 35ff6427147..f0fc60419cc 100644
 
 			return (Node *) fstore;
 		}
 
+/*
index 2933a95c4a5..017102790ab 100644
 
+		/* 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 aec5556b008..48bfc9cee36 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -11114,6 +11114,31 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;

 
index 090cfccf65f..7626dc072ba 100644
@@ -1093,6 +1093,27 @@ typedef struct DomainConstraintState
 
index 91377a6cde3..3018234cc04 100644
@@ -400,6 +400,7 @@ typedef struct TypeCast
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfc946c20b..7e6f7b06992 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -770,6 +770,38 @@ typedef enum CoercionForm
 	COERCE_SQL_SYNTAX,			/* display with SQL-mandated special syntax */
 } CoercionForm;
 
+/*
+ * SafeTypeCastExpr -
+	 * This is used for deparsing only. In cases like: ``CAST(1 AS date
+	 * DEFAULT NULL ON CONVERSION ERROR)`` where the castexpr evaluates to

 
index f7f4ba6c2a8..472567696d0 100644
@@ -68,6 +68,8 @@ typedef enum ParseExprKind
 
index 00000000000..cbb7c02f409
+VALUES (CAST('error' AS integer DEFAULT NULL ON CONVERSION ERROR));
+ERROR:  cannot cast type record to tcast0 with DEFAULT expression in CAST ... ON CONVERSION ERROR
+ERROR:  cannot cast type record to comp_domain_with_typmod with DEFAULT expression in CAST ... ON CONVERSION ERROR
+ERROR:  cannot cast type money to numeric with DEFAULT expression in CAST ... ON CONVERSION ERROR
+ERROR:  cannot cast type money[] to numeric[] with DEFAULT expression in CAST ... ON CONVERSION ERROR
index 0e69644bca2..64ac60b157f 100644
 
+ERROR:  cannot cast type integer to casttesttype with DEFAULT expression in CAST ... ON CONVERSION ERROR
+DETAIL:  Safe type casts for user-defined types are not yet supported.

index 3a044ffd8bf..36aa23201e8 100644
 
 # ----------
index 00000000000..71385430955
+VALUES (CAST('error' AS integer DEFAULT NULL ON CONVERSION ERROR));

 
 
 SELECT * FROM xmltest;
 
index 5bc517602b1..7c81be5f7c2 100644
@@ -2745,6 +2745,8 @@ STRLEN

---

From: Corey Huinker <[email-masked]>
Date: Fri, 3 Apr 2026 15:09:27 -0400

> Other than that, everything is the same as v28.

Indeed it is, and everything still checks out.

---

From: jian he <[email-masked]>
Date: Tue, 23 Jun 2026 10:50:25 +0800

Hi,

In subquery_planner -> preprocess_expression ->
eval_const_expressions, we attempt
to evaluate certain constant expressions in an error-safe manner by introducing
an ErrorSaveContext in eval_const_expressions_context. However, this may
introduce consistency issues, since not all functions are error-safe.

Consider the following two queries:
SELECT CAST(65536 AS int2 DEFAULT NULL ON CONVERSION ERROR);
SELECT CAST(65536/0 AS int2 DEFAULT NULL ON CONVERSION ERROR);
ERROR:  division by zero

The first query returns NULL, while the second throws an error. Should we accept
this inconsistency, or make the first query error out too?
Similarly, should we reject uncastable CASTs, like `CAST(NULL::date AS int2)`?

More broadly, the patch involved significant effort (refactoring) in
parsing (transformTypeCast),
planning (eval_const_expressions) to ensure expressions do not fail.
These efforts cannot ensure that all operations are error-safe,
therefore there will be consistency issues.
----------------------------------------------------------------
[1] explains why SQL-language functions are inherently not error-safe.

This raises the question: how should we handle user-defined cast functions in
CAST(expr AS target_type DEFAULT defexpr ON CONVERSION ERROR)?

There are three options:
Option 1: Disallow them entirely.
Reject any cast expression backed by a user-defined function at parse time.

Option 2: Allow them with documented caveats.
Permit user-defined cast functions, but explicitly document the limitations:
- SQL-language functions are inherently incapable of error-safe execution.
- C-language functions must ensure that all their subroutines are also
error-safe.
- It is the user's responsibility to ensure the cast function is error-safe; if
it is not, errors will propagate as normal rather than being caught by the
DEFAULT clause.

Option 2 means that CAST(expr AS target_type DEFAULT defexpr ON
CONVERSION ERROR) does not check if the underlying cast function
actually supports error-safe operations.

Option 3: Disallow SQL-language functions, but allow C-language and
Internal-language functions.
Also document that you must ensure the C function is error-safe if you
want it to catch the error.

Currently, we are using Option 1.
Anyway, I rebased the patch and added comments in a few places that I
realized were necessary in hindsight.

[1]: https://postgr.es/m/[email-masked]

--
jian
https://www.enterprisedb.com/
From 88967acc1618f79485532f2531c593e7116b8811 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 18 Jun 2026 20:09:01 +0800
Subject: [PATCH v30 1/2] error safe for casting text to other types per
 pg_cast

 

 

 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 0c6d3ba4d22..c3263ad994c 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2720,6 +2720,12 @@ name_text(PG_FUNCTION_ARGS)
  */
 List *
 textToQualifiedNameList(text *textval)
+{
+	return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
 {
 	char	   *rawname;
 	List	   *result = NIL;
@@ -2731,12 +2737,12 @@ textToQualifiedNameList(text *textval)
 	rawname = text_to_cstring(textval);

 	if (namelist == NIL)
-		ereport(ERROR,

diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index fe8d8a58952..c4d4292eb1c 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int	varstr_levenshtein_less_equal(const char *source, int slen,
 										  int ins_c, int del_c, int sub_c,
 										  int max_d, bool trusted);
 extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
 extern char *scan_quoted_identifier(char **endp, char **nextp);
 extern char *scan_identifier(char **endp, char **nextp, char separator,
 							 bool downcase_unquoted);
-- 
2.34.1

From 270d4e9533ef21324dded6136f7e27959d3a4c52 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 23 Jun 2026 10:41:20 +0800
Subject: [PATCH v30 2/2] CAST(expr AS newtype DEFAULT expr ON CONVERSION
 ERROR)

Author: jian he <[email-masked]>
Reviewed-by: Amul Sul <[email-masked]>
Reviewed-by: Andrew Dunstan <[email-masked]>
Reviewed-by: Corey Huinker <[email-masked]>
Reviewed-by: Kirill Reshke <[email-masked]>
Reviewed-by: Matheus Alcantara <[email-masked]>
Discussion: https://postgr.es/m/CADkLM=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 contrib/citext/expected/citext.out            |    5 +
 contrib/citext/expected/citext_1.out          |    5 +
 contrib/citext/sql/citext.sql                 |    2 +
 .../pg_stat_statements/expected/select.out    |   24 +-
 contrib/pg_stat_statements/sql/select.sql     |    5 +
 doc/src/sgml/syntax.sgml                      |   39 +
 src/backend/executor/execExpr.c               |   97 +-
 src/backend/executor/execExprInterp.c         |   35 +
 src/backend/jit/llvm/llvmjit_expr.c           |   56 +
 src/backend/nodes/nodeFuncs.c                 |   53 +
 src/backend/optimizer/prep/prepagg.c          |   15 +
 src/backend/optimizer/util/clauses.c          |  101 +-
 src/backend/parser/gram.y                     |   21 +
 src/backend/parser/parse_agg.c                |    9 +
 src/backend/parser/parse_coerce.c             |  142 +-
 src/backend/parser/parse_expr.c               |  370 ++++-
 src/backend/parser/parse_func.c               |    3 +
 src/backend/parser/parse_target.c             |    3 +-
 src/backend/parser/parse_type.c               |   22 +
 src/backend/parser/parse_utilcmd.c            |    2 +-
 src/backend/utils/adt/arrayfuncs.c            |    9 +
 src/backend/utils/adt/ruleutils.c             |   25 +
 src/include/executor/execExpr.h               |    7 +
 src/include/executor/executor.h               |    2 +
 src/include/nodes/execnodes.h                 |   21 +
 src/include/nodes/parsenodes.h                |    1 +
 src/include/nodes/primnodes.h                 |   34 +
 src/include/optimizer/optimizer.h             |    2 +-
 src/include/parser/parse_coerce.h             |   16 +-
 src/include/parser/parse_node.h               |    2 +
 src/include/parser/parse_type.h               |    2 +
 src/test/regress/expected/cast.out            | 1185 +++++++++++++++++
 src/test/regress/expected/create_cast.out     |    5 +
 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                 |  453 +++++++
 src/test/regress/sql/create_cast.sql          |    1 +
 src/test/regress/sql/equivclass.sql           |    5 +
 src/test/regress/sql/xml.sql                  |    2 +
 src/tools/pgindent/typedefs.list              |    2 +
 41 files changed, 2715 insertions(+), 95 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

 

 -- Test = and <>.
 
+-- error safe type cast

+-- error safe type cast
 
   <sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index cfea7e160c2..327cb54444c 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -99,6 +99,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
 static void ExecInitJsonExpr(JsonExpr *jsexpr, 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 +144,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) 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 +177,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);
@@ -763,6 +787,18 @@ ExecBuildUpdateProjection(List *targetList,
  */
 ExprState *
 ExecPrepareExpr(Expr *node, EState *estate)
+{
+	return ExecPrepareExprWithContext(node, estate, NULL);
+}
+
+/*
+ * ExecPrepareExprWithContext: same as ExecPrepareExpr, but with an optional
+ * ErrorSaveContext for soft error handling.
+ *
+ * See ExecInitExprWithContext for details on the escontext parameter.
+ */
+ExprState *
+ExecPrepareExprWithContext(Expr *node, EState *estate, Node *escontext)
 {
 	ExprState  *result;
 	MemoryContext oldcontext;
@@ -771,7 +807,7 @@ ExecPrepareExpr(Expr *node, EState *estate)
 
 	node = expression_planner(node);
 
-	result = ExecInitExpr(node, NULL);
+	result = ExecInitExprWithContext(node, NULL, escontext);

@@ -1701,6 +1737,7 @@ ExecInitExprRec(Expr *node, ExprState *state,

 				ExecInitExprRec(acoerce->elemexpr, elemstate,
 								&elemstate->resvalue, &elemstate->resnull);
@@ -2176,6 +2213,16 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				ExecInitSafeTypeCastExpr(stcexpr, state, resv, resnull,
+										 &scratch);
+
+				break;
+			}
+
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesce = (CoalesceExpr *) node;
@@ -2736,7 +2783,8 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,

 	/* Keep extra copies of this info to save an indirection at runtime */
 	scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4737,6 +4785,51 @@ ExecBuildParamSetEqual(TupleDesc desc,
 	return state;
 }
 
+/*
+		stcstate->escontext.error_data = NULL;
index 0634af964a9..ed231d737c9 100644
@@ -578,6 +578,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
@@ -1936,6 +1937,28 @@ 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))
+				EEO_JUMP(stcstate->jump_end);
+			else
+			{
+				*op->resvalue = (Datum) 0;
+				*op->resnull = true;
+
+				/*
+				 * Type cast error occurred. Reset the ErrorSaveContext so
+				 * it's ready for the next coercion evaluation attempt.
+				 */
+				stcstate->escontext.error_occurred = false;
+				stcstate->escontext.details_wanted = false;
+
+				EEO_NEXT();
+			}
+		}
+
 		EEO_CASE(EEOP_JSONEXPR_PATH)
 		{
 			/* too complex for an inline implementation */
@@ -3654,6 +3677,18 @@ 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.
+		 * Currently, EEOP_SAFETYPE_CAST will do that.
+		 */
+	}
 }
 
 /*
 
index 2a2e00b372e..d46ef1a87e4 100644
@@ -1248,6 +1256,9 @@ exprSetCollation(Node *expr, Oid collation)
@@ -1569,6 +1580,9 @@ exprLocation(const Node *expr)
@@ -2345,6 +2359,18 @@ expression_tree_walker_impl(Node *node,
@@ -3404,6 +3430,19 @@ expression_tree_mutator_impl(Node *node,
@@ -4581,6 +4620,20 @@ raw_expression_tree_walker_impl(Node *node,
index 3737cc15ba1..beec75112db 100644
@@ -358,6 +358,21 @@ preprocess_aggrefs_walker(Node *node, PlannerInfo *root)
+		 * SafeTypeCastExpr->source may also contain an Aggref node, but since
+		 * it is kept only for deparsing purposes, we must not recurse into it
+		 * when processing aggregate nodes here.
+		 * We already prevent defexpr from containing aggregate functions.
+									  preprocess_aggrefs_walker,
+									  root);
index 01997e22266..390205db2ea 100644
@@ -71,6 +71,7 @@ typedef struct
 
 typedef struct
@@ -2494,6 +2495,10 @@ 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: If eval_const_expressions_context->escontext is not NULL, the
+ * expression is evaluated in an error-safe manner. In case of failure, it
+ * may return NULL instead of throwing an error.
  *--------------------
  */
 Node *
@@ -2509,6 +2514,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);
 }
 
@@ -2651,6 +2657,7 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 
@@ -2676,11 +2683,12 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 	(!expression_tree_walker((Node *) (node), contain_non_const_walker, NULL))

 /*
  * Recursive guts of eval_const_expressions/estimate_expression_value
@@ -3141,7 +3149,7 @@ eval_const_expressions_mutator(Node *node,

 				return (Node *) expr;
 			}
@@ -3161,7 +3169,7 @@ eval_const_expressions_mutator(Node *node,
 				 */
@@ -3285,6 +3293,42 @@ eval_const_expressions_mutator(Node *node,
 														  context);
+				newexpr->location = stc->location;
 
@@ -3402,6 +3446,7 @@ eval_const_expressions_mutator(Node *node,
 			{
 				ArrayCoerceExpr *ac = makeNode(ArrayCoerceExpr);
 				Node	   *save_case_val;
+				Expr	   *simple = NULL;
 
 				/*
@@ -3409,9 +3454,10 @@ eval_const_expressions_mutator(Node *node,
 
 				/*
 				 * Set up for the CaseTestExpr node contained in the elemexpr.
@@ -3420,9 +3466,10 @@ eval_const_expressions_mutator(Node *node,
 				save_case_val = context->case_val;
 				context->case_val = NULL;

 
@@ -3437,7 +3484,12 @@ 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);
+				{
+					simple =  (Expr *) ece_evaluate_expr(ac, context->escontext);
+
+					if (simple)
+						return (Node *) simple;
+				}
 
 				return (Node *) ac;
 			}
@@ -3633,7 +3685,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:
@@ -3716,7 +3768,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);
 			}
@@ -3813,7 +3866,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;
 			}
@@ -4143,7 +4196,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:
@@ -4543,6 +4596,8 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 								args, funcvariadic,
 								func_tuple, context);
 
+	Assert(!(SOFT_ERROR_OCCURRED(context->escontext) && newexpr != NULL));
+
 	if (!newexpr && allow_non_const && OidIsValid(func_form->prosupport))
 	{
 		/*
@@ -5268,7 +5323,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
 	newexpr->location = -1;

 /*
@@ -5722,10 +5777,13 @@ sql_inline_error_callback(void *arg)
  *
@@ -5750,7 +5808,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 
 	/*
 	 * And evaluate it.
@@ -5770,6 +5828,13 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	/* Get back to outer memory context */
 	MemoryContextSwitchTo(oldcontext);
 
index ff4e1388c55..47f81ced4c6 100644
@@ -16787,6 +16789,10 @@ func_expr_common_subexpr:
@@ -19950,10 +19956,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 *
 
 			break;
 
index 3b92ae2a920..adef374c764 100644

 	/*

 		/*
 
 		/*

 
 
 			/*

 	/*

 
@@ -1139,7 +1209,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 								baseTypeId, baseTypeMod,
 								targetTypeId,
 								ccontext, cformat, location,
-								false);
+								false,
+								NULL);
 	}
 
 	return (Node *) rowexpr;
@@ -1294,7 +1365,8 @@ coerce_null_to_domain(Oid typid, int32 typmod, Oid collation,
 								  COERCION_IMPLICIT,
 								  COERCE_IMPLICIT_CAST,
 								  -1,
-								  false);
+								  false,
+								  NULL);
 	return result;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 9adc9d4c0f6..af175b0e8bf 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@

 		case T_A_ArrayExpr:
 
 		case T_TypeCast:
+ * the error_occurred field should be initialized to false. It is set
 
 		if (coerce_hard)
 		{
+					 * element to the list. Also set error_occurred to true
+					 * (in case coerce_to_target_type_extended didn't) so
+					 * caller will notice array coercion failed.

+									  (Node *) escontext);
@@ -2795,20 +2897,218 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 
+	 * coerce_to_target_type_extended may modify the source expression, so we
+	 * still create a copy beforehand. This allows SafeTypeCastExpr to receive
+	 * the transformed source expression unchanged.
+												  (Node *) escontext);
+ * Check whether a type coercion is error-safe. If not, report an error.
 
-	return result;
+/*
+ * Recursion is required here because type casts may involve arrays over domains
+ * or domains over arrays, so we need resurse to the base element type for both
+ * the input and target types.
+ */
+static void
+CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
+						   bool *errorsafe_coercion, bool *userdefined)
+{
+	char		input_typtype = get_typtype(inputType);
+	char		target_typtype = get_typtype(targetType);
+	HeapTuple	tuple;
+
+	Assert(errorsafe_coercion != NULL);
+	Assert(userdefined != NULL);
+
+	if (!(*errorsafe_coercion))
+		return;
+
+	if (input_typtype == TYPTYPE_DOMAIN &&
+		target_typtype == TYPTYPE_DOMAIN)
+	{
+		CoercionErrorSafe_Internal(getBaseType(inputType),
+								   getBaseType(targetType),
+								   errorsafe_coercion,
+								   userdefined);
+		return;
+	}
+	else if (input_typtype == TYPTYPE_DOMAIN)
+	{
+		CoercionErrorSafe_Internal(getBaseType(inputType),
+								   targetType,
+								   errorsafe_coercion,
+								   userdefined);
+		return;
+	}
+	else if (target_typtype == TYPTYPE_DOMAIN)
+	{
+		CoercionErrorSafe_Internal(inputType,
+								   getBaseType(targetType),
+								   errorsafe_coercion,
+								   userdefined);
+		return;
+	}
+	else if ((input_typtype != TYPTYPE_BASE && inputType > FirstUnpinnedObjectId)
+			 || (target_typtype != TYPTYPE_BASE && targetType > FirstUnpinnedObjectId))
+	{
+		/*
+		 * Composite-to-composite casting is not implemented, so error-safe
+		 * casting between composite types is not possible and is forbidden
+		 * here.
+		 *
+		 * Additionally, our type system does not automatically cast a
+		 * user-defined range type to a built-in range type, even when their
+		 * base element types match. Due to potential edge cases, error-safe
+		 * casting for such types is disallowed.
+		 */
+		*errorsafe_coercion = false;
+
+		return;
+	}
+	else
+	{
+		Oid			input_typelem = get_element_type(inputType);
+		Oid			target_typelem = get_element_type(targetType);
+
+		if (OidIsValid(input_typelem) && OidIsValid(target_typelem))
+		{
+			/* Recurse into the array element types. */
+			CoercionErrorSafe_Internal(input_typelem,
+									   target_typelem,
+									   errorsafe_coercion,
+									   userdefined);
+			return;
+		}
+
+		/*
+		 * It is unlikely that an array can be coerced to a non-array, or vice
+		 * versa. Currently, this only occurs when the target type is text,
+		 * resulting in a CoerceViaIO cast expression—which is already
+		 * handled by CoercionErrorSafeCheck.
+		 */
+	}
+
+	/*
+	 * Casts from MONEY source type are not error safe.
+	 */
+	if (inputType == MONEYOID)
+	{
+		*errorsafe_coercion = false;
+
+		return;
+	}
+
+	/*
+	 * In case preivous logic didn't figure out that the real element base
+	 * type is identitcal then obvously, they are error safe.
+	 */
+	if (inputType == targetType)
+		return;
+	else
+	{
+		tuple = SearchSysCache2(CASTSOURCETARGET,
+								ObjectIdGetDatum(inputType),
+								ObjectIdGetDatum(targetType));
+
+		/*
+		 * A pg_cast entry might not exist for this specific cast; for
+		 * example, when using CoerceViaIO.
+		 */
+		if (HeapTupleIsValid(tuple))
+		{
+			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+			if (castForm->castfunc > FirstUnpinnedObjectId)
+			{
+				*errorsafe_coercion = false;
+				*userdefined = true;
+			}
+			ReleaseSysCache(tuple);
+		}
+		else if (inputType > FirstUnpinnedObjectId || targetType > FirstUnpinnedObjectId)
+		{
+			*errorsafe_coercion = false;
+			*userdefined = true;
+		}
+	}
 }
 
 /*
@@ -3224,6 +3524,8 @@ ParseExprKindName(ParseExprKind exprKind)
 		case EXPR_KIND_CHECK_CONSTRAINT:
 		case EXPR_KIND_DOMAIN_CHECK:
 			return "CHECK";
+		case EXPR_KIND_CAST_DEFAULT:
+			return "CAST DEFAULT";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 860767a52ee..0952d0e4ad7 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2746,6 +2746,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("set-returning functions are not allowed in check constraints");
 			break;
+		case EXPR_KIND_CAST_DEFAULT:
+			err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 6a862d5a4f9..35d99fdac07 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -845,7 +845,8 @@ transformAssignmentIndirection(ParseState *pstate,
 										COERCION_IMPLICIT,
 										COERCE_IMPLICIT_CAST,
 										location,
-										false);
+										false,
+										NULL);
 
 			return (Node *) fstore;
 		}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index bb7eccde9fd..bbf39dac619 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -660,6 +660,28 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
 	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
 }
 
+/*
+	fmgr_info(typinput, &flinfo);
+	return InputFunctionCallSafe(&flinfo, string, typioparam, atttypmod,
index a049cc67ed6..a7284f48429 100644
@@ -5196,7 +5196,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
index 13eed22dd6a..70523248f56 100644
 
index 88de5c0481c..2e47ec21855 100644
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				/*
+				 * Cannot deparse castexpr directly, becuase transformTypeCast
+				 * may have already constant-folded the cast expression into a
+				 * constant. Instead, use "source" and "defexpr" to
+				 * reconstruct the CAST DEFAULT clause.
+				 */
+				appendStringInfoString(buf, "CAST(");
+				get_rule_expr((Node *) stcexpr->source, 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_JsonExpr:
 			{
 				JsonExpr   *jexpr = (JsonExpr *) node;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c61b3d624d5..faf6976b040 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -265,6 +265,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 +755,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
 		{
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 650baab3efc..33bbdbfeffb 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);
@@ -380,6 +381,7 @@ extern ProjectionInfo *ExecBuildUpdateProjection(List *targetList,
 												 TupleTableSlot *slot,
 												 PlanState *parent);
 extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
+extern ExprState *ExecPrepareExprWithContext(Expr *node, EState *estate, Node *escontext);
 extern ExprState *ExecPrepareQual(List *qual, EState *estate);
 extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
 extern List *ExecPrepareExprList(List *nodes, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 53c138310db..2dc95aaaa33 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1096,6 +1096,27 @@ typedef struct DomainConstraintState
 	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;
 
+typedef struct SafeTypeCastState
+{
+	SafeTypeCastExpr *stcexpr;
+
+	/*
+	 * 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 4133c404a6b..641ff29c6d6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -402,6 +402,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 bb05aeebee4..bb4cc37ef9c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -760,6 +760,40 @@ 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 source expression.
+	 *
+	 * Cases like ``CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR)`` where
+	 * the castexpr evaluates to NULL, we need this field to reconstruct the
+	 * original query.
+	 */
+	Expr	   *source;
+
+	/*
+	 * transformed cast expression, NULL means cannot coerce to target type
+	 */
+	Expr	   *castexpr pg_node_attr(query_jumble_ignore);
+	/* 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;
+
 /*
  * 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);

 
 
index 00000000000..d0afce2bc97
@@ -0,0 +1,1185 @@
+CREATE DOMAIN ddomain AS numeric CHECK ((1/ (VALUE - 1)) > 0);
+SELECT CAST ('1' AS ddomain DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  division by zero
+SELECT CAST (1 AS ddomain DEFAULT NULL ON CONVERSION ERROR);
+ERROR:  division by zero
+CREATE VIEW vddomain AS SELECT CAST (1 AS ddomain DEFAULT NULL ON CONVERSION ERROR);
+\sv vddomain
+CREATE OR REPLACE VIEW public.vddomain AS
+ SELECT CAST(1 AS ddomain DEFAULT NULL::numeric::ddomain ON CONVERSION ERROR) AS ddomain
+DROP VIEW vddomain;
+-- Test nested type casts; all sub-expressions will be evaluated in an error-safe manner too.
+SELECT CAST(CAST(ARRAY[CAST(ARRAY[42, 42] AS d_int42arr)] AS d_int42arr1[])
+            AS d_int8arr[]
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr DEFAULT NULL ON CONVERSION ERROR);
+DROP DOMAIN ddomain;
 
index fb3e0ec41b2..f4fc64c1a92 100644
@@ -24,6 +24,12 @@ SELECT * FROM xmltest;
   2 | <value>two</value>
 (2 rows)
 
index 8fa0a6c47fb..b6ef7ccecb2 100644
 
 # ----------
index 00000000000..776e9ab8e0b
@@ -0,0 +1,453 @@
+CREATE DOMAIN ddomain AS numeric CHECK ((1/ (VALUE - 1)) > 0);
+SELECT CAST ('1' AS ddomain DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST (1 AS ddomain DEFAULT NULL ON CONVERSION ERROR);
+CREATE VIEW vddomain AS SELECT CAST (1 AS ddomain DEFAULT NULL ON CONVERSION ERROR);
+\sv vddomain
+DROP VIEW vddomain;
+-- Test nested type casts; all sub-expressions will be evaluated in an error-safe manner too.
+SELECT CAST(CAST(ARRAY[CAST(ARRAY[42, 42] AS d_int42arr)] AS d_int42arr1[])
+            AS d_int8arr[]
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr DEFAULT NULL ON CONVERSION ERROR);
+DROP DOMAIN ddomain;

 
index aafd39433a6..dbfec20a05e 100644
@@ -16,6 +16,8 @@ SELECT count(*) = 0 AS skip_test FROM xmltest \gset
 
 SELECT * FROM xmltest;
 
index c5db6ca6705..69a005aa039 100644
@@ -2782,6 +2782,8 @@ STRLEN