From 61a8a17e8eac8e131e751a643e69555eda34c8fe Mon Sep 17 00:00:00 2001 From: jian he Date: Wed, 10 Dec 2025 16:15:37 +0800 Subject: [PATCH v16 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. 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$<iteration count>:&l b means that the types are binary-coercible, thus no conversion is required. + + + + casterrorsafe bool + + + This flag indicates whether the castfunc function + is error-safe. It is meaningful only when castfunc + is not zero. User-defined casts can set it + to true via CREATE CAST. + For error-safe type cast, see . + + + + 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 CREATE CAST (source_type AS target_type) - WITH FUNCTION function_name [ (argument_type [, ...]) ] + WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ] [ AS ASSIGNMENT | AS IMPLICIT ] CREATE CAST (source_type AS target_type) @@ -194,6 +194,18 @@ SELECT CAST ( 2 AS numeric ) + 4.0; + + SAFE + + + 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 safe type cast. + + + + function_name[(argument_type [, ...])] 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 ( expression AS type If the type cast fails, instead of error out, evaluation falls back to the default expression specified in the ON ERROR clause. - At present, this only support built-in type casts; see . - User-defined type casts created with CREATE CAST are not supported. + User-defined type casts created with CREATE CAST are supported too. 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 8a435cd93db..0bc9373c7f2 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 0eb8e0a2bb0..36d443205f4 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 9e6573444cc..661d11e57dd 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 drop_option %type 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 grant_role_opt_list %type 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 a10ec64a708..1e1fa5b1f20 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