public inbox for [email protected]  
help / color / mirror / Atom feed
From: jian he <[email protected]>
To: Corey Huinker <[email protected]>
Cc: Vik Fearing <[email protected]>
Cc: Isaac Morland <[email protected]>
Cc: [email protected]
Subject: Re: CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions
Date: Wed, 10 Dec 2025 16:57:09 +0800
Message-ID: <CACJufxFEzD3mqc+MDpgzvdt+4Azbn2pF6TWW=dSCqSK7OHoL6A@mail.gmail.com> (raw)
In-Reply-To: <CACJufxHrE0s7G0xg1frWo2+tFLTLaikKCObixH-4p9zMYKtHFw@mail.gmail.com>
References: <CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com>
	<CAMsGm5dpfm2PHL8XZvC-JSd+UPkgx3rpReUA=G=4+rUCH+Ntcw@mail.gmail.com>
	<CADkLM=eD_S8mGhPfu5+hXXvXgR0-cxGpGd9dgPzD+nCuO7HFaQ@mail.gmail.com>
	<CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com>
	<[email protected]>
	<CACJufxGRAnwJzu7nMq4ZP=yqa1Sz=qR+mR1TmY0aCDjJoJRRtg@mail.gmail.com>
	<[email protected]>
	<CACJufxFy+DFpJ2e-czyCTAgSJXNFaQGWFKA4mjbW-LAMGc1YBA@mail.gmail.com>
	<CADkLM=f1Jv81=s5Ckazx3zZq=M5KoBJMJkOZux_-L+gezODCEQ@mail.gmail.com>
	<CACJufxGw_OY7K3rfG4kDb902O2guhT-wgTjTJQ=pWeVWRTHpHQ@mail.gmail.com>
	<CADkLM=cFSg3+6Sk00dLAF7Q7jnrKBk6+N5gRxT5BCxRvaGtR-g@mail.gmail.com>
	<CACJufxE_aO5FtBGwhDym-Fwe7k8oJY7a8jcYDx77=t3maPvG0g@mail.gmail.com>
	<CADkLM=chahh6ddZFjLL6AUdqzL_Px0raTu-5Jzn2WN8yELtmJw@mail.gmail.com>
	<CACJufxE053=bO3pDUpGba6Yz3VGpU_XCbg4HO6Rew5EJ7k7VnQ@mail.gmail.com>
	<CACJufxF--5d=fmoRBHfqJE9Vy38dCURNKYOKKpujRCnoTEQ7nQ@mail.gmail.com>
	<CACJufxHpMJn22Nu_wmG6eV_S8SAM6KM+GhMO7GuzVb=d9q5C4A@mail.gmail.com>
	<CACJufxHM2e3DQmbRdDZvWyG3ZCLyOg6XFifvOz_TGy1tGw7NHw@mail.gmail.com>
	<CADkLM=daTLuRcwzc6Egtwvh4XYgtABWuMBVnEznd-dXqmXfzUw@mail.gmail.com>
	<CACJufxEcrrcaeFW+zYsjgb6r+ijzwszyxeHk3wxGY+3idiA2ZA@mail.gmail.com>
	<CADkLM=ehavqENDBCcYQufPFKboV90+o_uFdhcrh=Ymq_TNqo=A@mail.gmail.com>
	<CADkLM=ecTybe9Z9TSRD-NKZ=-V4DuGVRtXZGO6+F7=m3Gg9GGQ@mail.gmail.com>
	<CACJufxH5OSeY0-qirksn8S2FUycxON-O=iwc0-Nne1MTAguGhQ@mail.gmail.com>
	<CADkLM=eFasBpS1cqf67TpKGbKoUSy00FuT05Yz4RpXQBpqktuw@mail.gmail.com>
	<CACJufxHrE0s7G0xg1frWo2+tFLTLaikKCObixH-4p9zMYKtHFw@mail.gmail.com>

On Tue, Dec 9, 2025 at 11:39 AM jian he <[email protected]> wrote:
>
> On Mon, Dec 1, 2025 at 1:41 PM Corey Huinker <[email protected]> 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
> >
> > 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.
> >

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.
-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);

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/


Attachments:

  [text/x-patch] v15-0022-error-safe-for-user-defined-CREATE-CAST.patch (71.9K, 2-v15-0022-error-safe-for-user-defined-CREATE-CAST.patch)
  download | inline diff:
From 99d299237edcb65151a94b7ab17497dad6d7b778 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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);
 
 -- Test the operators and indexing functions
 
@@ -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);
 
 	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
 
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)));
 }
 
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..ca52cfcd086 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,73 +378,73 @@
 
 # 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)',
