From 5be82e6f95fdcf77a314e403ba398d3f78d22f08 Mon Sep 17 00:00:00 2001
From: Amul Sul <sulamul@gmail.com>
Date: Tue, 31 Mar 2026 14:49:02 +0530
Subject: [PATCH] fixup! CAST(expr AS newtype DEFAULT expr ON CONVERSION ERROR)

---
 contrib/citext/expected/citext.out        |  4 +-
 contrib/citext/expected/citext_1.out      |  4 +-
 doc/src/sgml/syntax.sgml                  | 52 ++++++++++++++---------
 src/backend/executor/execExpr.c           |  4 +-
 src/backend/parser/parse_expr.c           | 21 +++++----
 src/test/regress/expected/cast.out        |  8 ++--
 src/test/regress/expected/create_cast.out |  4 +-
 7 files changed, 53 insertions(+), 44 deletions(-)

diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 73a3f923b07..69649c2c5e2 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -11,10 +11,10 @@ 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
+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 cast for user-defined types are not yet supported.
+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 28788adf26d..896939d35ce 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -11,10 +11,10 @@ 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
+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 cast for user-defined types are not yet supported.
+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/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index aa0fc1ef81c..48239c45fe2 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2071,10 +2071,6 @@ 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 default behavior when no <literal>ON CONVERSION ERROR</literal> clause is specified is equivalent to:
-<synopsis>
-CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
-</synopsis>
    </para>
 
    <para>
@@ -2130,33 +2126,47 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
     </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:
+   <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> NULL ON CONVERSION ERROR )
 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>
-    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>
+     With <literal>NULL ON CONVERSION ERROR</literal>, a <literal>NULL</literal>
+     value is returned when the cast fails.  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>
-    Some examples:
+     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 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>
-SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR);
-<lineannotation>Result: </lineannotation><computeroutput>1.1</computeroutput>
 </screen>
     </para>
-  </sect3>
+   </sect3>
   </sect2>
 
   <sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 84a48e8e3f6..59482fab56c 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -4794,8 +4794,8 @@ ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state,
 						 ExprEvalStep *scratch)
 {
 	/*
-	 * Coercion to the target type failed. Falling back to the DEFAULT
-	 * expression from the ON CONVERSION ERROR clause to finish.
+	 * If there's no cast expression (castexpr == NULL), only the DEFAULT
+	 * expression needs to be evaluated; set it up and return early.
 	 */
 	if (stcexpr->castexpr == NULL)
 	{
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 83adc598565..a83d0dda76e 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2785,11 +2785,7 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	Oid			targetTypecoll;
 	int32		targetTypmod;
 	int			location;
-	ErrorSaveContext *escontext = makeNode(ErrorSaveContext);
-
-	escontext->type = T_ErrorSaveContext;
-	escontext->error_occurred = false;
-	escontext->details_wanted = false;
+	ErrorSaveContext *escontext = NULL;
 
 	/* Look up the type name first */
 	typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod);
@@ -2801,6 +2797,11 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	{
 		Oid			defColl;
 
+		escontext = makeNode(ErrorSaveContext);
+		escontext->type = T_ErrorSaveContext;
+		escontext->error_occurred = false;
+		escontext->details_wanted = false;
+
 		defexpr = transformExpr(pstate, tc->defexpr, EXPR_KIND_CAST_DEFAULT);
 
 		defexpr = coerce_to_target_type(pstate, defexpr, exprType(defexpr),
@@ -2961,13 +2962,11 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 	if (!errorsafe_coercion)
 		ereport(ERROR,
 				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+				errmsg("cannot cast type %s to %s with DEFAULT expression in CAST ... ON CONVERSION ERROR",
 					   format_type_be(inputType),
-					   format_type_be(targetType),
-					   "DEFAULT",
-					   "CAST ... ON CONVERSION ERROR"),
+					   format_type_be(targetType)),
 				userdefined
-				? errdetail("Safe type cast for user-defined types are not yet supported.")
+				? errdetail("Safe type casts for user-defined types are not yet supported.")
 				: errdetail("Explicit cast is defined but definition is not error safe."),
 				parser_errposition(pstate, exprLocation(source)));
 }
@@ -3048,7 +3047,7 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 	}
 
 	/*
-	 * Casts source data type as MONEY are not error safe.
+	 * Casts from MONEY source type are not error safe.
 	 */
 	if (inputType == MONEYOID)
 	{
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
index e5740d2f5bc..1a55fceeef3 100644
--- a/src/test/regress/expected/cast.out
+++ b/src/test/regress/expected/cast.out
@@ -186,7 +186,7 @@ SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0;
 (2 rows)
 
 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
+ERROR:  cannot cast type record to tcast0 with DEFAULT expression in CAST ... ON CONVERSION ERROR
 LINE 1: SELECT CAST(ROW(1,2) AS tcast0 DEFAULT NULL ON CONVERSION ER...
                     ^
 DETAIL:  Explicit cast is defined but definition is not error safe.
@@ -443,7 +443,7 @@ ERROR:  value too long for type character(3)
 LINE 1: ...ST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)'...
                                                              ^
 SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); -- error
-ERROR:  cannot cast type record to comp_domain_with_typmod when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+ERROR:  cannot cast type record to comp_domain_with_typmod with DEFAULT expression in CAST ... ON CONVERSION ERROR
 LINE 1: SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT ...
                     ^
 DETAIL:  Explicit cast is defined but definition is not error safe.
@@ -641,12 +641,12 @@ SELECT CAST('-0'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
 (1 row)
 
 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
+ERROR:  cannot cast type money to numeric with DEFAULT expression in CAST ... ON CONVERSION ERROR
 LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
                     ^
 DETAIL:  Explicit cast is defined but definition is not error safe.
 SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
-ERROR:  cannot cast type money[] to numeric[] when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+ERROR:  cannot cast type money[] to numeric[] with DEFAULT expression in CAST ... ON CONVERSION ERROR
 LINE 1: SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVE...
                     ^
 DETAIL:  Explicit cast is defined but definition is not error safe.
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 28c880b10e2..64ac60b157f 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -89,10 +89,10 @@ SELECT 1234::int4::casttesttype; -- Should work now
 (1 row)
 
 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
+ERROR:  cannot cast type integer to casttesttype with DEFAULT expression in CAST ... ON CONVERSION ERROR
 LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
                     ^
-DETAIL:  Safe type cast for user-defined types are not yet supported.
+DETAIL:  Safe type casts 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,
-- 
2.47.1

