From c01cb4d504ee637b1164f9e84b95cbacc40a81d4 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 20 Mar 2026 14:14:31 +0800
Subject: [PATCH v22 23/23] 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.

The new synopsis of CREATE CAST is:
CREATE CAST (source_type AS target_type)
    WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]

The following cast in citext, hstore contrib module refactored to error safe:
CAST (bpchar AS citext)
CAST (boolean AS citext)
CAST (inet AS citext)
CAST (text[] AS hstore)
CAST (hstore AS json)
CAST (hstore AS jsonb)

Author: jian he <jian.universality@gmail.com>
Reviewed-by: Amul Sul <sulamul@gmail.com>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Reviewed-by: Corey Huinker <corey.huinker@gmail.com>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 contrib/citext/citext--1.4.sql            |   6 +-
 contrib/citext/expected/citext.out        |  24 +-
 contrib/citext/expected/citext_1.out      |  24 +-
 contrib/citext/sql/citext.sql             |   5 +-
 contrib/hstore/expected/hstore.out        |  37 +++
 contrib/hstore/hstore--1.2--1.3.sql       |   2 +-
 contrib/hstore/hstore--1.4.sql            |   6 +-
 contrib/hstore/hstore_io.c                |   8 +-
 contrib/hstore/sql/hstore.sql             |  11 +
 doc/src/sgml/catalogs.sgml                |  15 +
 doc/src/sgml/ref/create_cast.sgml         |  14 +-
 doc/src/sgml/syntax.sgml                  |   3 +-
 src/backend/catalog/pg_cast.c             |   4 +-
 src/backend/commands/functioncmds.c       |  16 +-
 src/backend/commands/typecmds.c           |   1 +
 src/backend/parser/gram.y                 |  18 +-
 src/backend/parser/parse_expr.c           |  32 +--
 src/include/catalog/pg_cast.dat           | 332 +++++++++++-----------
 src/include/catalog/pg_cast.h             |   5 +
 src/include/nodes/parsenodes.h            |   1 +
 src/include/parser/kwlist.h               |   1 +
 src/test/regress/expected/create_cast.out |   8 +-
 src/test/regress/expected/opr_sanity.out  |  24 +-
 src/test/regress/sql/create_cast.sql      |   5 +
 24 files changed, 369 insertions(+), 233 deletions(-)

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 ecfaf2aa85a..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 3e12cd793ad..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 c820b0bd4d9..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 acea8806ba4..19084f3371d 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -840,12 +840,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              
 --------------------------------
@@ -854,6 +870,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[]);
@@ -1552,6 +1575,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                                             
 -------------------------------------------------------------------------------------------------------------
@@ -1570,6 +1600,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 9b72efb8674..59a45b52a9e 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -739,20 +739,20 @@ hstore_from_array(PG_FUNCTION_ARGS)
 
 		case 1:
 			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")));
 	}
@@ -773,7 +773,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 8ae95e8a510..a803e11ec84 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -190,9 +190,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']);
@@ -361,10 +368,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 61ffe804ee8..fef0e72fe5f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1874,6 +1874,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 aa0fc1ef81c..6edcb9569e8 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2142,9 +2142,8 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
     If the type cast fails, an error is not raised.  Instead, evaluation falls
     back to the default <replaceable>expression</replaceable> specified in the
     <literal>ON CONVERSION ERROR</literal> clause.
-    At present, this only support built-in type casts, see <xref linkend="catalog-pg-cast"/>.
     User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link>
-    are not supported.
+    are supported too.
   </para>
 
     <para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 5119c2acda2..9eff941eabb 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 3afd762e9dc..106e9fc2195 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1668,6 +1668,20 @@ CreateCast(CreateCastStmt *stmt)
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("cast function must not return a set")));
 
+		if (stmt->safe)
+		{
+			if (procstruct->prolang != INTERNALlanguageId &&
+				procstruct->prolang != ClanguageId)
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("safe type cast functions are only supported for C and internal languages"));
+
+			if (sourcetyptype != TYPTYPE_BASE || targettyptype != TYPTYPE_BASE)
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("safe type cast is allowed for base type only are only"));
+		}
+
 		ReleaseSysCache(tuple);
 	}
 	else
@@ -1796,7 +1810,7 @@ CreateCast(CreateCastStmt *stmt)
 	}
 
 	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 3dab6bb5a79..b3713b8513c 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1765,6 +1765,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 	/* Create cast from the range type to its multirange type */
 	CastCreate(typoid, multirangeOid, castFuncOid, InvalidOid, InvalidOid,
 			   COERCION_CODE_EXPLICIT, COERCION_METHOD_FUNCTION,
+			   false,
 			   DEPENDENCY_INTERNAL);
 
 	pfree(multirangeArrayName);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4d692274d55..531da18ed21 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -358,7 +358,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
@@ -813,7 +813,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 SPLIT SOURCE SQL_P STABLE STANDALONE_P
@@ -9403,14 +9403,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;
 				}
@@ -9421,6 +9422,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = false;
@@ -9433,6 +9435,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
 
 					n->sourcetype = $4;
 					n->targettype = $6;
+					n->safe = false;
 					n->func = NULL;
 					n->context = (CoercionContext) $10;
 					n->inout = true;
@@ -9445,6 +9448,9 @@ cast_context:  AS IMPLICIT_P					{ $$ = COERCION_IMPLICIT; }
 		| /*EMPTY*/								{ $$ = COERCION_EXPLICIT; }
 		;
 