-  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'},
 
 # 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..bf47544f675 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;
 
 /* ----------------
@@ -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)
 
 -- 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
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



  [text/x-patch] v15-0019-refactor-point_dt.patch (8.4K, 3-v15-0019-refactor-point_dt.patch)
  download | inline diff:
From 35715cfacd3ee194601443ba00dcac65630454d5 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 src/backend/utils/adt/geo_ops.c | 77 +++++++++++++++++++--------------
 1 file changed, 44 insertions(+), 33 deletions(-)

diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 9101a720744..ad6ba1d6621 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -82,7 +82,7 @@ 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);
 
@@ -839,7 +839,7 @@ box_distance(PG_FUNCTION_ARGS)
 	box_cn(&a, box1);
 	box_cn(&b, box2);
 
-	PG_RETURN_FLOAT8(point_dt(&a, &b));
+	PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
 }
 
 
@@ -1808,7 +1808,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 +1995,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 +2184,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 +2269,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 +2279,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 +2289,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 +2299,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)));
 }
 
 
@@ -2743,7 +2754,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 +2795,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 +3119,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 +3187,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;
 		}
@@ -4766,7 +4777,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 +4839,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 +4851,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)));
 }
 
@@ -5069,7 +5080,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 +5096,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 +5108,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 +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;
 
-	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;
 
@@ -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



  [text/x-patch] v15-0020-error-safe-for-casting-geometry-data-type.patch (12.1K, 4-v15-0020-error-safe-for-casting-geometry-data-type.patch)
  download | inline diff:
From 89539ec5c8663872ad954e98d435f3d4e321b7d0 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 10 Dec 2025 13:35:42 +0800
Subject: [PATCH v15 20/22] 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
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=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
 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 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);
@@ -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,8 +837,8 @@ 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, 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).
  */
@@ -2328,13 +2344,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();
 }
 
 
@@ -3288,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);
 	}
 
@@ -4119,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
@@ -4135,7 +4178,7 @@ point_add(PG_FUNCTION_ARGS)
 
 	result = (Point *) palloc(sizeof(Point));
 
-	point_add_point(result, p1, p2);
+	point_add_point(result, p1, p2, NULL);
 
 	PG_RETURN_POINT_P(result);
 }
@@ -4247,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);
 }
@@ -4411,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);
 }
@@ -4469,7 +4512,7 @@ path_poly(PG_FUNCTION_ARGS)
 
 	/* This is not very consistent --- other similar cases return NULL ... */
 	if (!path->closed)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("open path cannot be converted to polygon")));
 
@@ -4519,7 +4562,9 @@ poly_center(PG_FUNCTION_ARGS)
 
 	result = (Point *) palloc(sizeof(Point));
 
-	poly_to_circle(&circle, poly);
+	if (!poly_to_circle(&circle, poly, fcinfo->context))
+		PG_RETURN_NULL();
+
 	*result = circle.center;
 
 	PG_RETURN_POINT_P(result);
@@ -4981,7 +5026,7 @@ circle_add_pt(PG_FUNCTION_ARGS)
 
 	result = (CIRCLE *) palloc(sizeof(CIRCLE));
 
-	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);
@@ -5202,14 +5247,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()
@@ -5220,15 +5281,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, NULL);
+	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;
+
+	circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		goto fail;
 
 	PG_RETURN_CIRCLE_P(circle);
+
+fail:
+	PG_RETURN_NULL();
 }
 
 
@@ -5292,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);
 
@@ -5304,14 +5386,43 @@ 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);
+	{
+		point_add_point(&result->center,
+						&result->center,
+						&poly->p[i],
+						escontext);
+		if (SOFT_ERROR_OCCURRED(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, NULL));
-	result->radius = float8_div(result->radius, poly->npts);
+	{
+		x = point_dt(&poly->p[i], &result->center, escontext);
+		if (SOFT_ERROR_OCCURRED(escontext))
+			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
@@ -5322,7 +5433,8 @@ poly_circle(PG_FUNCTION_ARGS)
 
 	result = (CIRCLE *) palloc(sizeof(CIRCLE));
 
-	poly_to_circle(result, poly);
+	if (!poly_to_circle(result, poly, fcinfo->context))
+		PG_RETURN_NULL();
 
 	PG_RETURN_CIRCLE_P(result);
 }
-- 
2.34.1



  [text/x-patch] v15-0018-introduce-float8-safe-function.patch (3.8K, 5-v15-0018-introduce-float8-safe-function.patch)
  download | inline diff:
From 72fa0fc1a8a154b68442d2a8731bb82e39eff4fa Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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
+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;
+}
+
 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(NULL);
-
-	return result;
+	return float8_mi_safe(val1, val2, NULL);
 }
 
 static inline float4
