From 99d299237edcb65151a94b7ab17497dad6d7b778 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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