+opt_safe: SAFE						{ $$ = true; }
+		| /*EMPTY*/					{ $$ = false; }
+		;
 
 DropCastStmt: DROP CAST opt_if_exists '(' Typename AS Typename ')' opt_drop_behavior
 				{
@@ -18956,6 +18962,7 @@ unreserved_keyword:
 			| ROUTINES
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
@@ -19605,6 +19612,7 @@ bare_label_keyword:
 			| ROW
 			| ROWS
 			| RULE
+			| SAFE
 			| SAVEPOINT
 			| SCALAR
 			| SCHEMA
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index a1e2fdc9a1f..b832838ce16 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -84,8 +84,7 @@ static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
 								   Node *source, Oid inputType, Oid targetType);
 static void CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
-									   bool *errorsafe_coercion,
-									   bool *userdefined);
+									   bool *errorsafe_coercion);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
 											JsonObjectConstructor *ctor);
@@ -2999,7 +2998,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 					   Oid inputType, Oid targetType)
 {
 	bool		errorsafe_coercion = true;
-	bool		userdefined = false;
 
 	/*
 	 * Binary coercion cast is error-safe, CoerceViaIO can also be evaluated
@@ -3011,8 +3009,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 		return;
 
 	CoercionErrorSafe_Internal(inputType, targetType,
-							   &errorsafe_coercion,
-							   &userdefined);
+							   &errorsafe_coercion);
 
 	if (!errorsafe_coercion)
 		ereport(ERROR,
@@ -3022,15 +3019,13 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *source,
 					   format_type_be(targetType),
 					   "DEFAULT",
 					   "CAST ... ON CONVERSION ERROR"),
-				userdefined
-				? errhint("Safe type cast for user-defined types are not yet supported.")
-				: errhint("Explicit cast is defined but definition is not error safe."),
+				errhint("Explicit cast is defined but definition is not error safe."),
 				parser_errposition(pstate, exprLocation(source)));
 }
 
 static void
 CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
-						   bool *errorsafe_coercion, bool *userdefined)
+						   bool *errorsafe_coercion)
 {
 	HeapTuple	tuple;
 
@@ -3041,7 +3036,6 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 	check_stack_depth();
 
 	Assert(errorsafe_coercion != NULL);
-	Assert(userdefined != NULL);
 
 	if (!(*errorsafe_coercion))
 		return;
@@ -3051,24 +3045,21 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 	{
 		CoercionErrorSafe_Internal(getBaseType(inputType),
 								   getBaseType(targetType),
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if (input_typtype == TYPTYPE_DOMAIN)
 	{
 		CoercionErrorSafe_Internal(getBaseType(inputType),
 								   targetType,
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if (target_typtype == TYPTYPE_DOMAIN)
 	{
 		CoercionErrorSafe_Internal(inputType,
 								   getBaseType(targetType),
-								   errorsafe_coercion,
-								   userdefined);
+								   errorsafe_coercion);
 		return;
 	}
 	else if ((input_typtype != TYPTYPE_BASE && input_typtype != TYPTYPE_PSEUDO && inputType > FirstUnpinnedObjectId)
@@ -3102,8 +3093,7 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 			/* recurse into array element type */
 			CoercionErrorSafe_Internal(input_typelem,
 									   target_typelem,
-									   errorsafe_coercion,
-									   userdefined);
+									   errorsafe_coercion);
 			return;
 		}
 	}
@@ -3135,15 +3125,13 @@ CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
 		{
 			Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
 
-			if (castForm->castfunc > FirstUnpinnedObjectId)
+			if (castForm->castmethod != COERCION_METHOD_BINARY &&
+				!castForm->casterrorsafe)
 			{
 				*errorsafe_coercion = false;
-				*userdefined = true;
 			}
 			ReleaseSysCache(tuple);
 		}
-		else if (inputType > FirstUnpinnedObjectId && targetType > FirstUnpinnedObjectId)
-			*errorsafe_coercion = false;
 	}
 }
 
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index a7b6d812c5a..abb65ddd516 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' },
 
@@ -316,63 +316,63 @@
 { 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' },
 { 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 bytea and uuid type
 { castsource => 'bytea', casttarget => 'uuid', castfunc => 'uuid(bytea)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 { castsource => 'uuid', casttarget => 'bytea', castfunc => 'bytea(uuid)',
-  castcontext => 'e', castmethod => 'f' },
+  castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
 
 # 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',
@@ -398,73 +398,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',
@@ -474,13 +474,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
@@ -491,68 +491,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',
@@ -562,36 +562,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 d81314946ee..6deded3e04e 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -49,6 +49,10 @@ CATALOG(pg_cast,2605,CastRelationId)
 
 	/* cast method */
 	char		castmethod;
+
+	/* cast function error safe */
+	bool		casterrorsafe BKI_DEFAULT(f);
+
 } FormData_pg_cast;
 
 END_CATALOG_STRUCT
@@ -105,6 +109,7 @@ extern ObjectAddress CastCreate(Oid sourcetypeid,
 								Oid outcastid,
 								char castcontext,
 								char castmethod,
+								bool casterrorsafe,
 								DependencyType behavior);
 
 #endif							/* PG_CAST_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b45c29d52f4..cedb1180c04 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4242,6 +4242,7 @@ typedef struct CreateCastStmt
 	ObjectWithArgs *func;
 	CoercionContext context;
 	bool		inout;
+	bool		safe;
 } CreateCastStmt;
 
 /* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b7ded6e6088..816e277b67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -406,6 +406,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 68e69d1c584..b3c7d62e526 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 6ff4d7ee901..9943a419695 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -950,8 +950,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,
@@ -960,8 +960,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.
@@ -970,15 +970,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
@@ -996,8 +996,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.*
@@ -1005,8 +1005,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