@@ -160,19 +176,33 @@ float4_mul(const float4 val1, const float4 val2)
 }
 
 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(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
-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(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
+float8_div(const float8 val1, const float8 val2)
+{
+	return float8_div_safe(val1, val2, NULL);
+}
+
+
 /*
  * Routines for NaN-aware comparisons
  *
-- 
2.34.1



  [text/x-patch] v15-0021-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patch (120.2K, 6-v15-0021-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patch)
  download | inline diff:
From 08a0595584f9540a54586cfce0c50ea410a592bc Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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:
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}

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

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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    |  23 +-
 contrib/pg_stat_statements/sql/select.sql     |   5 +
 doc/src/sgml/syntax.sgml                      |  30 +
 src/backend/executor/execExpr.c               | 109 ++-
 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                 |  14 +
 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 +
 40 files changed, 2179 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.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)
 
+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..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 <>.
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/syntax.sgml b/doc/src/sgml/syntax.sgml
index 34c83880a66..32af9ea061c 100644
--- a/doc/src/sgml/syntax.sgml
+++ 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.
+    The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
    </para>
 
    <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>
 
   <sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b05ff476a63..e6738c0ae75 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,
@@ -170,6 +173,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
  *
@@ -1701,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
 				elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+				elemstate->escontext = state->escontext;
 
 				ExecInitExprRec(acoerce->elemexpr, elemstate,
 								&elemstate->resvalue, &elemstate->resnull);
@@ -2177,6 +2222,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;
@@ -2743,7 +2796,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;
@@ -4741,6 +4794,60 @@ 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)
+{
+	/*
+	 * 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.
+	 */
+	if (stcexpr->cast_expr == NULL)
+	{
+		ExecInitExprRec((Expr *) stcexpr->default_expr,
+						state, resv, resnull);
+		return;
+	}
+	else
+	{
+		SafeTypeCastState *stcstate;
+
+		stcstate = palloc0(sizeof(SafeTypeCastState));
+		stcstate->stcexpr = stcexpr;
+		stcstate->escontext.type = T_ErrorSaveContext;
+		state->escontext = &stcstate->escontext;
+
+		/* evaluate argument expression into step's result area */
+		ExecInitExprRec((Expr *) stcexpr->cast_expr,
+						state, resv, resnull);
+		scratch->opcode = EEOP_SAFETYPE_CAST;
+		scratch->d.stcexpr.stcstate = stcstate;
+		ExprEvalPushStep(state, scratch);
+
+		/*
+		 * Steps to evaluate the DEFAULT expression.  Skip it if this is a
+		 * binary coercion cast.
+		 */
+		if (!IsA(stcexpr->cast_expr, RelabelType))
+		{
+			ErrorSaveContext *saved_escontext;
+
+			saved_escontext = state->escontext;
+			state->escontext = NULL;
+			ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+							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 1d88cdd2cb4..ca6e0fd56cd 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();
 		}
 
+		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;
 
+			case EEOP_SAFETYPE_CAST:
+				{
+					SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+					if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+					{
+						/*
+						 * 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]);
+					}
+					else
+						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 d228318dc72..b3ae69068d0 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;
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4464,6 +4503,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->raw_default))
+					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 bda4c4eb292..e6d8b6c467f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -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));
 			}
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr 	*stc = (SafeTypeCastExpr *) node;
+				SafeTypeCastExpr	*newexpr;
+				Node	   *source_expr = stc->source_expr;
+				Node	   *default_expr = stc->default_expr;
+
+				source_expr = eval_const_expressions_mutator(source_expr, context);
+				default_expr = eval_const_expressions_mutator(default_expr, context);
+
+				/*
+				 * We must not reduce any recognizably constant subexpressions
+				 * in cast_expr here, since we don’t want it to fail
+				 * prematurely.
+				 */
+				newexpr = makeNode(SafeTypeCastExpr);
+				newexpr->source_expr = source_expr;
+				newexpr->cast_expr = stc->cast_expr;
+				newexpr->default_expr = default_expr;
+				newexpr->resulttype = stc->resulttype;
+				newexpr->resulttypmod = stc->resulttypmod;
+				newexpr->resultcollid = stc->resultcollid;
+
+				return (Node *) newexpr;
+			}
+
 		case T_SubPlan:
 		case T_AlternativeSubPlan:
 
@@ -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);
 
+	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/gram.y b/src/backend/parser/gram.y
index c3a0a354a9c..8a27e045bc0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -16013,6 +16013,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->raw_default = 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->raw_default = $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 b8340557b34..de77f6e9302 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -490,6 +490,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:
 
@@ -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(pstate, expr, exprtype,
-						 targettype, targettypmod,
-						 ccontext, cformat, location);
+	result = coerce_type_extend(pstate, expr, exprtype,
+								targettype, targettypmod,
+								ccontext, cformat, location,
+								escontext);
+	if (SOFT_ERROR_OCCURRED(escontext))
+		return NULL;
 
 	/*
 	 * 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 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 (!coercion_failed && !con->constisnull && newcon->constlen == -1)
 			newcon->constvalue =
 				PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
 
@@ -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 @@
 
 #include "access/htup_details.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.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,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 *can_coerce);
 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 +79,10 @@ 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 void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
+								   Node *sourceexpr, Oid inputType,
+								   Oid targetType);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
 											JsonObjectConstructor *ctor);
@@ -164,13 +171,17 @@ 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:
 			result = transformTypeCast(pstate, (TypeCast *) expr);
 			break;
 
+		case T_SafeTypeCast:
+			result = transformTypeSafeCast(pstate, (SafeTypeCast *) expr);
+			break;
+
 		case T_CollateClause:
 			result = transformCollateClause(pstate, (CollateClause *) expr);
 			break;
@@ -564,6 +575,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 +1836,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,17 +2026,24 @@ 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.  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.
  */
 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;
+	ErrorSaveContext escontext = {T_ErrorSaveContext};
 
 	/*
 	 * 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;
 	}
 
+	coerce_type_coll = get_typcollation(coerce_type);
+
 	/*
 	 * Coerce elements to target type
 	 *
@@ -2134,28 +2162,82 @@ 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);
-		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))));
+			/*
+			 * 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..fef83ce73b4 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]);
 
+		/* 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 6cf90be40bb..50ae41837ba 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;
 
+		case T_SafeTypeCastExpr:
+			{
+				SafeTypeCastExpr   *stcexpr = castNode(SafeTypeCastExpr, node);
+
+				/*
+				 * Here, 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.
+				 */
+				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 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 0fe63c6bb83..c9e5d0de8ed 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1759,6 +1759,20 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
 	return InputFunctionCall(&flinfo, str, typioparam, typmod);
 }
 
+/* error-safe version of OidInputFunctionCall */
+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/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..937cbf3bc9b 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_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 fa2b657fb2f..f99fc26eb1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,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 c0dbe85ed1c..a9357b98d70 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/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;
 
+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..82b0fb83b4b 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;
 
+/*
+ * 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;	/* untransformed DEFAULT expression */
+} SafeTypeCast;
+
 /*
  * CollateClause - a COLLATE expression
  */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..1401109fb3c 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 -
+ *		Transformed representation of
+ * 		CAST(expr AS typename DEFAULT expr2 ON ERROR)
+ * 		CAST(expr AS typename NULL ON ERROR)
+ */
+typedef struct SafeTypeCastExpr
+{
+	Expr		xpr;
+
+	/* transformed source expression */
+	Node	   *source_expr;
+
+	/*
+	 * The transformed cast expression; NULL if we can not cocerce source
+	 * expression to the 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;
+
+	/* Original SafeTypeCastExpr's location */
+	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 44ec5296a18..587e711596a 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -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);
 
 extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
 
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 8d775c72c59..1eca1ce727d 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,11 +43,26 @@ extern Node *coerce_to_target_type(ParseState *pstate,
 								   CoercionContext ccontext,
 								   CoercionForm cformat,
 								   int location);
+extern Node *coerce_to_target_type_extended(ParseState *pstate,
+											Node *expr,
+											Oid exprtype,
+											Oid targettype,
+											int32 targettypmod,
+											CoercionContext ccontext,
+											CoercionForm cformat,
+											int location,
+											Node *escontext);
 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_extend(ParseState *pstate, Node *node,
+								Oid inputTypeId,
+								Oid targetTypeId, int32 targetTypeMod,
+								CoercionContext ccontext, CoercionForm cformat,
+								int location,
+								Node *escontext);
 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_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/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..40aca2b31c9 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,
+								Node *escontext, Datum *result);
 
 extern Oid	typeidTypeRelid(Oid type_id);
 extern Oid	typeOrDomainTypeRelid(Oid type_id);
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..b0dc7b8faea
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,810 @@
+SET extra_float_digits = 0;
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date 
+------
+ 
+(1 row)
+
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+ char 
+------
+ A
+(1 row)
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION ERROR);
+ int4 
+------
+   18
+(1 row)
+
+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 (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...
+                                       ^
+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('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...
+                    ^
+--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));
+ column1 
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}'  ON CONVERSION ERROR));
+ column1 
+---------
+ {"1 3"}
+(1 row)
+
+-- 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"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+            AS INT[]
+            DEFAULT NULL ON CONVERSION ERROR);
+ array 
+-------
+ 
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR:  integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR:  cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+                                        ^
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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(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 user-defined type, domain, array over domain, domain over array
+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 DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR:  value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+                                              ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+ comp2 
+-------
+ (123)
+(1 row)
+
+SELECT CAST(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);
+ 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);
+ 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)'...
+                                                             ^
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+  array  
+---------
+ {42,42}
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+  array  
+---------
+ {41,43}
+(1 row)
+
+-- test array coerce
+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 ARRAY[1] ON CONVERSION ERROR);
+ array 
+-------
+ {1}
+(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 int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+  int4  
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+  int4   
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array 
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+  array  
+---------
+ {21,22}
+(1 row)
+
+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}...
+                             ^
+-----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)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+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...
+                    ^
+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
+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:  Explicit cast is defined but definition is not error safe
+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:  Explicit cast is defined but definition is not error safe
+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:  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 CONVERSIO...
+                    ^
+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
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+ int8 
+------
+   19
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+ int4 
+------
+   20
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+ int2 
+------
+   21
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+ int4 
+------
+   22
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+ int8 
+------
+   23
+(1 row)
+
+-----safe cast from text type to other data types
+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)
+
+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 betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange 
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange 
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange  AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange 
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+     datemultirange     
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+      tsmultirange      
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+     tstzmultirange     
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+  col1 float4, col2 float8, col3 numeric, col4 numeric[],
+  col5 int2 default 32767,
+  col6 int4 default 32768,
+  col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+       CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2  | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale 
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 |   32767 |   32767 |  32767 |   32767 |     32767 |     32767 |      32767 |          32767.0
+ 32767 |   32767 |   32767 |  32767 |   32767 |     32767 |     32767 |      32767 |          32767.0
+ 32767 |   32767 |   32767 |  32767 |   32767 |     32767 |     32767 |      32767 |          32767.0
+ 32767 |   32767 |   32767 |  32767 |   32767 |     32767 |     32767 |      32767 |          32767.0
+(4 rows)
+
+SELECT col6 as int4,
+       CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4  | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale 
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 |         |   32768 |  32768 |   32768 |     32768 |     32768 |      32768 |          32768.0
+ 32768 |         |   32768 |  32768 |   32768 |     32768 |     32768 |      32768 |          32768.0
+ 32768 |         |   32768 |  32768 |   32768 |     32768 |     32768 |      32768 |          32768.0
+ 32768 |         |   32768 |  32768 |   32768 |     32768 |     32768 |      32768 |          32768.0
+(4 rows)
+
+SELECT col7 as int8,
+       CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+    int8    | to_int2 | to_int4 | to_oid |  to_int8   |  to_float4  | to_float8  | to_numeric | to_numeric_scale 
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 |         |         |        | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |                 
+ 4294967296 |         |         |        | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |                 
+ 4294967296 |         |         |        | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |                 
+ 4294967296 |         |         |        | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |                 
+(4 rows)
+
+SELECT col3 as numeric,
+       CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+       numeric       | to_int2 | to_int4 | to_oid | to_int8 |  to_float4  |      to_float8       |     to_numeric      | to_numeric_scale 
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 |         |         |        |         | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |                 
+            Infinity |         |         |        |         |    Infinity |             Infinity |            Infinity |                 
+           -Infinity |         |         |        |         |   -Infinity |            -Infinity |           -Infinity |                 
+                 NaN |         |         |        |         |         NaN |                  NaN |                 NaN |              NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+       CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+       CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+       CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+       CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+       CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+       CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+          num_arr           | int2arr | int4arr | int8arr |           f4arr            |           f8arr            | numarr 
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234}                  | {11}    | {11}    | {11}    | {11.1234}                  | {11.1234}                  | {11.1}
+ {11.1234,12,Infinity,NaN}  |         |         |         | {11.1234,12,Infinity,NaN}  | {11.1234,12,Infinity,NaN}  | 
+ {11.1234,12,-Infinity,NaN} |         |         |         | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} | 
+ {11.1234,12,-Infinity,NaN} |         |         |         | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} | 
+(4 rows)
+
+SELECT col1 as float4,
+       CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+  float4   | to_int2 | to_int4 | to_oid | to_int8 | to_float4 |    to_float8     | to_numeric | to_numeric_scale 
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+   11.1234 |      11 |      11 |        |      11 |   11.1234 | 11.1233997344971 |    11.1234 |             11.1
+  Infinity |         |         |        |         |  Infinity |         Infinity |   Infinity |                 
+ -Infinity |         |         |        |         | -Infinity |        -Infinity |  -Infinity |                 
+       NaN |         |         |        |         |       NaN |              NaN |        NaN |              NaN
+(4 rows)
+
+SELECT col2 as float8,
+       CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+  float8   | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale 
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+   11.1234 |      11 |      11 |        |      11 |   11.1234 |   11.1234 |    11.1234 |             11.1
+  Infinity |         |         |        |         |  Infinity |  Infinity |   Infinity |                 
+ -Infinity |         |         |        |         | -Infinity | -Infinity |  -Infinity |                 
+       NaN |         |         |        |         |       NaN |       NaN |        NaN |              NaN
+(4 rows)
+
+-- 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
+('-infinity', '-infinity', '-infinity', '-infinity',
+  '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+  '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+       CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+   date    | to_timestamptz |  to_date  | to_times | to_timetz | to_timestamp_scale 
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity      | -infinity |          |           | -infinity
+ -infinity | -infinity      | -infinity |          |           | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+       CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz |  to_date  | to_times | to_timetz | to_timestamp_scale 
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity      | -infinity |          |           | -infinity
+ infinity  | infinity       | infinity  |          |           | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+       CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz |  to_date  | to_times | to_timetz | to_timestamp_scale 
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity   | -infinity      | -infinity |          |           | -infinity
+ infinity    | infinity       | infinity  |          |           | infinity
+(2 rows)
+
+SELECT col3 as interval,
+       CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+       CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval  | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale 
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity |                |         |          |           |                    | -infinity
+ infinity  |                |         |          |           |                    | infinity
+(2 rows)
+
+SELECT col4 as time,
+       CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+       CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+       CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+    time     |  to_times   | to_timetz |          to_interval          |       to_interval_scale       
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39    | 15:36:39    | t         | @ 15 hours 36 mins 39 secs    | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t         | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+       CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+       CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+       CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+       CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+       CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+     timetz     |   to_time   | to_time_scale |   to_timetz    | to_timetz_scale | to_interval | to_interval_scale 
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04    | 15:36:39    | 15:36:39      | 15:36:39-04    | 15:36:39-04     |             | 
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99   | 23:59:59.99-07 | 23:59:59.99-07  |             | 
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(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_safecast3;
+ jsonb  | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint 
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" |            |            |         |           |           |         |            
+(1 row)
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+       CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+       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;
+\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 '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast0,
+    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 * FROM safecastview;
+ bpchar |   cast0    | cast1 | cast2 | cast3 
+--------+------------+-------+-------+-------
+ 123    | 2025-12-07 | {1,2} |       | {1,2}
+(1 row)
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+            DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+            DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ 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
+SELECT * FROM safecastview1;
+  cast1  |  cast2  
+---------+---------
+ {41,43} | {41,43}
+(1 row)
+
+RESET datestyle;
+--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:  functions in index expression must be marked IMMUTABLE
+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 public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+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;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..0054ed0ef67 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)
 
+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
 
 # ----------
 # 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..9b6f3cc0649
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,350 @@
+SET extra_float_digits = 0;
+
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION 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 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 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
+
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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(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 user-defined type, domain, array over domain, domain over array
+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 DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --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);
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] 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 int[] 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 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
+
+-----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);
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+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);
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+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;
+
+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 betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange  AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+  col1 float4, col2 float8, col3 numeric, col4 numeric[],
+  col5 int2 default 32767,
+  col6 int4 default 32768,
+  col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+       CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+       CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+       CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+       CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+       CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+       CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+       CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+       CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+       CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+       CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+       CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+       CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+       CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+       CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+       CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+       CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+       CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+       CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+       CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- 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
+('-infinity', '-infinity', '-infinity', '-infinity',
+  '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+  '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+       CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+       CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+       CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+       CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+       CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+       CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+       CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+       CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+       CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+       CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+       CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+       CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+       CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+       CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+       CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+       CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+       CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(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_safecast3;
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+       CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+       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;
+\sv safecastview
+SELECT * FROM safecastview;
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+            DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+       CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+            DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+RESET datestyle;
+
+--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)));
+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;
+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;
+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/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



  [text/x-patch] v15-0016-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patch (6.3K, 7-v15-0016-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patch)
  download | inline diff:
From e9c2d202693dbea0610b30dc32ee75c11548d564 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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 = 'jsonb'::regtype
order by castsource::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)

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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 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)
 
 	for (i = 0; i < lengthof(messages); i++)
 		if (messages[i].type == type)
-			ereport(ERROR,
+			ereturn(escontext,,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg(messages[i].msg, sqltype)));
 
@@ -1851,7 +1851,10 @@ 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)
 	{
@@ -1860,7 +1863,10 @@ 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);
 
@@ -1875,7 +1881,10 @@ 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)
 	{
@@ -1884,7 +1893,10 @@ 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
@@ -1905,7 +1917,10 @@ 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)
 	{
@@ -1914,7 +1929,10 @@ 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));
@@ -1932,7 +1950,10 @@ 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)
 	{
@@ -1941,7 +1962,10 @@ 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));
@@ -1959,7 +1983,10 @@ 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)
 	{
@@ -1968,7 +1995,10 @@ 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));
@@ -1986,7 +2016,10 @@ 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)
 	{
@@ -1995,7 +2028,10 @@ 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));
@@ -2013,7 +2049,10 @@ 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)
 	{
@@ -2022,7 +2061,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
 	}
 
 	if (v.type != jbvNumeric)
-		cannotCastJsonbValue(v.type, "double precision");
+	{
+		cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+		PG_RETURN_NULL();
+	}
 
 	retValue = DirectFunctionCall1(numeric_float8,
 								   NumericGetDatum(v.val.numeric));
-- 
2.34.1



  [text/x-patch] v15-0017-refactor-float_overflow_error-float_underflow_error-float_zero_d.patch (14.2K, 8-v15-0017-refactor-float_overflow_error-float_underflow_error-float_zero_d.patch)
  download | inline diff:
From d357e4541ca62c39982e886df927184714e9d088 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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(-)

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 55c7030ba81..2710e42f5bb 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();
 		}
@@ -3430,7 +3430,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();
@@ -3603,15 +3603,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);
 		if (float8_eq(Cx1, Cx2))
 			Cx = Cx1;
 		else
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..1d0cb026d4e 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,7 @@ 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;
 }
@@ -116,7 +116,7 @@ float8_pl(const float8 val1, const float8 val2)
 
 	result = val1 + val2;
 	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	return result;
 }
@@ -128,7 +128,7 @@ 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;
 }
@@ -140,7 +140,7 @@ float8_mi(const float8 val1, const float8 val2)
 
 	result = val1 - val2;
 	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
+		float_overflow_error(NULL);
 
 	return result;
 }
@@ -152,9 +152,9 @@ 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;
 }
@@ -166,9 +166,9 @@ float8_mul(const float8 val1, const float8 val2)
 
 	result = val1 * val2;
 	if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
-		float_overflow_error();
+		float_overflow_error(NULL);
 	if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
-		float_underflow_error();
+		float_underflow_error(NULL);
 
 	return result;
 }
@@ -179,12 +179,12 @@ 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;
 }
@@ -195,12 +195,12 @@ float8_div(const float8 val1, const float8 val2)
 	float8		result;
 
 	if (unlikely(val2 == 0.0) && !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.0) && val1 != 0.0 && !isinf(val2))
-		float_underflow_error();
+		float_underflow_error(NULL);
 
 	return result;
 }
-- 
2.34.1



  [text/x-patch] v15-0015-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patch (3.1K, 9-v15-0015-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 6dce08e996087a3bba2f766b74b584fef78db9c1 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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)

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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();
 
 	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 c144caf2458..e84cf4b25f8 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);
 }
@@ -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



  [text/x-patch] v15-0014-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patch (3.6K, 10-v15-0014-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 978901cde740075e912ea9a24a82e20f1ec8798a Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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)

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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();
 
 	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")));
 
@@ -2955,7 +2958,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 1e8b859ff29..c144caf2458 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);
 }
@@ -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



  [text/x-patch] v15-0013-error-safe-for-casting-interval-to-other-types-per-pg_cast.patch (2.1K, 11-v15-0013-error-safe-for-casting-interval-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 5f38709a59f0e7a00ec09adb9c935056f78d4028 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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 = 'interval'::regtype
order by castsource::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)

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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 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;
 
 	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 2dc90a2b8a9..1e8b859ff29 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



  [text/x-patch] v15-0012-error-safe-for-casting-date-to-other-types-per-pg_cast.patch (2.2K, 12-v15-0012-error-safe-for-casting-date-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 42d29ceea40e0d1f006f59bfa3b4f55b3cbe454c Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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 = 'date'::regtype
order by castsource::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)

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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();
 
 	PG_RETURN_TIMESTAMP(result);
 }
-- 
2.34.1



  [text/x-patch] v15-0011-error-safe-for-casting-float8-to-other-types-per-pg_cast.patch (3.6K, 13-v15-0011-error-safe-for-casting-float8-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 031b8a5ea41f3ac8ffb35235969b313af01e7ec3 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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 = 'float8'::regtype
order by castsource::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)

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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 d5f15bfa7de..55c7030ba81 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;
 	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 +1229,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 +1254,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")));
 
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 68299e91512..7ff7f267b7e 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")));
 
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index c9d6669a7de..11c76ee40b5 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);
 
 	/* 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



  [text/x-patch] v15-0010-error-safe-for-casting-float4-to-other-types-per-pg_cast.patch (3.1K, 14-v15-0010-error-safe-for-casting-float4-to-other-types-per-pg_cast.patch)
  download | inline diff:
From bc44fa418746256b09c6c1d5ce7c839e10374aa2 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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 = 'float4'::regtype
order by castsource::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)

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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 849639fda9f..d5f15bfa7de 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 */
 	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 +1323,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 d5631f35465..68299e91512 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -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 3e78cdf4ea0..c9d6669a7de 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);
 
 	/* 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



  [text/x-patch] v15-0009-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patch (5.2K, 15-v15-0009-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 1b8c0e0ce6c3d9f45c451de3ae155b07b81f51b8 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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 = 'numeric'::regtype
order by castsource::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)

discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
 src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
 1 file changed, 43 insertions(+), 15 deletions(-)

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 1d626aecbe7..3e78cdf4ea0 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,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);
 
 	if (!numericvar_to_int64(&x, &val))
-		ereport(ERROR,
+		ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
 
@@ -4572,10 +4589,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);
 }
@@ -4667,7 +4688,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



  [text/x-patch] v15-0007-error-safe-for-casting-integer-to-other-types-per-pg_cast.patch (2.9K, 16-v15-0007-error-safe-for-casting-integer-to-other-types-per-pg_cast.patch)
  download | inline diff:
From fd1a8676cf8ba7bff310337b97011a784d2174d4 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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 = 'integer'::regtype
order by castsource::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.

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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



  [text/x-patch] v15-0008-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patch (3.9K, 17-v15-0008-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 4623763149ef4c2aaf822aaa0fc6bead2611e39c Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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 = 'bigint'::regtype
order by castsource::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
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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 9cd420b4b9d..d5631f35465 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



  [text/x-patch] v15-0005-error-safe-for-casting-inet-to-other-types-per-pg_cast.patch (1.8K, 18-v15-0005-error-safe-for-casting-inet-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 287ab2b7d17c850b69ff295f774ed9da6817f84c Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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 = 'inet'::regtype
order by castsource::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)

inet_to_cidr is already error safe.

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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 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)
 
 	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



  [text/x-patch] v15-0006-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patch (1.5K, 19-v15-0006-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patch)
  download | inline diff:
From fef9b1d6ffb6b1e48c5536aefc0fd981b911fa05 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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)

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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



  [text/x-patch] v15-0004-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patch (2.1K, 20-v15-0004-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patch)
  download | inline diff:
From fbc8f5c3c53eafddc10b6d8d9b7f1ea2d67f3c30 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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.
so here we only need handle function "varchar".

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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



  [text/x-patch] v15-0002-error-safe-for-casting-character-to-other-types-per-pg_cast.patch (4.6K, 21-v15-0002-error-safe-for-casting-character-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 78f154c0f30f7cbc209916116638a245984c2a2e Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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'::regtype
order by castsource::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 functions already error safe.

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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 5e7bd933afc..1d88cdd2cb4 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 41e775570ec..9e8016456ce 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);
 
-	PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+	PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
 }
 
 
@@ -1028,19 +1028,25 @@ 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
-	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
 }
 
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 732dac47bc4..b15168c430e 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,
 						   const Datum *named_argvalue, const bool *named_argnull,
 						   const Datum *argvalue, const 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



  [text/x-patch] v15-0001-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patch (2.6K, 22-v15-0001-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 70416850ab1ef1ef8244e51b63e9bf9cbe335300 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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)
 
 	/* Check that the bit string is not too long */
 	if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("integer out of range")));
 
@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)
 
 	/* Check that the bit string is not too long */
 	if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
-		ereport(ERROR,
+		ereturn(fcinfo->context, (Datum) 0,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("bigint out of range")));
 
-- 
2.34.1



  [text/x-patch] v15-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patch (10.2K, 23-v15-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patch)
  download | inline diff:
From 7867024f1ec405db6aa0c91de715bd8707550f3f Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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

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'::regtype
order by castsource::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 functions already error safe.

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 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(-)

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 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 (!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")));
 
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
+	ereturn(escontext, NULL,
 			errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			errmsg("unsupported XML feature"),
 			errdetail("This functionality requires the server to be built with libxml support."));
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);
 
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



view thread (75+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
  Subject: Re: CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions
  In-Reply-To: <CACJufxFEzD3mqc+MDpgzvdt+4Azbn2pF6TWW=dSCqSK7OHoL6A@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox