From 2773db72ca0b1c94017713179490b9ee5a569a07 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 22 Nov 2025 00:08:00 +0800 Subject: [PATCH 01/23] error safe for casting bytea to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+------------+----------+-------------+------------+------------+--------- bytea | smallint | 6370 | e | f | bytea_int2 | int2 bytea | integer | 6371 | e | f | bytea_int4 | int4 bytea | bigint | 6372 | e | f | bytea_int8 | int8 (3 rows) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/bytea.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c index 6e7b914c5639..f43ad6b4affb 100644 --- a/src/backend/utils/adt/bytea.c +++ b/src/backend/utils/adt/bytea.c @@ -1027,7 +1027,7 @@ bytea_int2(PG_FUNCTION_ARGS) /* Check that the byte array is not too long */ if (len > sizeof(result)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range")); @@ -1052,7 +1052,7 @@ bytea_int4(PG_FUNCTION_ARGS) /* Check that the byte array is not too long */ if (len > sizeof(result)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range")); @@ -1077,7 +1077,7 @@ bytea_int8(PG_FUNCTION_ARGS) /* Check that the byte array is not too long */ if (len > sizeof(result)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range")); From 980131c12b3b09107df6f33cf37ae9d032de10d6 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 22 Nov 2025 10:33:08 +0800 Subject: [PATCH 02/23] error safe for casting bit/varbit to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or castsource::regtype ='varbit'::regtype) order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname -------------+-------------+----------+-------------+------------+-----------+--------- bit | bigint | 2076 | e | f | bittoint8 | int8 bit | integer | 1684 | e | f | bittoint4 | int4 bit | bit | 1685 | i | f | bit | bit bit varying | bit varying | 1687 | i | f | varbit | varbit (4 rows) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/varbit.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c index 205a67dafc56..6e9b808e20a7 100644 --- a/src/backend/utils/adt/varbit.c +++ b/src/backend/utils/adt/varbit.c @@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS) PG_RETURN_VARBIT_P(arg); if (!isExplicit) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), errmsg("bit string length %d does not match type bit(%d)", VARBITLEN(arg), len))); @@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS) PG_RETURN_VARBIT_P(arg); if (!isExplicit) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), errmsg("bit string too long for type bit varying(%d)", len))); @@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS) /* Check that the bit string is not too long */ if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); @@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS) /* Check that the bit string is not too long */ if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); From 2d874b1937640cadf505a757b37b471324230c7e Mon Sep 17 00:00:00 2001 From: jian he Date: Mon, 24 Nov 2025 12:52:16 +0800 Subject: [PATCH 03/23] error safe for casting character to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='character'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+-------------------+----------+-------------+------------+-------------+--------- character | text | 401 | i | f | rtrim1 | text character | character varying | 401 | i | f | rtrim1 | text character | "char" | 944 | a | f | text_char | char character | name | 409 | i | f | bpchar_name | name character | xml | 2896 | e | f | texttoxml | xml character | character | 668 | i | f | bpchar | bpchar (6 rows) only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling. other functions already error safe. discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/executor/execExprInterp.c | 2 +- src/backend/utils/adt/varchar.c | 2 +- src/backend/utils/adt/xml.c | 18 ++++++++++++------ src/include/utils/xml.h | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 5e7bd933afc3..1d88cdd2cb4b 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op) *op->resvalue = PointerGetDatum(xmlparse(data, xexpr->xmloption, - preserve_whitespace)); + preserve_whitespace, NULL)); *op->resnull = false; } break; diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c index 39fc27e1f11f..8618c7a1b138 100644 --- a/src/backend/utils/adt/varchar.c +++ b/src/backend/utils/adt/varchar.c @@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS) { for (i = maxmblen; i < len; i++) if (s[i] != ' ') - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), errmsg("value too long for type character(%d)", maxlen))); diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index c8ab9d61c683..6301115d6257 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS) { text *data = PG_GETARG_TEXT_PP(0); - PG_RETURN_XML_P(xmlparse(data, xmloption, true)); + PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context)); } @@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr, xmltype * -xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace) +xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext) { #ifdef USE_LIBXML xmlDocPtr doc; doc = xml_parse(data, xmloption_arg, preserve_whitespace, - GetDatabaseEncoding(), NULL, NULL, NULL); - xmlFreeDoc(doc); + GetDatabaseEncoding(), NULL, NULL, escontext); + if (doc) + xmlFreeDoc(doc); + + if (SOFT_ERROR_OCCURRED(escontext)) + return NULL; return (xmltype *) data; #else - NO_XML_SUPPORT(); - return NULL; + ereturn(escontext, NULL + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported XML feature"), + errdetail("This functionality requires the server to be built with libxml support.")); #endif } diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h index 732dac47bc47..b15168c430e5 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args); extern xmltype *xmlelement(XmlExpr *xexpr, const Datum *named_argvalue, const bool *named_argnull, const Datum *argvalue, const bool *argnull); -extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace); +extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext); extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null); extern xmltype *xmlroot(xmltype *data, text *version, int standalone); extern bool xml_is_document(xmltype *arg); From 9415c1cadbb168d57a482c526970ca15974d0054 Mon Sep 17 00:00:00 2001 From: jian he Date: Fri, 12 Dec 2025 15:31:17 +0800 Subject: [PATCH 04/23] error safe for casting text to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'text'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+------------+----------+-------------+------------+---------------+---------- text | regclass | 1079 | i | f | text_regclass | regclass text | "char" | 944 | a | f | text_char | char text | name | 407 | i | f | text_name | name text | xml | 2896 | e | f | texttoxml | xml (4 rows) already error safe: text_name, text_char. texttoxml is refactored in character type error safe patch. discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++------- src/backend/utils/adt/regproc.c | 13 ++++++-- src/backend/utils/adt/varlena.c | 10 ++++-- src/backend/utils/adt/xml.c | 2 +- src/include/catalog/namespace.h | 6 ++++ src/include/utils/varlena.h | 1 + 6 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index c94089caa582..bd25eb4550d8 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -440,6 +440,16 @@ Oid RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg) +{ + return RangeVarGetRelidExtendedSafe(relation, lockmode, flags, + callback, callback_arg, + NULL); +} + +Oid +RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags, + RangeVarGetRelidCallback callback, void *callback_arg, + Node *escontext) { uint64 inval_count; Oid relId; @@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, if (relation->catalogname) { if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0) - ereport(ERROR, + ereturn(escontext, InvalidOid, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cross-database references are not implemented: \"%s.%s.%s\"", relation->catalogname, relation->schemaname, @@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, * return InvalidOid. */ if (namespaceId != myTempNamespace) - ereport(ERROR, + ereturn(escontext, InvalidOid, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("temporary tables cannot specify a schema name"))); } @@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, { int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR; - if (relation->schemaname) - ereport(elevel, + if (relation->schemaname && elevel == DEBUG1) + ereport(DEBUG1, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on relation \"%s.%s\"", relation->schemaname, relation->relname))); - else - ereport(elevel, + else if (relation->schemaname && elevel == ERROR) + ereturn(escontext, InvalidOid, + errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on relation \"%s.%s\"", + relation->schemaname, relation->relname)); + else if (elevel == DEBUG1) + ereport(DEBUG1, + errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on relation \"%s\"", + relation->relname)); + else if (elevel == ERROR) + ereturn(escontext, InvalidOid, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on relation \"%s\"", relation->relname))); @@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, { int elevel = missing_ok ? DEBUG1 : ERROR; - if (relation->schemaname) - ereport(elevel, + if (relation->schemaname && elevel == DEBUG1) + ereport(DEBUG1, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("relation \"%s.%s\" does not exist", relation->schemaname, relation->relname))); - else - ereport(elevel, + else if (relation->schemaname && elevel == ERROR) + ereturn(escontext, InvalidOid, + errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s.%s\" does not exist", + relation->schemaname, relation->relname)); + else if (elevel == DEBUG1) + ereport(DEBUG1, + errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" does not exist", + relation->relname)); + else if (elevel == ERROR) + ereturn(escontext, InvalidOid, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("relation \"%s\" does not exist", relation->relname))); @@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok) */ RangeVar * makeRangeVarFromNameList(const List *names) +{ + return makeRangeVarFromNameListSafe(names, NULL); +} + +RangeVar * +makeRangeVarFromNameListSafe(const List *names, Node *escontext) { RangeVar *rel = makeRangeVar(NULL, NULL, -1); @@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names) rel->relname = strVal(lthird(names)); break; default: - ereport(ERROR, + ereturn(escontext, NULL, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("improper relation name (too many dotted names): %s", NameListToString(names)))); diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c index e5c2246f2c92..59cc508f805b 100644 --- a/src/backend/utils/adt/regproc.c +++ b/src/backend/utils/adt/regproc.c @@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS) text *relname = PG_GETARG_TEXT_PP(0); Oid result; RangeVar *rv; + List *namelist; - rv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + namelist = textToQualifiedNameListSafe(relname, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + PG_RETURN_NULL(); - /* We might not even have permissions on this relation; don't lock it. */ - result = RangeVarGetRelid(rv, NoLock, false); + rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + PG_RETURN_NULL(); + /* We might not even have permissions on this relation; don't lock it. */ + result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL, + fcinfo->context); PG_RETURN_OID(result); } diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index baa5b44ea8dd..ccd601ec34cb 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -2695,6 +2695,12 @@ name_text(PG_FUNCTION_ARGS) */ List * textToQualifiedNameList(text *textval) +{ + return textToQualifiedNameListSafe(textval, NULL); +} + +List * +textToQualifiedNameListSafe(text *textval, Node *escontext) { char *rawname; List *result = NIL; @@ -2706,12 +2712,12 @@ textToQualifiedNameList(text *textval) rawname = text_to_cstring(textval); if (!SplitIdentifierString(rawname, '.', &namelist)) - ereport(ERROR, + ereturn(escontext, NIL, (errcode(ERRCODE_INVALID_NAME), errmsg("invalid name syntax"))); if (namelist == NIL) - ereport(ERROR, + ereturn(escontext, NIL, (errcode(ERRCODE_INVALID_NAME), errmsg("invalid name syntax"))); diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 6301115d6257..dd0955d1e448 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node return (xmltype *) data; #else - ereturn(escontext, NULL + ereturn(escontext, NULL, errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unsupported XML feature"), errdetail("This functionality requires the server to be built with libxml support.")); diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index f1423f28c326..ab61af55ddcb 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg); +extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation, + LOCKMODE lockmode, uint32 flags, + RangeVarGetRelidCallback callback, + void *callback_arg, + Node *escontext); extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation); extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation, LOCKMODE lockmode, @@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname); extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid); extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p); extern RangeVar *makeRangeVarFromNameList(const List *names); +extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext); extern char *NameListToString(const List *names); extern char *NameListToQuotedString(const List *names); diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h index db9fdf72941d..0cf01ae5281a 100644 --- a/src/include/utils/varlena.h +++ b/src/include/utils/varlena.h @@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen, int ins_c, int del_c, int sub_c, int max_d, bool trusted); extern List *textToQualifiedNameList(text *textval); +extern List *textToQualifiedNameListSafe(text *textval, Node *escontext); extern bool SplitIdentifierString(char *rawstring, char separator, List **namelist); extern bool SplitDirectoriesString(char *rawstring, char separator, From 6663f3431ed13ebb88650b33021f21806fb36877 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 22 Nov 2025 00:13:45 +0800 Subject: [PATCH 05/23] error safe for casting character varying to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and (castsource::regtype = 'character varying'::regtype) order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname -------------------+-------------------+----------+-------------+------------+---------------+---------- character varying | regclass | 1079 | i | f | text_regclass | regclass character varying | "char" | 944 | a | f | text_char | char character varying | name | 1400 | i | f | text_name | name character varying | xml | 2896 | e | f | texttoxml | xml character varying | character varying | 669 | i | f | varchar | varchar (5 rows) texttoxml, text_regclass was refactored as error safe in prior patch. text_char, text_name is already error safe. so here we only need handle function "varchar". discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/varchar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c index 8618c7a1b138..e853e0d7dbb2 100644 --- a/src/backend/utils/adt/varchar.c +++ b/src/backend/utils/adt/varchar.c @@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS) { for (i = maxmblen; i < len; i++) if (s_data[i] != ' ') - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), errmsg("value too long for type character varying(%d)", maxlen))); From 4170a08e2a0c7e52abc8fd007f6571c0fc2f31e1 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 22 Nov 2025 10:28:54 +0800 Subject: [PATCH 06/23] error safe for casting inet to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'inet'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+-------------------+----------+-------------+------------+--------------+--------- inet | cidr | 1715 | a | f | inet_to_cidr | cidr inet | text | 730 | a | f | network_show | text inet | character varying | 730 | a | f | network_show | text inet | character | 730 | a | f | network_show | text (4 rows) inet_to_cidr is already error safe. discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/network.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c index 3a2002097ddc..c7e0828764e7 100644 --- a/src/backend/utils/adt/network.c +++ b/src/backend/utils/adt/network.c @@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS) if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip), tmp, sizeof(tmp)) == NULL) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("could not format inet value: %m"))); From 9e6f9957a961fe6260d2257f495fb17e2651d093 Mon Sep 17 00:00:00 2001 From: jian he Date: Fri, 12 Dec 2025 15:03:06 +0800 Subject: [PATCH 07/23] error safe for casting macaddr8 to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+------------+----------+-------------+------------+-------------------+--------- macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr (1 row) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/mac8.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c index ea715a7a0d43..02b5edb49600 100644 --- a/src/backend/utils/adt/mac8.c +++ b/src/backend/utils/adt/mac8.c @@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS) result = palloc0_object(macaddr); if ((addr->d != 0xFF) || (addr->e != 0xFE)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("macaddr8 data out of range to convert to macaddr"), errhint("Only addresses that have FF and FE as values in the " From 680ebbdf56a3d85a551d4d313b4b26b398601966 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 22 Nov 2025 00:20:10 +0800 Subject: [PATCH 08/23] error safe for casting integer to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'integer'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+------------------+----------+-------------+------------+--------------+--------- integer | bigint | 481 | i | f | int48 | int8 integer | smallint | 314 | a | f | i4toi2 | int2 integer | real | 318 | i | f | i4tof | float4 integer | double precision | 316 | i | f | i4tod | float8 integer | numeric | 1740 | i | f | int4_numeric | numeric integer | money | 3811 | a | f | int4_cash | money integer | boolean | 2557 | e | f | int4_bool | bool integer | bytea | 6368 | e | f | int4_bytea | bytea integer | "char" | 78 | e | f | i4tochar | char integer | bit | 1683 | e | f | bitfromint4 | bit (10 rows) only int4_cash, i4toi2, i4tochar need take care of error handling. but support for cash data type is not easy, so only i4toi2, i4tochar function refactoring. discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/char.c | 2 +- src/backend/utils/adt/int.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c index 22dbfc950b1f..e90844a29f0c 100644 --- a/src/backend/utils/adt/char.c +++ b/src/backend/utils/adt/char.c @@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS) int32 arg1 = PG_GETARG_INT32(0); if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("\"char\" out of range"))); diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index 60411ee024de..b4860a94adbf 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS) int32 arg1 = PG_GETARG_INT32(0); if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); From ef625fd17a1a8722d5696897c918bbc63b148658 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 22 Nov 2025 00:22:00 +0800 Subject: [PATCH 09/23] error safe for casting bigint to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'bigint'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+------------------+----------+-------------+------------+--------------+--------- bigint | smallint | 714 | a | f | int82 | int2 bigint | integer | 480 | a | f | int84 | int4 bigint | real | 652 | i | f | i8tof | float4 bigint | double precision | 482 | i | f | i8tod | float8 bigint | numeric | 1781 | i | f | int8_numeric | numeric bigint | money | 3812 | a | f | int8_cash | money bigint | oid | 1287 | i | f | i8tooid | oid bigint | regproc | 1287 | i | f | i8tooid | oid bigint | regprocedure | 1287 | i | f | i8tooid | oid bigint | regoper | 1287 | i | f | i8tooid | oid bigint | regoperator | 1287 | i | f | i8tooid | oid bigint | regclass | 1287 | i | f | i8tooid | oid bigint | regcollation | 1287 | i | f | i8tooid | oid bigint | regtype | 1287 | i | f | i8tooid | oid bigint | regconfig | 1287 | i | f | i8tooid | oid bigint | regdictionary | 1287 | i | f | i8tooid | oid bigint | regrole | 1287 | i | f | i8tooid | oid bigint | regnamespace | 1287 | i | f | i8tooid | oid bigint | regdatabase | 1287 | i | f | i8tooid | oid bigint | bytea | 6369 | e | f | int8_bytea | bytea bigint | bit | 2075 | e | f | bitfromint8 | bit (21 rows) already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8 discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/int8.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 678f971508be..9e1a2c7792b4 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1251,7 +1251,7 @@ int84(PG_FUNCTION_ARGS) int64 arg = PG_GETARG_INT64(0); if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); @@ -1272,7 +1272,7 @@ int82(PG_FUNCTION_ARGS) int64 arg = PG_GETARG_INT64(0); if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); @@ -1355,7 +1355,7 @@ i8tooid(PG_FUNCTION_ARGS) int64 arg = PG_GETARG_INT64(0); if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("OID out of range"))); From 192d8561248943c20bd26fc6d849d83e94e947cc Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 22 Nov 2025 00:25:37 +0800 Subject: [PATCH 10/23] error safe for casting numeric to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'numeric'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+------------------+----------+-------------+------------+----------------+--------- numeric | bigint | 1779 | a | f | numeric_int8 | int8 numeric | smallint | 1783 | a | f | numeric_int2 | int2 numeric | integer | 1744 | a | f | numeric_int4 | int4 numeric | real | 1745 | i | f | numeric_float4 | float4 numeric | double precision | 1746 | i | f | numeric_float8 | float8 numeric | money | 3824 | a | f | numeric_cash | money numeric | numeric | 1703 | i | f | numeric | numeric (7 rows) discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com --- src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 2460698df019..86a169083053 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -1244,7 +1244,8 @@ numeric (PG_FUNCTION_ARGS) */ if (NUMERIC_IS_SPECIAL(num)) { - (void) apply_typmod_special(num, typmod, NULL); + if (!apply_typmod_special(num, typmod, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_NUMERIC(duplicate_numeric(num)); } @@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS) init_var(&var); set_var_from_num(num, &var); - (void) apply_typmod(&var, typmod, NULL); - new = make_result(&var); + if (!apply_typmod(&var, typmod, fcinfo->context)) + PG_RETURN_NULL(); + new = make_result_safe(&var, fcinfo->context); free_var(&var); @@ -3018,7 +3020,10 @@ numeric_mul(PG_FUNCTION_ARGS) Numeric num2 = PG_GETARG_NUMERIC(1); Numeric res; - res = numeric_mul_safe(num1, num2, NULL); + res = numeric_mul_safe(num1, num2, fcinfo->context); + + if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context))) + PG_RETURN_NULL(); PG_RETURN_NUMERIC(res); } @@ -4392,9 +4397,15 @@ numeric_int4_safe(Numeric num, Node *escontext) Datum numeric_int4(PG_FUNCTION_ARGS) { + int32 result; Numeric num = PG_GETARG_NUMERIC(0); - PG_RETURN_INT32(numeric_int4_safe(num, NULL)); + result = numeric_int4_safe(num, fcinfo->context); + + if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context))) + PG_RETURN_NULL(); + + PG_RETURN_INT32(result); } /* @@ -4462,9 +4473,15 @@ numeric_int8_safe(Numeric num, Node *escontext) Datum numeric_int8(PG_FUNCTION_ARGS) { + int64 result; Numeric num = PG_GETARG_NUMERIC(0); - PG_RETURN_INT64(numeric_int8_safe(num, NULL)); + result = numeric_int8_safe(num, fcinfo->context); + + if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context))) + PG_RETURN_NULL(); + + PG_RETURN_INT64(result); } @@ -4488,11 +4505,11 @@ numeric_int2(PG_FUNCTION_ARGS) if (NUMERIC_IS_SPECIAL(num)) { if (NUMERIC_IS_NAN(num)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert NaN to %s", "smallint"))); else - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert infinity to %s", "smallint"))); } @@ -4501,12 +4518,12 @@ numeric_int2(PG_FUNCTION_ARGS) init_var_from_num(num, &x); if (!numericvar_to_int64(&x, &val)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); @@ -4571,10 +4588,14 @@ numeric_float8(PG_FUNCTION_ARGS) tmp = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num))); - - result = DirectFunctionCall1(float8in, CStringGetDatum(tmp)); - - pfree(tmp); + if (!DirectInputFunctionCallSafe(float8in, tmp, + InvalidOid, -1, + (Node *) fcinfo->context, + &result)) + { + pfree(tmp); + PG_RETURN_NULL(); + } PG_RETURN_DATUM(result); } @@ -4666,7 +4687,14 @@ numeric_float4(PG_FUNCTION_ARGS) tmp = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num))); - result = DirectFunctionCall1(float4in, CStringGetDatum(tmp)); + if (!DirectInputFunctionCallSafe(float4in, tmp, + InvalidOid, -1, + (Node *) fcinfo->context, + &result)) + { + pfree(tmp); + PG_RETURN_NULL(); + } pfree(tmp); From 20589fdf058810a4fabb6cbf48512d10e17e7aba Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 22 Nov 2025 00:28:20 +0800 Subject: [PATCH 11/23] error safe for casting float4 to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'float4'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+------------------+----------+-------------+------------+----------------+--------- real | bigint | 653 | a | f | ftoi8 | int8 real | smallint | 238 | a | f | ftoi2 | int2 real | integer | 319 | a | f | ftoi4 | int4 real | double precision | 311 | i | f | ftod | float8 real | numeric | 1742 | a | f | float4_numeric | numeric (5 rows) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/float.c | 4 ++-- src/backend/utils/adt/int8.c | 2 +- src/backend/utils/adt/numeric.c | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 849639fda9f8..d5f15bfa7de0 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -1298,7 +1298,7 @@ ftoi4(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); @@ -1323,7 +1323,7 @@ ftoi2(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 9e1a2c7792b4..1185f975bbd8 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1342,7 +1342,7 @@ ftoi8(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 86a169083053..78c37c044f89 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -4657,7 +4657,8 @@ float4_numeric(PG_FUNCTION_ARGS) init_var(&result); /* Assume we need not worry about leading/trailing spaces */ - (void) set_var_from_str(buf, buf, &result, &endptr, NULL); + if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context)) + PG_RETURN_NULL(); res = make_result(&result); From 27b0800747b3960bd1d699b626209e51a464eef6 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 22 Nov 2025 00:31:53 +0800 Subject: [PATCH 12/23] error safe for casting float8 to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'float8'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------------+------------+----------+-------------+------------+----------------+--------- double precision | bigint | 483 | a | f | dtoi8 | int8 double precision | smallint | 237 | a | f | dtoi2 | int2 double precision | integer | 317 | a | f | dtoi4 | int4 double precision | real | 312 | a | f | dtof | float4 double precision | numeric | 1743 | a | f | float8_numeric | numeric (5 rows) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/float.c | 13 +++++++++---- src/backend/utils/adt/int8.c | 2 +- src/backend/utils/adt/numeric.c | 3 ++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index d5f15bfa7de0..55c7030ba81c 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -1199,9 +1199,14 @@ dtof(PG_FUNCTION_ARGS) result = (float4) num; if (unlikely(isinf(result)) && !isinf(num)) - float_overflow_error(); + ereturn(fcinfo->context, (Datum) 0, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow")); + if (unlikely(result == 0.0f) && num != 0.0) - float_underflow_error(); + ereturn(fcinfo->context, (Datum) 0, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: underflow")); PG_RETURN_FLOAT4(result); } @@ -1224,7 +1229,7 @@ dtoi4(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); @@ -1249,7 +1254,7 @@ dtoi2(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 1185f975bbd8..cb579466b240 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1307,7 +1307,7 @@ dtoi8(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 78c37c044f89..2564f52a3ffb 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -4559,7 +4559,8 @@ float8_numeric(PG_FUNCTION_ARGS) init_var(&result); /* Assume we need not worry about leading/trailing spaces */ - (void) set_var_from_str(buf, buf, &result, &endptr, NULL); + if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context)) + PG_RETURN_NULL(); res = make_result(&result); From 211405c105a6ec6c7ca823e9287e8f5520f59548 Mon Sep 17 00:00:00 2001 From: jian he Date: Tue, 2 Dec 2025 13:15:32 +0800 Subject: [PATCH 13/23] error safe for casting date to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'date'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+-----------------------------+----------+-------------+------------+------------------+------------- date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz (2 rows) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/date.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 421ccc306f67..d9679aa53ab0 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -730,15 +730,6 @@ date2timestamptz_safe(DateADT dateVal, Node *escontext) return result; } -/* - * Promote date to timestamptz, throwing error for overflow. - */ -static TimestampTz -date2timestamptz(DateADT dateVal) -{ - return date2timestamptz_safe(dateVal, NULL); -} - /* * date2timestamp_no_overflow * @@ -1323,7 +1314,9 @@ date_timestamp(PG_FUNCTION_ARGS) DateADT dateVal = PG_GETARG_DATEADT(0); Timestamp result; - result = date2timestamp(dateVal); + result = date2timestamp_safe(dateVal, fcinfo->context); + if(SOFT_ERROR_OCCURRED(fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_TIMESTAMP(result); } @@ -1396,7 +1389,9 @@ date_timestamptz(PG_FUNCTION_ARGS) DateADT dateVal = PG_GETARG_DATEADT(0); TimestampTz result; - result = date2timestamptz(dateVal); + result = date2timestamptz_safe(dateVal, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_TIMESTAMP(result); } From c297cf9e6a7569b7ed8e62beb22c0ea30473460d Mon Sep 17 00:00:00 2001 From: jian he Date: Fri, 12 Dec 2025 15:05:26 +0800 Subject: [PATCH 14/23] error safe for casting interval to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'interval'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+------------------------+----------+-------------+------------+----------------+---------- interval | time without time zone | 1419 | a | f | interval_time | time interval | interval | 1200 | i | f | interval_scale | interval (2 rows) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/date.c | 2 +- src/backend/utils/adt/timestamp.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index d9679aa53ab0..c6ce784b0dc1 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -2106,7 +2106,7 @@ interval_time(PG_FUNCTION_ARGS) TimeADT result; if (INTERVAL_NOT_FINITE(span)) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("cannot convert infinite interval to time"))); diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 3569d201ee15..11f896147ea8 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS) result = palloc_object(Interval); *result = *interval; - AdjustIntervalForTypmod(result, typmod, NULL); + if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_INTERVAL_P(result); } From 6cbc4e9d406068b00c894d3592fb1aaf786e12c1 Mon Sep 17 00:00:00 2001 From: jian he Date: Tue, 2 Dec 2025 12:04:40 +0800 Subject: [PATCH 15/23] error safe for casting timestamptz to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype) order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname --------------------------+-----------------------------+----------+-------------+------------+-----------------------+------------- timestamp with time zone | date | 1178 | a | f | timestamptz_date | date timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz (5 rows) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/date.c | 9 ++++++--- src/backend/utils/adt/timestamp.c | 10 ++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index c6ce784b0dc1..191f78273726 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -1406,7 +1406,10 @@ timestamptz_date(PG_FUNCTION_ARGS) TimestampTz timestamp = PG_GETARG_TIMESTAMP(0); DateADT result; - result = timestamptz2date_safe(timestamp, NULL); + result = timestamptz2date_safe(timestamp, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->args)) + PG_RETURN_NULL(); + PG_RETURN_DATEADT(result); } @@ -2036,7 +2039,7 @@ timestamptz_time(PG_FUNCTION_ARGS) PG_RETURN_NULL(); if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); @@ -2955,7 +2958,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS) PG_RETURN_NULL(); if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 11f896147ea8..d9181ddecef6 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS) result = timestamp; - AdjustTimestampForTypmod(&result, typmod, NULL); + if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_TIMESTAMPTZ(result); } @@ -6494,8 +6495,13 @@ Datum timestamptz_timestamp(PG_FUNCTION_ARGS) { TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + Timestamp result; - PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp)); + result = timestamptz2timestamp_safe(timestamp, fcinfo->context); + if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context))) + PG_RETURN_NULL(); + + PG_RETURN_TIMESTAMP(result); } /* From 8511fb16dbc3c89b89e56afc012495d3c6fb6a22 Mon Sep 17 00:00:00 2001 From: jian he Date: Tue, 2 Dec 2025 12:14:36 +0800 Subject: [PATCH 16/23] error safe for casting timestamp to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype) order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname -----------------------------+-----------------------------+----------+-------------+------------+-----------------------+------------- timestamp without time zone | date | 2029 | a | f | timestamp_date | date timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp (4 rows) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/date.c | 7 +++++-- src/backend/utils/adt/timestamp.c | 10 ++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 191f78273726..305e6c052e6d 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -1330,7 +1330,10 @@ timestamp_date(PG_FUNCTION_ARGS) Timestamp timestamp = PG_GETARG_TIMESTAMP(0); DateADT result; - result = timestamp2date_safe(timestamp, NULL); + result = timestamp2date_safe(timestamp, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + PG_RETURN_NULL(); + PG_RETURN_DATEADT(result); } @@ -2008,7 +2011,7 @@ timestamp_time(PG_FUNCTION_ARGS) PG_RETURN_NULL(); if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index d9181ddecef6..7c3930011e70 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS) result = timestamp; - AdjustTimestampForTypmod(&result, typmod, NULL); + if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_TIMESTAMP(result); } @@ -6432,8 +6433,13 @@ Datum timestamp_timestamptz(PG_FUNCTION_ARGS) { Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + TimestampTz result; - PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp)); + result = timestamp2timestamptz_safe(timestamp, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + PG_RETURN_NULL(); + + PG_RETURN_TIMESTAMPTZ(result); } /* From 6ce43ddf115cc112884cacb4649a79621765a338 Mon Sep 17 00:00:00 2001 From: jian he Date: Mon, 24 Nov 2025 14:26:53 +0800 Subject: [PATCH 17/23] error safe for casting jsonb to other types per pg_cast select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype = 'jsonb'::regtype order by castsource::regtype; castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname ------------+------------------+----------+-------------+------------+---------------+--------- jsonb | boolean | 3556 | e | f | jsonb_bool | bool jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric jsonb | smallint | 3450 | e | f | jsonb_int2 | int2 jsonb | integer | 3451 | e | f | jsonb_int4 | int4 jsonb | bigint | 3452 | e | f | jsonb_int8 | int8 jsonb | real | 3453 | e | f | jsonb_float4 | float4 jsonb | double precision | 2580 | e | f | jsonb_float8 | float8 (7 rows) discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index dcf84c3fddc2..2c074bcad6ae 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -1818,7 +1818,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res) * Emit correct, translatable cast error message */ static void -cannotCastJsonbValue(enum jbvType type, const char *sqltype) +cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext) { static const struct { @@ -1839,7 +1839,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype) for (i = 0; i < lengthof(messages); i++) if (messages[i].type == type) - ereport(ERROR, + ereturn(escontext,, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(messages[i].msg, sqltype))); @@ -1854,7 +1854,10 @@ jsonb_bool(PG_FUNCTION_ARGS) JsonbValue v; if (!JsonbExtractScalar(&in->root, &v)) - cannotCastJsonbValue(v.type, "boolean"); + { + cannotCastJsonbValue(v.type, "boolean", fcinfo->context); + PG_RETURN_NULL(); + } if (v.type == jbvNull) { @@ -1863,7 +1866,10 @@ jsonb_bool(PG_FUNCTION_ARGS) } if (v.type != jbvBool) - cannotCastJsonbValue(v.type, "boolean"); + { + cannotCastJsonbValue(v.type, "boolean", fcinfo->context); + PG_RETURN_NULL(); + } PG_FREE_IF_COPY(in, 0); @@ -1878,7 +1884,10 @@ jsonb_numeric(PG_FUNCTION_ARGS) Numeric retValue; if (!JsonbExtractScalar(&in->root, &v)) - cannotCastJsonbValue(v.type, "numeric"); + { + cannotCastJsonbValue(v.type, "numeric", fcinfo->context); + PG_RETURN_NULL(); + } if (v.type == jbvNull) { @@ -1887,7 +1896,10 @@ jsonb_numeric(PG_FUNCTION_ARGS) } if (v.type != jbvNumeric) - cannotCastJsonbValue(v.type, "numeric"); + { + cannotCastJsonbValue(v.type, "numeric", fcinfo->context); + PG_RETURN_NULL(); + } /* * v.val.numeric points into jsonb body, so we need to make a copy to @@ -1908,7 +1920,10 @@ jsonb_int2(PG_FUNCTION_ARGS) Datum retValue; if (!JsonbExtractScalar(&in->root, &v)) - cannotCastJsonbValue(v.type, "smallint"); + { + cannotCastJsonbValue(v.type, "smallint", fcinfo->context); + PG_RETURN_NULL(); + } if (v.type == jbvNull) { @@ -1917,7 +1932,10 @@ jsonb_int2(PG_FUNCTION_ARGS) } if (v.type != jbvNumeric) - cannotCastJsonbValue(v.type, "smallint"); + { + cannotCastJsonbValue(v.type, "smallint", fcinfo->context); + PG_RETURN_NULL(); + } retValue = DirectFunctionCall1(numeric_int2, NumericGetDatum(v.val.numeric)); @@ -1935,7 +1953,10 @@ jsonb_int4(PG_FUNCTION_ARGS) Datum retValue; if (!JsonbExtractScalar(&in->root, &v)) - cannotCastJsonbValue(v.type, "integer"); + { + cannotCastJsonbValue(v.type, "integer", fcinfo->context); + PG_RETURN_NULL(); + } if (v.type == jbvNull) { @@ -1944,7 +1965,10 @@ jsonb_int4(PG_FUNCTION_ARGS) } if (v.type != jbvNumeric) - cannotCastJsonbValue(v.type, "integer"); + { + cannotCastJsonbValue(v.type, "integer", fcinfo->context); + PG_RETURN_NULL(); + } retValue = DirectFunctionCall1(numeric_int4, NumericGetDatum(v.val.numeric)); @@ -1962,7 +1986,10 @@ jsonb_int8(PG_FUNCTION_ARGS) Datum retValue; if (!JsonbExtractScalar(&in->root, &v)) - cannotCastJsonbValue(v.type, "bigint"); + { + cannotCastJsonbValue(v.type, "bigint", fcinfo->context); + PG_RETURN_NULL(); + } if (v.type == jbvNull) { @@ -1971,7 +1998,10 @@ jsonb_int8(PG_FUNCTION_ARGS) } if (v.type != jbvNumeric) - cannotCastJsonbValue(v.type, "bigint"); + { + cannotCastJsonbValue(v.type, "bigint", fcinfo->context); + PG_RETURN_NULL(); + } retValue = DirectFunctionCall1(numeric_int8, NumericGetDatum(v.val.numeric)); @@ -1989,7 +2019,10 @@ jsonb_float4(PG_FUNCTION_ARGS) Datum retValue; if (!JsonbExtractScalar(&in->root, &v)) - cannotCastJsonbValue(v.type, "real"); + { + cannotCastJsonbValue(v.type, "real", fcinfo->context); + PG_RETURN_NULL(); + } if (v.type == jbvNull) { @@ -1998,7 +2031,10 @@ jsonb_float4(PG_FUNCTION_ARGS) } if (v.type != jbvNumeric) - cannotCastJsonbValue(v.type, "real"); + { + cannotCastJsonbValue(v.type, "real", fcinfo->context); + PG_RETURN_NULL(); + } retValue = DirectFunctionCall1(numeric_float4, NumericGetDatum(v.val.numeric)); @@ -2016,7 +2052,10 @@ jsonb_float8(PG_FUNCTION_ARGS) Datum retValue; if (!JsonbExtractScalar(&in->root, &v)) - cannotCastJsonbValue(v.type, "double precision"); + { + cannotCastJsonbValue(v.type, "double precision", fcinfo->context); + PG_RETURN_NULL(); + } if (v.type == jbvNull) { @@ -2025,7 +2064,10 @@ jsonb_float8(PG_FUNCTION_ARGS) } if (v.type != jbvNumeric) - cannotCastJsonbValue(v.type, "double precision"); + { + cannotCastJsonbValue(v.type, "double precision", fcinfo->context); + PG_RETURN_NULL(); + } retValue = DirectFunctionCall1(numeric_float8, NumericGetDatum(v.val.numeric)); From e1bc99ea8284711a448dfbcd18d8a6af70236ba9 Mon Sep 17 00:00:00 2001 From: jian he Date: Tue, 9 Dec 2025 14:36:39 +0800 Subject: [PATCH 18/23] refactor float_overflow_error,float_underflow_error,float_zero_divide_error discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- contrib/btree_gist/btree_float4.c | 2 +- contrib/btree_gist/btree_float8.c | 4 +- src/backend/utils/adt/float.c | 104 +++++++++++++++--------------- src/include/utils/float.h | 34 +++++----- 4 files changed, 72 insertions(+), 72 deletions(-) diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c index d9c859835dac..a7325a7bb29d 100644 --- a/contrib/btree_gist/btree_float4.c +++ b/contrib/btree_gist/btree_float4.c @@ -101,7 +101,7 @@ float4_dist(PG_FUNCTION_ARGS) r = a - b; if (unlikely(isinf(r)) && !isinf(a) && !isinf(b)) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT4(fabsf(r)); } diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c index 567beede178a..7c99b84de352 100644 --- a/contrib/btree_gist/btree_float8.c +++ b/contrib/btree_gist/btree_float8.c @@ -79,7 +79,7 @@ gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo) r = arg1 - arg2; if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2)) - float_overflow_error(); + float_overflow_error(NULL); return fabs(r); } @@ -109,7 +109,7 @@ float8_dist(PG_FUNCTION_ARGS) r = a - b; if (unlikely(isinf(r)) && !isinf(a) && !isinf(b)) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(fabs(r)); } diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 55c7030ba81c..2710e42f5bb8 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -83,25 +83,25 @@ static void init_degree_constants(void); * This does mean that you don't get a useful error location indicator. */ pg_noinline void -float_overflow_error(void) +float_overflow_error(struct Node *escontext) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value out of range: overflow"))); } pg_noinline void -float_underflow_error(void) +float_underflow_error(struct Node *escontext) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value out of range: underflow"))); } pg_noinline void -float_zero_divide_error(void) +float_zero_divide_error(struct Node *escontext) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); } @@ -1460,9 +1460,9 @@ dsqrt(PG_FUNCTION_ARGS) result = sqrt(arg1); if (unlikely(isinf(result)) && !isinf(arg1)) - float_overflow_error(); + float_overflow_error(NULL); if (unlikely(result == 0.0) && arg1 != 0.0) - float_underflow_error(); + float_underflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -1479,9 +1479,9 @@ dcbrt(PG_FUNCTION_ARGS) result = cbrt(arg1); if (unlikely(isinf(result)) && !isinf(arg1)) - float_overflow_error(); + float_overflow_error(NULL); if (unlikely(result == 0.0) && arg1 != 0.0) - float_underflow_error(); + float_underflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -1617,24 +1617,24 @@ dpow(PG_FUNCTION_ARGS) if (absx == 1.0) result = 1.0; else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0)) - float_overflow_error(); + float_overflow_error(NULL); else - float_underflow_error(); + float_underflow_error(NULL); } } else if (errno == ERANGE) { if (result != 0.0) - float_overflow_error(); + float_overflow_error(NULL); else - float_underflow_error(); + float_underflow_error(NULL); } else { if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); if (unlikely(result == 0.0) && arg1 != 0.0) - float_underflow_error(); + float_underflow_error(NULL); } } @@ -1674,14 +1674,14 @@ dexp(PG_FUNCTION_ARGS) if (unlikely(errno == ERANGE)) { if (result != 0.0) - float_overflow_error(); + float_overflow_error(NULL); else - float_underflow_error(); + float_underflow_error(NULL); } else if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); else if (unlikely(result == 0.0)) - float_underflow_error(); + float_underflow_error(NULL); } PG_RETURN_FLOAT8(result); @@ -1712,9 +1712,9 @@ dlog1(PG_FUNCTION_ARGS) result = log(arg1); if (unlikely(isinf(result)) && !isinf(arg1)) - float_overflow_error(); + float_overflow_error(NULL); if (unlikely(result == 0.0) && arg1 != 1.0) - float_underflow_error(); + float_underflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -1745,9 +1745,9 @@ dlog10(PG_FUNCTION_ARGS) result = log10(arg1); if (unlikely(isinf(result)) && !isinf(arg1)) - float_overflow_error(); + float_overflow_error(NULL); if (unlikely(result == 0.0) && arg1 != 1.0) - float_underflow_error(); + float_underflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -1778,7 +1778,7 @@ dacos(PG_FUNCTION_ARGS) result = acos(arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -1809,7 +1809,7 @@ dasin(PG_FUNCTION_ARGS) result = asin(arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -1835,7 +1835,7 @@ datan(PG_FUNCTION_ARGS) */ result = atan(arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -1861,7 +1861,7 @@ datan2(PG_FUNCTION_ARGS) */ result = atan2(arg1, arg2); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -1902,7 +1902,7 @@ dcos(PG_FUNCTION_ARGS) (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -1957,7 +1957,7 @@ dsin(PG_FUNCTION_ARGS) (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2137,7 +2137,7 @@ dacosd(PG_FUNCTION_ARGS) result = 90.0 + asind_q1(-arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2174,7 +2174,7 @@ dasind(PG_FUNCTION_ARGS) result = -asind_q1(-arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2206,7 +2206,7 @@ datand(PG_FUNCTION_ARGS) result = (atan_arg1 / atan_1_0) * 45.0; if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2242,7 +2242,7 @@ datan2d(PG_FUNCTION_ARGS) result = (atan2_arg1_arg2 / atan_1_0) * 45.0; if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2365,7 +2365,7 @@ dcosd(PG_FUNCTION_ARGS) result = sign * cosd_q1(arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2487,7 +2487,7 @@ dsind(PG_FUNCTION_ARGS) result = sign * sind_q1(arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2645,7 +2645,7 @@ dcosh(PG_FUNCTION_ARGS) result = get_float8_infinity(); if (unlikely(result == 0.0)) - float_underflow_error(); + float_underflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2665,7 +2665,7 @@ dtanh(PG_FUNCTION_ARGS) result = tanh(arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2765,7 +2765,7 @@ derf(PG_FUNCTION_ARGS) result = erf(arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2785,7 +2785,7 @@ derfc(PG_FUNCTION_ARGS) result = erfc(arg1); if (unlikely(isinf(result))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -2814,7 +2814,7 @@ dgamma(PG_FUNCTION_ARGS) /* Per POSIX, an input of -Inf causes a domain error */ if (arg1 < 0) { - float_overflow_error(); + float_overflow_error(NULL); result = get_float8_nan(); /* keep compiler quiet */ } else @@ -2836,12 +2836,12 @@ dgamma(PG_FUNCTION_ARGS) if (errno != 0 || isinf(result) || isnan(result)) { if (result != 0.0) - float_overflow_error(); + float_overflow_error(NULL); else - float_underflow_error(); + float_underflow_error(NULL); } else if (result == 0.0) - float_underflow_error(); + float_underflow_error(NULL); } PG_RETURN_FLOAT8(result); @@ -2873,7 +2873,7 @@ dlgamma(PG_FUNCTION_ARGS) * to report overflow, but it should never underflow. */ if (errno == ERANGE || (isinf(result) && !isinf(arg1))) - float_overflow_error(); + float_overflow_error(NULL); PG_RETURN_FLOAT8(result); } @@ -3013,7 +3013,7 @@ float8_combine(PG_FUNCTION_ARGS) tmp = Sx1 / N1 - Sx2 / N2; Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N; if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2)) - float_overflow_error(); + float_overflow_error(NULL); } /* @@ -3080,7 +3080,7 @@ float8_accum(PG_FUNCTION_ARGS) if (isinf(Sx) || isinf(Sxx)) { if (!isinf(transvalues[1]) && !isinf(newval)) - float_overflow_error(); + float_overflow_error(NULL); Sxx = get_float8_nan(); } @@ -3163,7 +3163,7 @@ float4_accum(PG_FUNCTION_ARGS) if (isinf(Sx) || isinf(Sxx)) { if (!isinf(transvalues[1]) && !isinf(newval)) - float_overflow_error(); + float_overflow_error(NULL); Sxx = get_float8_nan(); } @@ -3430,7 +3430,7 @@ float8_regr_accum(PG_FUNCTION_ARGS) (isinf(Sxy) && !isinf(transvalues[1]) && !isinf(newvalX) && !isinf(transvalues[3]) && !isinf(newvalY))) - float_overflow_error(); + float_overflow_error(NULL); if (isinf(Sxx)) Sxx = get_float8_nan(); @@ -3603,15 +3603,15 @@ float8_regr_combine(PG_FUNCTION_ARGS) tmp1 = Sx1 / N1 - Sx2 / N2; Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N; if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2)) - float_overflow_error(); + float_overflow_error(NULL); Sy = float8_pl(Sy1, Sy2); tmp2 = Sy1 / N1 - Sy2 / N2; Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N; if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2)) - float_overflow_error(); + float_overflow_error(NULL); Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N; if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2)) - float_overflow_error(); + float_overflow_error(NULL); if (float8_eq(Cx1, Cx2)) Cx = Cx1; else diff --git a/src/include/utils/float.h b/src/include/utils/float.h index fc2a9cf6475f..1d0cb026d4e1 100644 --- a/src/include/utils/float.h +++ b/src/include/utils/float.h @@ -30,9 +30,9 @@ extern PGDLLIMPORT int extra_float_digits; /* * Utility functions in float.c */ -pg_noreturn extern void float_overflow_error(void); -pg_noreturn extern void float_underflow_error(void); -pg_noreturn extern void float_zero_divide_error(void); +extern void float_overflow_error(struct Node *escontext); +extern void float_underflow_error(struct Node *escontext); +extern void float_zero_divide_error(struct Node *escontext); extern int is_infinite(float8 val); extern float8 float8in_internal(char *num, char **endptr_p, const char *type_name, const char *orig_string, @@ -104,7 +104,7 @@ float4_pl(const float4 val1, const float4 val2) result = val1 + val2; if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) - float_overflow_error(); + float_overflow_error(NULL); return result; } @@ -116,7 +116,7 @@ float8_pl(const float8 val1, const float8 val2) result = val1 + val2; if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) - float_overflow_error(); + float_overflow_error(NULL); return result; } @@ -128,7 +128,7 @@ float4_mi(const float4 val1, const float4 val2) result = val1 - val2; if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) - float_overflow_error(); + float_overflow_error(NULL); return result; } @@ -140,7 +140,7 @@ float8_mi(const float8 val1, const float8 val2) result = val1 - val2; if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) - float_overflow_error(); + float_overflow_error(NULL); return result; } @@ -152,9 +152,9 @@ float4_mul(const float4 val1, const float4 val2) result = val1 * val2; if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) - float_overflow_error(); + float_overflow_error(NULL); if (unlikely(result == 0.0f) && val1 != 0.0f && val2 != 0.0f) - float_underflow_error(); + float_underflow_error(NULL); return result; } @@ -166,9 +166,9 @@ float8_mul(const float8 val1, const float8 val2) result = val1 * val2; if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) - float_overflow_error(); + float_overflow_error(NULL); if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0) - float_underflow_error(); + float_underflow_error(NULL); return result; } @@ -179,12 +179,12 @@ float4_div(const float4 val1, const float4 val2) float4 result; if (unlikely(val2 == 0.0f) && !isnan(val1)) - float_zero_divide_error(); + float_zero_divide_error(NULL); result = val1 / val2; if (unlikely(isinf(result)) && !isinf(val1)) - float_overflow_error(); + float_overflow_error(NULL); if (unlikely(result == 0.0f) && val1 != 0.0f && !isinf(val2)) - float_underflow_error(); + float_underflow_error(NULL); return result; } @@ -195,12 +195,12 @@ float8_div(const float8 val1, const float8 val2) float8 result; if (unlikely(val2 == 0.0) && !isnan(val1)) - float_zero_divide_error(); + float_zero_divide_error(NULL); result = val1 / val2; if (unlikely(isinf(result)) && !isinf(val1)) - float_overflow_error(); + float_overflow_error(NULL); if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2)) - float_underflow_error(); + float_underflow_error(NULL); return result; } From d68896b1199f0ac9dd4ee96374db8b1a41a7934e Mon Sep 17 00:00:00 2001 From: jian he Date: Wed, 10 Dec 2025 11:55:40 +0800 Subject: [PATCH 19/23] introduce float8 safe function this patch introduce the following function: float8_pl_safe float8_mi_safe float8_mul_safe float8_div_safe refactoring existing function is be too invasive. thus add these new functions. It's close to existing non-safe version functions. discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/include/utils/float.h | 63 +++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/include/utils/float.h b/src/include/utils/float.h index 1d0cb026d4e1..f46861ab5c3f 100644 --- a/src/include/utils/float.h +++ b/src/include/utils/float.h @@ -110,17 +110,25 @@ float4_pl(const float4 val1, const float4 val2) } static inline float8 -float8_pl(const float8 val1, const float8 val2) +float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext) { float8 result; result = val1 + val2; if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) - float_overflow_error(NULL); - + { + float_overflow_error(escontext); + return 0.0; + } return result; } +static inline float8 +float8_pl(const float8 val1, const float8 val2) +{ + return float8_pl_safe(val1, val2, NULL);; +} + static inline float4 float4_mi(const float4 val1, const float4 val2) { @@ -134,17 +142,25 @@ float4_mi(const float4 val1, const float4 val2) } static inline float8 -float8_mi(const float8 val1, const float8 val2) +float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext) { float8 result; result = val1 - val2; if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) - float_overflow_error(NULL); - + { + float_overflow_error(escontext); + return 0.0; + } return result; } +static inline float8 +float8_mi(const float8 val1, const float8 val2) +{ + return float8_mi_safe(val1, val2, NULL); +} + static inline float4 float4_mul(const float4 val1, const float4 val2) { @@ -160,19 +176,33 @@ float4_mul(const float4 val1, const float4 val2) } static inline float8 -float8_mul(const float8 val1, const float8 val2) +float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext) { float8 result; result = val1 * val2; if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) - float_overflow_error(NULL); + { + float_overflow_error(escontext); + return 0.0; + } + if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0) - float_underflow_error(NULL); + { + float_underflow_error(escontext); + return 0.0; + } return result; } +static inline float8 +float8_mul(const float8 val1, const float8 val2) +{ + return float8_mul_safe(val1, val2, NULL); +} + + static inline float4 float4_div(const float4 val1, const float4 val2) { @@ -190,21 +220,28 @@ float4_div(const float4 val1, const float4 val2) } static inline float8 -float8_div(const float8 val1, const float8 val2) +float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext) { float8 result; if (unlikely(val2 == 0.0) && !isnan(val1)) - float_zero_divide_error(NULL); + float_zero_divide_error(escontext); result = val1 / val2; if (unlikely(isinf(result)) && !isinf(val1)) - float_overflow_error(NULL); + float_overflow_error(escontext); if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2)) - float_underflow_error(NULL); + float_underflow_error(escontext); return result; } +static inline float8 +float8_div(const float8 val1, const float8 val2) +{ + return float8_div_safe(val1, val2, NULL); +} + + /* * Routines for NaN-aware comparisons * From 2ea5ed4d6860383dcc1a695b012a89675f6d3327 Mon Sep 17 00:00:00 2001 From: jian he Date: Wed, 10 Dec 2025 12:00:54 +0800 Subject: [PATCH 20/23] refactor point_dt point_dt used in many places, it will be used in later on patch, thus refactoring make it error safe. discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- src/backend/utils/adt/geo_ops.c | 77 +++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c index 43b7eb43a79c..5c3b9e2ed607 100644 --- a/src/backend/utils/adt/geo_ops.c +++ b/src/backend/utils/adt/geo_ops.c @@ -82,7 +82,7 @@ static inline void point_sub_point(Point *result, Point *pt1, Point *pt2); static inline void point_mul_point(Point *result, Point *pt1, Point *pt2); static inline void point_div_point(Point *result, Point *pt1, Point *pt2); static inline bool point_eq_point(Point *pt1, Point *pt2); -static inline float8 point_dt(Point *pt1, Point *pt2); +static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext); static inline float8 point_sl(Point *pt1, Point *pt2); static int point_inside(Point *p, int npts, Point *plist); @@ -839,7 +839,7 @@ box_distance(PG_FUNCTION_ARGS) box_cn(&a, box1); box_cn(&b, box2); - PG_RETURN_FLOAT8(point_dt(&a, &b)); + PG_RETURN_FLOAT8(point_dt(&a, &b, NULL)); } @@ -1808,7 +1808,7 @@ path_length(PG_FUNCTION_ARGS) iprev = path->npts - 1; /* include the closure segment */ } - result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i])); + result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i], NULL)); } PG_RETURN_FLOAT8(result); @@ -1995,13 +1995,24 @@ point_distance(PG_FUNCTION_ARGS) Point *pt1 = PG_GETARG_POINT_P(0); Point *pt2 = PG_GETARG_POINT_P(1); - PG_RETURN_FLOAT8(point_dt(pt1, pt2)); + PG_RETURN_FLOAT8(point_dt(pt1, pt2, NULL)); } static inline float8 -point_dt(Point *pt1, Point *pt2) +point_dt(Point *pt1, Point *pt2, Node *escontext) { - return hypot(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y)); + float8 x; + float8 y; + + x = float8_mi_safe(pt1->x, pt2->x, escontext); + if (unlikely(SOFT_ERROR_OCCURRED(escontext))) + return 0.0; + + y = float8_mi_safe(pt1->y, pt2->y, escontext); + if (unlikely(SOFT_ERROR_OCCURRED(escontext))) + return 0.0; + + return hypot(x, y); } Datum @@ -2173,7 +2184,7 @@ lseg_length(PG_FUNCTION_ARGS) { LSEG *lseg = PG_GETARG_LSEG_P(0); - PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1])); + PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1], NULL)); } /*---------------------------------------------------------- @@ -2258,8 +2269,8 @@ lseg_lt(PG_FUNCTION_ARGS) LSEG *l1 = PG_GETARG_LSEG_P(0); LSEG *l2 = PG_GETARG_LSEG_P(1); - PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]), - point_dt(&l2->p[0], &l2->p[1]))); + PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1], NULL), + point_dt(&l2->p[0], &l2->p[1], NULL))); } Datum @@ -2268,8 +2279,8 @@ lseg_le(PG_FUNCTION_ARGS) LSEG *l1 = PG_GETARG_LSEG_P(0); LSEG *l2 = PG_GETARG_LSEG_P(1); - PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]), - point_dt(&l2->p[0], &l2->p[1]))); + PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1], NULL), + point_dt(&l2->p[0], &l2->p[1], NULL))); } Datum @@ -2278,8 +2289,8 @@ lseg_gt(PG_FUNCTION_ARGS) LSEG *l1 = PG_GETARG_LSEG_P(0); LSEG *l2 = PG_GETARG_LSEG_P(1); - PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]), - point_dt(&l2->p[0], &l2->p[1]))); + PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1], NULL), + point_dt(&l2->p[0], &l2->p[1], NULL))); } Datum @@ -2288,8 +2299,8 @@ lseg_ge(PG_FUNCTION_ARGS) LSEG *l1 = PG_GETARG_LSEG_P(0); LSEG *l2 = PG_GETARG_LSEG_P(1); - PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]), - point_dt(&l2->p[0], &l2->p[1]))); + PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1], NULL), + point_dt(&l2->p[0], &l2->p[1], NULL))); } @@ -2743,7 +2754,7 @@ line_closept_point(Point *result, LINE *line, Point *point) if (result != NULL) *result = closept; - return point_dt(&closept, point); + return point_dt(&closept, point, NULL); } Datum @@ -2784,7 +2795,7 @@ lseg_closept_point(Point *result, LSEG *lseg, Point *pt) if (result != NULL) *result = closept; - return point_dt(&closept, pt); + return point_dt(&closept, pt, NULL); } Datum @@ -3108,9 +3119,9 @@ on_pl(PG_FUNCTION_ARGS) static bool lseg_contain_point(LSEG *lseg, Point *pt) { - return FPeq(point_dt(pt, &lseg->p[0]) + - point_dt(pt, &lseg->p[1]), - point_dt(&lseg->p[0], &lseg->p[1])); + return FPeq(point_dt(pt, &lseg->p[0], NULL) + + point_dt(pt, &lseg->p[1], NULL), + point_dt(&lseg->p[0], &lseg->p[1], NULL)); } Datum @@ -3176,11 +3187,11 @@ on_ppath(PG_FUNCTION_ARGS) if (!path->closed) { n = path->npts - 1; - a = point_dt(pt, &path->p[0]); + a = point_dt(pt, &path->p[0], NULL); for (i = 0; i < n; i++) { - b = point_dt(pt, &path->p[i + 1]); - if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1]))) + b = point_dt(pt, &path->p[i + 1], NULL); + if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1], NULL))) PG_RETURN_BOOL(true); a = b; } @@ -4766,7 +4777,7 @@ circle_overlap(PG_FUNCTION_ARGS) CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); - PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center), + PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL), float8_pl(circle1->radius, circle2->radius))); } @@ -4828,7 +4839,7 @@ circle_contained(PG_FUNCTION_ARGS) CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); - PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center), + PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL), float8_mi(circle2->radius, circle1->radius))); } @@ -4840,7 +4851,7 @@ circle_contain(PG_FUNCTION_ARGS) CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); - PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center), + PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL), float8_mi(circle1->radius, circle2->radius))); } @@ -5069,7 +5080,7 @@ circle_distance(PG_FUNCTION_ARGS) CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); float8 result; - result = float8_mi(point_dt(&circle1->center, &circle2->center), + result = float8_mi(point_dt(&circle1->center, &circle2->center, NULL), float8_pl(circle1->radius, circle2->radius)); if (result < 0.0) result = 0.0; @@ -5085,7 +5096,7 @@ circle_contain_pt(PG_FUNCTION_ARGS) Point *point = PG_GETARG_POINT_P(1); float8 d; - d = point_dt(&circle->center, point); + d = point_dt(&circle->center, point, NULL); PG_RETURN_BOOL(d <= circle->radius); } @@ -5097,7 +5108,7 @@ pt_contained_circle(PG_FUNCTION_ARGS) CIRCLE *circle = PG_GETARG_CIRCLE_P(1); float8 d; - d = point_dt(&circle->center, point); + d = point_dt(&circle->center, point, NULL); PG_RETURN_BOOL(d <= circle->radius); } @@ -5112,7 +5123,7 @@ dist_pc(PG_FUNCTION_ARGS) CIRCLE *circle = PG_GETARG_CIRCLE_P(1); float8 result; - result = float8_mi(point_dt(point, &circle->center), + result = float8_mi(point_dt(point, &circle->center, NULL), circle->radius); if (result < 0.0) result = 0.0; @@ -5130,7 +5141,7 @@ dist_cpoint(PG_FUNCTION_ARGS) Point *point = PG_GETARG_POINT_P(1); float8 result; - result = float8_mi(point_dt(point, &circle->center), circle->radius); + result = float8_mi(point_dt(point, &circle->center, NULL), circle->radius); if (result < 0.0) result = 0.0; @@ -5215,7 +5226,7 @@ box_circle(PG_FUNCTION_ARGS) circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0); circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0); - circle->radius = point_dt(&circle->center, &box->high); + circle->radius = point_dt(&circle->center, &box->high, NULL); PG_RETURN_CIRCLE_P(circle); } @@ -5299,7 +5310,7 @@ poly_to_circle(CIRCLE *result, POLYGON *poly) for (i = 0; i < poly->npts; i++) result->radius = float8_pl(result->radius, - point_dt(&poly->p[i], &result->center)); + point_dt(&poly->p[i], &result->center, NULL)); result->radius = float8_div(result->radius, poly->npts); } From bf5ef3ff5c346054592b35fb26af829c8ee9b009 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 13 Dec 2025 22:57:21 +0800 Subject: [PATCH 21/23] error safe for casting geometry data type select castsource::regtype, casttarget::regtype, pp.prosrc from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc join pg_type pt on pt.oid = castsource join pg_type pt1 on pt1.oid = casttarget and pc.castfunc > 0 and pt.typarray <> 0 and pt.typnamespace = 'pg_catalog'::regnamespace and pt1.typnamespace = 'pg_catalog'::regnamespace and (pt.typcategory = 'G' or pt1.typcategory = 'G') order by castsource::regtype, casttarget::regtype; castsource | casttarget | prosrc ------------+------------+--------------- point | box | point_box lseg | point | lseg_center path | polygon | path_poly box | point | box_center box | lseg | box_diagonal box | polygon | box_poly box | circle | box_circle polygon | point | poly_center polygon | path | poly_path polygon | box | poly_box polygon | circle | poly_circle circle | point | circle_center circle | box | circle_box circle | polygon | (14 rows) already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center This patch make these functions error safe: path_poly, lseg_center, box_center, box_circle, poly_center, poly_circle, circle_box can not error safe: cast circle to polygon, because it's a SQL function discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com --- src/backend/utils/adt/geo_ops.c | 194 +++++++++++++++++++++++++------- 1 file changed, 153 insertions(+), 41 deletions(-) diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c index 5c3b9e2ed607..834f42841591 100644 --- a/src/backend/utils/adt/geo_ops.c +++ b/src/backend/utils/adt/geo_ops.c @@ -77,7 +77,8 @@ enum path_delim /* Routines for points */ static inline void point_construct(Point *result, float8 x, float8 y); -static inline void point_add_point(Point *result, Point *pt1, Point *pt2); +static inline void point_add_point(Point *result, Point *pt1, Point *pt2, + Node *escontext); static inline void point_sub_point(Point *result, Point *pt1, Point *pt2); static inline void point_mul_point(Point *result, Point *pt1, Point *pt2); static inline void point_div_point(Point *result, Point *pt1, Point *pt2); @@ -108,7 +109,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg); /* Routines for boxes */ static inline void box_construct(BOX *result, Point *pt1, Point *pt2); -static void box_cn(Point *center, BOX *box); +static void box_cn(Point *center, BOX *box, Node* escontext); static bool box_ov(BOX *box1, BOX *box2); static float8 box_ar(BOX *box); static float8 box_ht(BOX *box); @@ -125,7 +126,7 @@ static float8 circle_ar(CIRCLE *circle); /* Routines for polygons */ static void make_bound_box(POLYGON *poly); -static void poly_to_circle(CIRCLE *result, POLYGON *poly); +static bool poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext); static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start); static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly); static bool plist_same(int npts, Point *p1, Point *p2); @@ -836,8 +837,8 @@ box_distance(PG_FUNCTION_ARGS) Point a, b; - box_cn(&a, box1); - box_cn(&b, box2); + box_cn(&a, box1, NULL); + box_cn(&b, box2, NULL); PG_RETURN_FLOAT8(point_dt(&a, &b, NULL)); } @@ -851,7 +852,9 @@ box_center(PG_FUNCTION_ARGS) BOX *box = PG_GETARG_BOX_P(0); Point *result = palloc_object(Point); - box_cn(result, box); + box_cn(result, box, fcinfo->context); + if ((SOFT_ERROR_OCCURRED(fcinfo->context))) + PG_RETURN_NULL(); PG_RETURN_POINT_P(result); } @@ -869,12 +872,25 @@ box_ar(BOX *box) /* box_cn - stores the centerpoint of the box into *center. */ static void -box_cn(Point *center, BOX *box) +box_cn(Point *center, BOX *box, Node *escontext) { - center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0); - center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0); -} + float8 x; + float8 y; + + x = float8_pl_safe(box->high.x, box->low.x, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return; + center->x = float8_div_safe(x, 2.0, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return; + + y = float8_pl_safe(box->high.y, box->low.y, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return; + + center->y = float8_div_safe(y, 2.0, escontext); +} /* box_wd - returns the width (length) of the box * (horizontal magnitude). @@ -2328,13 +2344,31 @@ lseg_center(PG_FUNCTION_ARGS) { LSEG *lseg = PG_GETARG_LSEG_P(0); Point *result; + float8 x; + float8 y; result = palloc_object(Point); - result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0); - result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0); + x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + result->x = float8_div_safe(x, 2.0, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + result->y = float8_div_safe(y, 2.0, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; PG_RETURN_POINT_P(result); + +fail: + PG_RETURN_NULL(); } @@ -3288,7 +3322,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg) if (result != NULL) { - box_cn(&point, box); + box_cn(&point, box, NULL); lseg_closept_point(result, lseg, &point); } @@ -4119,11 +4153,20 @@ construct_point(PG_FUNCTION_ARGS) static inline void -point_add_point(Point *result, Point *pt1, Point *pt2) +point_add_point(Point *result, Point *pt1, Point *pt2, Node *escontext) { - point_construct(result, - float8_pl(pt1->x, pt2->x), - float8_pl(pt1->y, pt2->y)); + float8 x; + float8 y; + + x = float8_pl_safe(pt1->x, pt2->x, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return; + + y = float8_pl_safe(pt1->y, pt2->y, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return; + + point_construct(result, x, y); } Datum @@ -4135,7 +4178,7 @@ point_add(PG_FUNCTION_ARGS) result = palloc_object(Point); - point_add_point(result, p1, p2); + point_add_point(result, p1, p2, NULL); PG_RETURN_POINT_P(result); } @@ -4247,8 +4290,8 @@ box_add(PG_FUNCTION_ARGS) result = palloc_object(BOX); - point_add_point(&result->high, &box->high, p); - point_add_point(&result->low, &box->low, p); + point_add_point(&result->high, &box->high, p, NULL); + point_add_point(&result->low, &box->low, p, NULL); PG_RETURN_BOX_P(result); } @@ -4411,7 +4454,7 @@ path_add_pt(PG_FUNCTION_ARGS) int i; for (i = 0; i < path->npts; i++) - point_add_point(&path->p[i], &path->p[i], point); + point_add_point(&path->p[i], &path->p[i], point, NULL); PG_RETURN_PATH_P(path); } @@ -4469,7 +4512,7 @@ path_poly(PG_FUNCTION_ARGS) /* This is not very consistent --- other similar cases return NULL ... */ if (!path->closed) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("open path cannot be converted to polygon"))); @@ -4519,7 +4562,9 @@ poly_center(PG_FUNCTION_ARGS) result = palloc_object(Point); - poly_to_circle(&circle, poly); + if (!poly_to_circle(&circle, poly, fcinfo->context)) + PG_RETURN_NULL(); + *result = circle.center; PG_RETURN_POINT_P(result); @@ -4981,7 +5026,7 @@ circle_add_pt(PG_FUNCTION_ARGS) result = palloc_object(CIRCLE); - point_add_point(&result->center, &circle->center, point); + point_add_point(&result->center, &circle->center, point, NULL); result->radius = circle->radius; PG_RETURN_CIRCLE_P(result); @@ -5202,14 +5247,30 @@ circle_box(PG_FUNCTION_ARGS) box = palloc_object(BOX); - delta = float8_div(circle->radius, sqrt(2.0)); + delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; - box->high.x = float8_pl(circle->center.x, delta); - box->low.x = float8_mi(circle->center.x, delta); - box->high.y = float8_pl(circle->center.y, delta); - box->low.y = float8_mi(circle->center.y, delta); + box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; PG_RETURN_BOX_P(box); + +fail: + PG_RETURN_NULL(); } /* box_circle() @@ -5220,15 +5281,35 @@ box_circle(PG_FUNCTION_ARGS) { BOX *box = PG_GETARG_BOX_P(0); CIRCLE *circle; + float8 x; + float8 y; circle = palloc_object(CIRCLE); - circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0); - circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0); + x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + circle->center.x = float8_div_safe(x, 2.0, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; - circle->radius = point_dt(&circle->center, &box->high, NULL); + circle->center.y = float8_div_safe(y, 2.0, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + circle->radius = point_dt(&circle->center, &box->high, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; PG_RETURN_CIRCLE_P(circle); + +fail: + PG_RETURN_NULL(); } @@ -5292,10 +5373,11 @@ circle_poly(PG_FUNCTION_ARGS) * XXX This algorithm should use weighted means of line segments * rather than straight average values of points - tgl 97/01/21. */ -static void -poly_to_circle(CIRCLE *result, POLYGON *poly) +static bool +poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext) { int i; + float8 x; Assert(poly->npts > 0); @@ -5304,14 +5386,43 @@ poly_to_circle(CIRCLE *result, POLYGON *poly) result->radius = 0; for (i = 0; i < poly->npts; i++) - point_add_point(&result->center, &result->center, &poly->p[i]); - result->center.x = float8_div(result->center.x, poly->npts); - result->center.y = float8_div(result->center.y, poly->npts); + { + point_add_point(&result->center, + &result->center, + &poly->p[i], + escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + + result->center.x = float8_div_safe(result->center.x, + poly->npts, + escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + result->center.y = float8_div_safe(result->center.y, + poly->npts, + escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; for (i = 0; i < poly->npts; i++) - result->radius = float8_pl(result->radius, - point_dt(&poly->p[i], &result->center, NULL)); - result->radius = float8_div(result->radius, poly->npts); + { + x = point_dt(&poly->p[i], &result->center, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + result->radius = float8_pl_safe(result->radius, x, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + + result->radius = float8_div_safe(result->radius, poly->npts, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + return true; } Datum @@ -5322,7 +5433,8 @@ poly_circle(PG_FUNCTION_ARGS) result = palloc_object(CIRCLE); - poly_to_circle(result, poly); + if (!poly_to_circle(result, poly, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_CIRCLE_P(result); } From 1436739db0a89ca993736b059984cce57ffb19f9 Mon Sep 17 00:00:00 2001 From: jian he Date: Mon, 15 Dec 2025 12:28:40 +0800 Subject: [PATCH 22/23] CAST(expr AS newtype DEFAULT ON ERROR) # Bumps catversion required. * With this patchset, most functions in pg_cast.castfunc are now error-safe. * CoerceViaIO and CoerceToDomain were already error-safe in the HEAD. * this patch extends error-safe behavior to ArrayCoerceExpr. * We also ensure that when a coercion fails, execution falls back to evaluating the specified default node. * The doc has been refined, though it may still need more review. demo: SELECT CAST('1' AS date DEFAULT '2011-01-01' ON ERROR), CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR); date | int4 ------------+--------- 2011-01-01 | {-1011} [0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com --- contrib/citext/expected/citext.out | 5 + contrib/citext/expected/citext_1.out | 5 + contrib/citext/sql/citext.sql | 2 + .../pg_stat_statements/expected/select.out | 23 +- contrib/pg_stat_statements/sql/select.sql | 5 + doc/src/sgml/syntax.sgml | 30 + src/backend/executor/execExpr.c | 109 ++- src/backend/executor/execExprInterp.c | 29 + src/backend/jit/llvm/llvmjit_expr.c | 26 + src/backend/nodes/nodeFuncs.c | 61 ++ src/backend/optimizer/util/clauses.c | 51 +- src/backend/parser/gram.y | 22 + src/backend/parser/parse_agg.c | 9 + src/backend/parser/parse_coerce.c | 78 +- src/backend/parser/parse_expr.c | 412 ++++++++- src/backend/parser/parse_func.c | 3 + src/backend/parser/parse_target.c | 14 + src/backend/parser/parse_type.c | 14 + src/backend/parser/parse_utilcmd.c | 2 +- src/backend/utils/adt/arrayfuncs.c | 8 + src/backend/utils/adt/ruleutils.c | 21 + src/backend/utils/fmgr/fmgr.c | 14 + src/include/executor/execExpr.h | 7 + src/include/executor/executor.h | 1 + src/include/fmgr.h | 3 + src/include/nodes/execnodes.h | 21 + src/include/nodes/parsenodes.h | 11 + src/include/nodes/primnodes.h | 36 + src/include/optimizer/optimizer.h | 2 +- src/include/parser/parse_coerce.h | 15 + src/include/parser/parse_node.h | 2 + src/include/parser/parse_type.h | 2 + src/test/regress/expected/cast.out | 810 ++++++++++++++++++ src/test/regress/expected/create_cast.out | 5 + src/test/regress/expected/equivclass.out | 7 + src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/cast.sql | 350 ++++++++ src/test/regress/sql/create_cast.sql | 1 + src/test/regress/sql/equivclass.sql | 3 + src/tools/pgindent/typedefs.list | 3 + 40 files changed, 2179 insertions(+), 45 deletions(-) create mode 100644 src/test/regress/expected/cast.out create mode 100644 src/test/regress/sql/cast.sql diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out index 8c0bf54f0f34..33da19d8df4d 100644 --- a/contrib/citext/expected/citext.out +++ b/contrib/citext/expected/citext.out @@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); --------+--------- (0 rows) +SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error +ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR +LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI... + ^ +HINT: Safe type cast for user-defined types are not yet supported -- Test the operators and indexing functions -- Test = and <>. SELECT 'a'::citext = 'a'::citext AS t; diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out index c5e5f180f2b9..647eea191424 100644 --- a/contrib/citext/expected/citext_1.out +++ b/contrib/citext/expected/citext_1.out @@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); --------+--------- (0 rows) +SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error +ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR +LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI... + ^ +HINT: Safe type cast for user-defined types are not yet supported -- Test the operators and indexing functions -- Test = and <>. SELECT 'a'::citext = 'a'::citext AS t; diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql index aa1cf9abd5cd..99794497d47e 100644 --- a/contrib/citext/sql/citext.sql +++ b/contrib/citext/sql/citext.sql @@ -9,6 +9,8 @@ SELECT amname, opcname FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); +SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error + -- Test the operators and indexing functions -- Test = and <>. diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out index 75c896f38851..6e67997f0a2e 100644 --- a/contrib/pg_stat_statements/expected/select.out +++ b/contrib/pg_stat_statements/expected/select.out @@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY; ----- (0 rows) +--error safe type cast +SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR); + int4 +------ + 2 +(1 row) + +SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR); + int4 +------ + 11 +(1 row) + +SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR); + numeric +--------- + 12 +(1 row) + -- DISTINCT and ORDER BY patterns -- Try some query permutations which once produced identical query IDs SELECT DISTINCT 1 AS "int"; @@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; 2 | 2 | SELECT $1 AS "int" ORDER BY 1 1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i 1 | 1 | SELECT $1 || $2 + 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR) + 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR) 2 | 2 | SELECT DISTINCT $1 AS "int" 0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C" 1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t @@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; | | ) + | | SELECT f FROM t ORDER BY f 1 | 1 | select $1::jsonb ? $2 -(17 rows) +(19 rows) SELECT pg_stat_statements_reset() IS NOT NULL AS t; t diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql index 11662cde08c9..7ee8160fd847 100644 --- a/contrib/pg_stat_statements/sql/select.sql +++ b/contrib/pg_stat_statements/sql/select.sql @@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3; SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY; SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY; +--error safe type cast +SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR); +SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR); +SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR); + -- DISTINCT and ORDER BY patterns -- Try some query permutations which once produced identical query IDs SELECT DISTINCT 1 AS "int"; diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 34c83880a66e..32af9ea061c2 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -2106,6 +2106,10 @@ CAST ( expression AS type The CAST syntax conforms to SQL; the syntax with :: is historical PostgreSQL usage. + The equivalent ON CONVERSION ERROR behavior is: + +CAST ( expression AS type ERROR ON CONVERSION ERROR ) + @@ -2160,6 +2164,32 @@ CAST ( expression AS type . + + + Safe Type Cast + + A type cast may occasionally fail. To guard against such failures, you can + provide an ON ERROR clause to handle potential errors. + The syntax for safe type cast is: + +CAST ( expression AS type DEFAULT expression ON CONVERSION ERROR ) + + 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. + + + + Some examples: + +SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR); +Result: 3 +SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR); +Result: 1.1 + + + diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index c35744b105e5..12ec679b90b4 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -99,6 +99,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, static void ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state, Datum *resv, bool *resnull, ExprEvalStep *scratch); +static void ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch); static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning, ErrorSaveContext *escontext, bool omit_quotes, bool exists_coerce, @@ -170,6 +173,47 @@ ExecInitExpr(Expr *node, PlanState *parent) return state; } +/* + * ExecInitExprSafe: soft error variant of ExecInitExpr. + * + * use it only for expression nodes support soft errors, not all expression + * nodes support it. +*/ +ExprState * +ExecInitExprSafe(Expr *node, PlanState *parent) +{ + ExprState *state; + ExprEvalStep scratch = {0}; + + /* Special case: NULL expression produces a NULL ExprState pointer */ + if (node == NULL) + return NULL; + + /* Initialize ExprState with empty step list */ + state = makeNode(ExprState); + state->expr = node; + state->parent = parent; + state->ext_params = NULL; + state->escontext = makeNode(ErrorSaveContext); + state->escontext->type = T_ErrorSaveContext; + state->escontext->error_occurred = false; + state->escontext->details_wanted = false; + + /* Insert setup steps as needed */ + ExecCreateExprSetupSteps(state, (Node *) node); + + /* Compile the expression proper */ + ExecInitExprRec(node, state, &state->resvalue, &state->resnull); + + /* Finally, append a DONE step */ + scratch.opcode = EEOP_DONE_RETURN; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} + /* * ExecInitExprWithParams: prepare a standalone expression tree for execution * @@ -1701,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state, elemstate->innermost_caseval = palloc_object(Datum); elemstate->innermost_casenull = palloc_object(bool); + elemstate->escontext = state->escontext; ExecInitExprRec(acoerce->elemexpr, elemstate, &elemstate->resvalue, &elemstate->resnull); @@ -2176,6 +2221,14 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + + ExecInitSafeTypeCastExpr(stcexpr, state, resv, resnull, &scratch); + break; + } + case T_CoalesceExpr: { CoalesceExpr *coalesce = (CoalesceExpr *) node; @@ -2736,7 +2789,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, /* Initialize function call parameter structure too */ InitFunctionCallInfoData(*fcinfo, flinfo, - nargs, inputcollid, NULL, NULL); + nargs, inputcollid, (Node *) state->escontext, NULL); /* Keep extra copies of this info to save an indirection at runtime */ scratch->d.func.fn_addr = flinfo->fn_addr; @@ -4733,6 +4786,60 @@ ExecBuildParamSetEqual(TupleDesc desc, return state; } +/* + * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary + * expressions. + */ +static void +ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch) +{ + /* + * If we can not coerce to the target type, fallback to the DEFAULT + * expression specified in the ON CONVERSION ERROR clause, and we are done. + */ + if (stcexpr->cast_expr == NULL) + { + ExecInitExprRec((Expr *) stcexpr->default_expr, + state, resv, resnull); + return; + } + else + { + SafeTypeCastState *stcstate; + + stcstate = palloc0(sizeof(SafeTypeCastState)); + stcstate->stcexpr = stcexpr; + stcstate->escontext.type = T_ErrorSaveContext; + state->escontext = &stcstate->escontext; + + /* evaluate argument expression into step's result area */ + ExecInitExprRec((Expr *) stcexpr->cast_expr, + state, resv, resnull); + scratch->opcode = EEOP_SAFETYPE_CAST; + scratch->d.stcexpr.stcstate = stcstate; + ExprEvalPushStep(state, scratch); + + /* + * Steps to evaluate the DEFAULT expression. Skip it if this is a + * binary coercion cast. + */ + if (!IsA(stcexpr->cast_expr, RelabelType)) + { + ErrorSaveContext *saved_escontext; + + saved_escontext = state->escontext; + state->escontext = NULL; + ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr, + state, resv, resnull); + state->escontext = saved_escontext; + } + + stcstate->jump_end = state->steps_len; + } +} + /* * Push steps to evaluate a JsonExpr and its various subsidiary expressions. */ diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 1d88cdd2cb4b..ca6e0fd56cd0 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -568,6 +568,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_XMLEXPR, &&CASE_EEOP_JSON_CONSTRUCTOR, &&CASE_EEOP_IS_JSON, + &&CASE_EEOP_SAFETYPE_CAST, &&CASE_EEOP_JSONEXPR_PATH, &&CASE_EEOP_JSONEXPR_COERCION, &&CASE_EEOP_JSONEXPR_COERCION_FINISH, @@ -1926,6 +1927,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_SAFETYPE_CAST) + { + SafeTypeCastState *stcstate = op->d.stcexpr.stcstate; + + if (SOFT_ERROR_OCCURRED(&stcstate->escontext)) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + + /* + * Reset for next use such as for catching errors when coercing + * a expression. + */ + stcstate->escontext.error_occurred = false; + stcstate->escontext.details_wanted = false; + + EEO_NEXT(); + } + else + EEO_JUMP(stcstate->jump_end); + } + EEO_CASE(EEOP_JSONEXPR_PATH) { /* too complex for an inline implementation */ @@ -3644,6 +3667,12 @@ ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op, ExprContext *econtext) econtext, op->d.arraycoerce.resultelemtype, op->d.arraycoerce.amstate); + + if (SOFT_ERROR_OCCURRED(op->d.arraycoerce.elemexprstate->escontext)) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + } } /* diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index f9c7f29e7280..db9f4928619f 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2256,6 +2256,32 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_SAFETYPE_CAST: + { + SafeTypeCastState *stcstate = op->d.stcexpr.stcstate; + + if (SOFT_ERROR_OCCURRED(&stcstate->escontext)) + { + /* + * Reset for next use such as for catching errors when + * coercing a expression. + */ + stcstate->escontext.error_occurred = false; + stcstate->escontext.details_wanted = false; + + /* set resnull to true */ + LLVMBuildStore(b, l_sbool_const(1), v_resnullp); + /* reset resvalue */ + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); + + LLVMBuildBr(b, opblocks[opno + 1]); + } + else + LLVMBuildBr(b, opblocks[stcstate->jump_end]); + + break; + } + case EEOP_JSONEXPR_PATH: { JsonExprState *jsestate = op->d.jsonexpr.jsestate; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 024a2b2fd841..8a8d15ca6f59 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -206,6 +206,9 @@ exprType(const Node *expr) case T_RowCompareExpr: type = BOOLOID; break; + case T_SafeTypeCastExpr: + type = ((const SafeTypeCastExpr *) expr)->resulttype; + break; case T_CoalesceExpr: type = ((const CoalesceExpr *) expr)->coalescetype; break; @@ -450,6 +453,8 @@ exprTypmod(const Node *expr) return typmod; } break; + case T_SafeTypeCastExpr: + return ((const SafeTypeCastExpr *) expr)->resulttypmod; case T_CoalesceExpr: { /* @@ -965,6 +970,9 @@ exprCollation(const Node *expr) /* RowCompareExpr's result is boolean ... */ coll = InvalidOid; /* ... so it has no collation */ break; + case T_SafeTypeCastExpr: + coll = ((const SafeTypeCastExpr *) expr)->resultcollid; + break; case T_CoalesceExpr: coll = ((const CoalesceExpr *) expr)->coalescecollid; break; @@ -1232,6 +1240,9 @@ exprSetCollation(Node *expr, Oid collation) /* RowCompareExpr's result is boolean ... */ Assert(!OidIsValid(collation)); /* ... so never set a collation */ break; + case T_SafeTypeCastExpr: + ((SafeTypeCastExpr *) expr)->resultcollid = collation; + break; case T_CoalesceExpr: ((CoalesceExpr *) expr)->coalescecollid = collation; break; @@ -1550,6 +1561,9 @@ exprLocation(const Node *expr) /* just use leftmost argument's location */ loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs); break; + case T_SafeTypeCastExpr: + loc = ((const SafeTypeCastExpr *) expr)->location; + break; case T_CoalesceExpr: /* COALESCE keyword should always be the first thing */ loc = ((const CoalesceExpr *) expr)->location; @@ -2321,6 +2335,18 @@ expression_tree_walker_impl(Node *node, return true; } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *scexpr = (SafeTypeCastExpr *) node; + + if (WALK(scexpr->source_expr)) + return true; + if (WALK(scexpr->cast_expr)) + return true; + if (WALK(scexpr->default_expr)) + return true; + } + break; case T_CoalesceExpr: return WALK(((CoalesceExpr *) node)->args); case T_MinMaxExpr: @@ -3330,6 +3356,19 @@ expression_tree_mutator_impl(Node *node, return (Node *) newnode; } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *scexpr = (SafeTypeCastExpr *) node; + SafeTypeCastExpr *newnode; + + FLATCOPY(newnode, scexpr, SafeTypeCastExpr); + MUTATE(newnode->source_expr, scexpr->source_expr, Node *); + MUTATE(newnode->cast_expr, scexpr->cast_expr, Node *); + MUTATE(newnode->default_expr, scexpr->default_expr, Node *); + + return (Node *) newnode; + } + break; case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; @@ -4464,6 +4503,28 @@ raw_expression_tree_walker_impl(Node *node, return true; } break; + case T_SafeTypeCast: + { + SafeTypeCast *sc = (SafeTypeCast *) node; + + if (WALK(sc->cast)) + return true; + if (WALK(sc->raw_default)) + return true; + } + break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stc = (SafeTypeCastExpr *) node; + + if (WALK(stc->source_expr)) + return true; + if (WALK(stc->cast_expr)) + return true; + if (WALK(stc->default_expr)) + return true; + } + break; case T_CollateClause: return WALK(((CollateClause *) node)->arg); case T_SortBy: diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index ddafc21c819a..54046b125873 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2447,7 +2447,8 @@ estimate_expression_value(PlannerInfo *root, Node *node) ((Node *) evaluate_expr((Expr *) (node), \ exprType((Node *) (node)), \ exprTypmod((Node *) (node)), \ - exprCollation((Node *) (node)))) + exprCollation((Node *) (node)), \ + false)) /* * Recursive guts of eval_const_expressions/estimate_expression_value @@ -2958,6 +2959,32 @@ eval_const_expressions_mutator(Node *node, copyObject(jve->format)); } + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stc = (SafeTypeCastExpr *) node; + SafeTypeCastExpr *newexpr; + Node *source_expr = stc->source_expr; + Node *default_expr = stc->default_expr; + + source_expr = eval_const_expressions_mutator(source_expr, context); + default_expr = eval_const_expressions_mutator(default_expr, context); + + /* + * We must not reduce any recognizably constant subexpressions + * in cast_expr here, since we don’t want it to fail + * prematurely. + */ + newexpr = makeNode(SafeTypeCastExpr); + newexpr->source_expr = source_expr; + newexpr->cast_expr = stc->cast_expr; + newexpr->default_expr = default_expr; + newexpr->resulttype = stc->resulttype; + newexpr->resulttypmod = stc->resulttypmod; + newexpr->resultcollid = stc->resultcollid; + + return (Node *) newexpr; + } + case T_SubPlan: case T_AlternativeSubPlan: @@ -3380,7 +3407,8 @@ eval_const_expressions_mutator(Node *node, return (Node *) evaluate_expr((Expr *) svf, svf->type, svf->typmod, - InvalidOid); + InvalidOid, + false); else return copyObject((Node *) svf); } @@ -4698,7 +4726,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, newexpr->location = -1; return evaluate_expr((Expr *) newexpr, result_type, result_typmod, - result_collid); + result_collid, false); } /* @@ -5152,10 +5180,14 @@ sql_inline_error_callback(void *arg) * * We use the executor's routine ExecEvalExpr() to avoid duplication of * code and ensure we get the same result as the executor would get. + * + * When error_safe is set to true, we will evaluate the constant expression in a + * error safe way. If the evaluation fails, return NULL instead of throwing an + * error. */ Expr * evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, - Oid result_collation) + Oid result_collation, bool error_safe) { EState *estate; ExprState *exprstate; @@ -5180,7 +5212,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, * Prepare expr for execution. (Note: we can't use ExecPrepareExpr * because it'd result in recursively invoking eval_const_expressions.) */ - exprstate = ExecInitExpr(expr, NULL); + if (error_safe) + exprstate = ExecInitExprSafe(expr, NULL); + else + exprstate = ExecInitExpr(expr, NULL); /* * And evaluate it. @@ -5200,6 +5235,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, /* Get back to outer memory context */ MemoryContextSwitchTo(oldcontext); + if (error_safe && SOFT_ERROR_OCCURRED(exprstate->escontext)) + { + FreeExecutorState(estate); + return NULL; + } + /* * Must copy result out of sub-context used by expression eval. * diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 28f4e11e30ff..dd0809e4b35a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -16065,6 +16065,28 @@ func_expr_common_subexpr: } | CAST '(' a_expr AS Typename ')' { $$ = makeTypeCast($3, $5, @1); } + | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')' + { $$ = makeTypeCast($3, $5, @1); } + | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')' + { + TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1); + + SafeTypeCast *safecast = makeNode(SafeTypeCast); + safecast->cast = (Node *) cast; + safecast->raw_default = makeNullAConst(-1);; + + $$ = (Node *) safecast; + } + | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')' + { + TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1); + + SafeTypeCast *safecast = makeNode(SafeTypeCast); + safecast->cast = (Node *) cast; + safecast->raw_default = $7; + + $$ = (Node *) safecast; + } | EXTRACT '(' extract_list ')' { $$ = (Node *) makeFuncCall(SystemFuncName("extract"), diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index b8340557b345..de77f6e93021 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) err = _("grouping operations are not allowed in check constraints"); break; + case EXPR_KIND_CAST_DEFAULT: + if (isAgg) + err = _("aggregate functions are not allowed in CAST DEFAULT expressions"); + else + err = _("grouping operations are not allowed in CAST DEFAULT expressions"); + break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: @@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_DOMAIN_CHECK: err = _("window functions are not allowed in check constraints"); break; + case EXPR_KIND_CAST_DEFAULT: + err = _("window functions are not allowed in CAST DEFAULT expressions"); + break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: err = _("window functions are not allowed in DEFAULT expressions"); diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 78b1e366ad70..4ae87e2030bb 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -81,6 +81,31 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype, CoercionContext ccontext, CoercionForm cformat, int location) +{ + return coerce_to_target_type_extended(pstate, + expr, + exprtype, + targettype, + targettypmod, + ccontext, + cformat, + location, + NULL); +} + +/* + * escontext: If not NULL, expr (Unknown Const node type) will be coerced to the + * target type in an error-safe way. If it fails, NULL is returned. + * + * For other parameters, see above coerce_to_target_type. + */ +Node * +coerce_to_target_type_extended(ParseState *pstate, Node *expr, Oid exprtype, + Oid targettype, int32 targettypmod, + CoercionContext ccontext, + CoercionForm cformat, + int location, + Node *escontext) { Node *result; Node *origexpr; @@ -102,9 +127,12 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype, while (expr && IsA(expr, CollateExpr)) expr = (Node *) ((CollateExpr *) expr)->arg; - result = coerce_type(pstate, expr, exprtype, - targettype, targettypmod, - ccontext, cformat, location); + result = coerce_type_extend(pstate, expr, exprtype, + targettype, targettypmod, + ccontext, cformat, location, + escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return NULL; /* * If the target is a fixed-length type, it may need a length coercion as @@ -158,6 +186,18 @@ Node * coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, CoercionContext ccontext, CoercionForm cformat, int location) +{ + return coerce_type_extend(pstate, node, + inputTypeId, targetTypeId, targetTypeMod, + ccontext, cformat, location, NULL); +} + +Node * +coerce_type_extend(ParseState *pstate, Node *node, + Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, + CoercionContext ccontext, CoercionForm cformat, + int location, + Node *escontext) { Node *result; CoercionPathType pathtype; @@ -256,6 +296,8 @@ coerce_type(ParseState *pstate, Node *node, int32 inputTypeMod; Type baseType; ParseCallbackState pcbstate; + char *string; + bool coercion_failed = false; /* * If the target type is a domain, we want to call its base type's @@ -308,21 +350,27 @@ coerce_type(ParseState *pstate, Node *node, * We assume here that UNKNOWN's internal representation is the same * as CSTRING. */ - if (!con->constisnull) - newcon->constvalue = stringTypeDatum(baseType, - DatumGetCString(con->constvalue), - inputTypeMod); + if (con->constisnull) + string = NULL; else - newcon->constvalue = stringTypeDatum(baseType, - NULL, - inputTypeMod); + string = DatumGetCString(con->constvalue); + + if (!stringTypeDatumSafe(baseType, + string, + inputTypeMod, + escontext, + &newcon->constvalue)) + { + coercion_failed = true; + newcon->constisnull = true; + } /* * If it's a varlena value, force it to be in non-expanded * (non-toasted) format; this avoids any possible dependency on * external values and improves consistency of representation. */ - if (!con->constisnull && newcon->constlen == -1) + if (!coercion_failed && !con->constisnull && newcon->constlen == -1) newcon->constvalue = PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue)); @@ -339,7 +387,7 @@ coerce_type(ParseState *pstate, Node *node, * identical may not get recognized as such. See pgsql-hackers * discussion of 2008-04-04. */ - if (!con->constisnull && !newcon->constbyval) + if (!coercion_failed && !con->constisnull && !newcon->constbyval) { Datum val2; @@ -358,8 +406,10 @@ coerce_type(ParseState *pstate, Node *node, result = (Node *) newcon; - /* If target is a domain, apply constraints. */ - if (baseTypeId != targetTypeId) + if (coercion_failed) + result = NULL; + else if (baseTypeId != targetTypeId) + /* If target is a domain, apply constraints. */ result = coerce_to_domain(result, baseTypeId, baseTypeMod, targetTypeId, diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 6b8fa15fca33..a10ec64a708d 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -17,6 +17,7 @@ #include "access/htup_details.h" #include "catalog/pg_aggregate.h" +#include "catalog/pg_cast.h" #include "catalog/pg_type.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -37,6 +38,7 @@ #include "utils/date.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/syscache.h" #include "utils/timestamp.h" #include "utils/xml.h" @@ -60,7 +62,8 @@ static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref); static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c); static Node *transformSubLink(ParseState *pstate, SubLink *sublink); static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, - Oid array_type, Oid element_type, int32 typmod); + Oid array_type, Oid element_type, int32 typmod, + bool *can_coerce); static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault); static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c); static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m); @@ -76,6 +79,10 @@ static Node *transformWholeRowRef(ParseState *pstate, int sublevels_up, int location); static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); +static Node *transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc); +static void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, + Node *sourceexpr, Oid inputType, + Oid targetType); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); static Node *transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor); @@ -164,13 +171,17 @@ transformExprRecurse(ParseState *pstate, Node *expr) case T_A_ArrayExpr: result = transformArrayExpr(pstate, (A_ArrayExpr *) expr, - InvalidOid, InvalidOid, -1); + InvalidOid, InvalidOid, -1, NULL); break; case T_TypeCast: result = transformTypeCast(pstate, (TypeCast *) expr); break; + case T_SafeTypeCast: + result = transformTypeSafeCast(pstate, (SafeTypeCast *) expr); + break; + case T_CollateClause: result = transformCollateClause(pstate, (CollateClause *) expr); break; @@ -564,6 +575,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) case EXPR_KIND_VALUES_SINGLE: case EXPR_KIND_CHECK_CONSTRAINT: case EXPR_KIND_DOMAIN_CHECK: + case EXPR_KIND_CAST_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: case EXPR_KIND_INDEX_EXPRESSION: case EXPR_KIND_INDEX_PREDICATE: @@ -1824,6 +1836,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_DOMAIN_CHECK: err = _("cannot use subquery in check constraint"); break; + case EXPR_KIND_CAST_DEFAULT: + err = _("cannot use subquery in CAST DEFAULT expression"); + break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: err = _("cannot use subquery in DEFAULT expression"); @@ -2011,17 +2026,24 @@ transformSubLink(ParseState *pstate, SubLink *sublink) * If the caller specifies the target type, the resulting array will * be of exactly that type. Otherwise we try to infer a common type * for the elements using select_common_type(). + * + * Most of the time, can_coerce will be NULL. It is not NULL only when + * performing parse analysis for CAST(DEFAULT ... ON CONVERSION ERROR) + * expression. It's default to true. If coercing array elements fails, it will + * be set to false. */ static Node * transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, - Oid array_type, Oid element_type, int32 typmod) + Oid array_type, Oid element_type, int32 typmod, bool *can_coerce) { ArrayExpr *newa = makeNode(ArrayExpr); List *newelems = NIL; List *newcoercedelems = NIL; ListCell *element; Oid coerce_type; + Oid coerce_type_coll; bool coerce_hard; + ErrorSaveContext escontext = {T_ErrorSaveContext}; /* * Transform the element expressions @@ -2045,9 +2067,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, (A_ArrayExpr *) e, array_type, element_type, - typmod); + typmod, + can_coerce); /* we certainly have an array here */ - Assert(array_type == InvalidOid || array_type == exprType(newe)); + Assert(can_coerce || array_type == InvalidOid || array_type == exprType(newe)); newa->multidims = true; } else @@ -2088,6 +2111,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, } else { + /* Target type must valid for CAST DEFAULT */ + Assert(can_coerce == NULL); + /* Can't handle an empty array without a target type */ if (newelems == NIL) ereport(ERROR, @@ -2125,6 +2151,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, coerce_hard = false; } + coerce_type_coll = get_typcollation(coerce_type); + /* * Coerce elements to target type * @@ -2134,28 +2162,82 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, * If the array's type was merely derived from the common type of its * elements, then the elements are implicitly coerced to the common type. * This is consistent with other uses of select_common_type(). + * + * If can_coerce is not NULL, we need to safely test whether each element + * can be coerced to the target type, similar to what is done in + * transformTypeSafeCast. */ foreach(element, newelems) { Node *e = (Node *) lfirst(element); - Node *newe; + Node *newe = NULL; if (coerce_hard) { - newe = coerce_to_target_type(pstate, e, - exprType(e), - coerce_type, - typmod, - COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, - -1); - if (newe == NULL) - ereport(ERROR, - (errcode(ERRCODE_CANNOT_COERCE), - errmsg("cannot cast type %s to %s", - format_type_be(exprType(e)), - format_type_be(coerce_type)), - parser_errposition(pstate, exprLocation(e)))); + /* + * Can not coerce, just append the transformed element expression to + * the list. + */ + if (can_coerce && (!*can_coerce)) + newe = e; + else + { + Node *ecopy = NULL; + + if (can_coerce) + ecopy = copyObject(e); + + newe = coerce_to_target_type_extended(pstate, e, + exprType(e), + coerce_type, + typmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + -1, + (Node *) &escontext); + if (newe == NULL) + { + if (!can_coerce) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(exprType(e)), + format_type_be(coerce_type)), + parser_errposition(pstate, exprLocation(e)))); + else + { + /* + * Can not coerce, just append the transformed element + * expression to the list. + */ + newe = ecopy; + *can_coerce = false; + } + } + else if (can_coerce && IsA(ecopy, Const) && IsA(newe, FuncExpr)) + { + Node *result; + + /* + * pre-evaluate simple constant cast expressions in a way + * that tolerate errors. + */ + FuncExpr *newexpr = (FuncExpr *) copyObject(newe); + + assign_expr_collations(pstate, (Node *) newexpr); + + result = (Node *) evaluate_expr((Expr *) newexpr, + coerce_type, + typmod, + coerce_type_coll, + true); + if (result == NULL) + { + newe = ecopy; + *can_coerce = false; + } + } + } } else newe = coerce_to_common_type(pstate, e, @@ -2743,7 +2825,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) (A_ArrayExpr *) arg, targetBaseType, elementType, - targetBaseTypmod); + targetBaseTypmod, + NULL); } else expr = transformExprRecurse(pstate, arg); @@ -2780,6 +2863,291 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) return result; } +/* + * Handle an explicit CAST(... DEFAULT ... ON CONVERSION ERROR) construct. + * + * Transform SafeTypeCast node, look up the type name, and apply any necessary + * coercion function(s). + */ +static Node * +transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc) +{ + SafeTypeCastExpr *result; + TypeCast *tcast = (TypeCast *) tc->cast; + Node *source_expr; + Node *srcexpr; + Node *defexpr; + Node *cast_expr = NULL; + Oid inputType; + Oid targetType; + int32 targetTypmod; + Oid targetTypecoll; + Oid targetBaseType; + int32 targetBaseTypmod; + bool can_coerce = true; + + /* Look up the type name first */ + typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod); + targetBaseTypmod = targetTypmod; + + targetTypecoll = get_typcollation(targetType); + + targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod); + + /* now looking at DEFAULT expression */ + defexpr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT); + + defexpr = coerce_to_target_type(pstate, defexpr, exprType(defexpr), + targetType, targetTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + exprLocation(defexpr)); + if (defexpr == NULL) + ereport(ERROR, + errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot coerce %s expression to type %s", + "CAST DEFAULT", + format_type_be(targetType)), + parser_coercion_errposition(pstate, exprLocation(tc->raw_default), defexpr)); + + assign_expr_collations(pstate, defexpr); + + /* + * The collation of DEFAULT expression must match the collation of the + * target type. + */ + if (OidIsValid(targetTypecoll)) + { + Oid defColl = exprCollation(defexpr); + + if (OidIsValid(defColl) && targetTypecoll != defColl) + ereport(ERROR, + errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"), + errdetail("\"%s\" versus \"%s\"", + get_collation_name(targetTypecoll), + get_collation_name(defColl)), + parser_errposition(pstate, exprLocation(defexpr))); + } + + /* + * If the type cast target type is an array type, we invoke + * transformArrayExpr() directly so that we can pass down the type + * information. This avoids some cases where transformArrayExpr() might not + * infer the correct type. Otherwise, just transform the argument normally. + */ + if (IsA(tcast->arg, A_ArrayExpr)) + { + Oid elementType; + + /* + * If target is a domain over array, work with the base array type + * here. Below, we'll cast the array type to the domain. In the + * usual case that the target is not a domain, the remaining steps + * will be a no-op. + */ + elementType = get_element_type(targetBaseType); + + if (OidIsValid(elementType)) + { + source_expr = transformArrayExpr(pstate, + (A_ArrayExpr *) tcast->arg, + targetBaseType, + elementType, + targetBaseTypmod, + &can_coerce); + } + else + source_expr = transformExprRecurse(pstate, tcast->arg); + } + else + source_expr = transformExprRecurse(pstate, tcast->arg); + + /* + * Since we can't be certain that coerce_to_target_type_extended won't + * modify the source expression, we create a copy of it first. This ensures + * the transformed source expression is correctly passed to SafeTypeCastExpr + */ + srcexpr = copyObject(source_expr); + + inputType = exprType(source_expr); + if (inputType == InvalidOid) + return (Node *) NULL; /* return NULL if NULL input */ + + if (can_coerce) + { + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + cast_expr = coerce_to_target_type_extended(pstate, source_expr, inputType, + targetType, targetTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + tcast->location, + (Node *) &escontext); + if (cast_expr == NULL) + can_coerce = false; + + CoercionErrorSafeCheck(pstate, cast_expr, source_expr, inputType, + targetType); + } + + if (IsA(srcexpr, Const) && cast_expr && IsA(cast_expr, FuncExpr)) + { + Node *result; + + /* + * pre-evaluate simple constant cast expressions in a error safe way + * + * Rationale: + * 1. When deparsing safe cast expressions (or in other cases), + * eval_const_expressions might be invoked, but it cannot + * handle errors gracefully. + * 2. If the cast expression involves only simple constants, we + * can safely evaluate it ahead of time. If the evaluation + * fails, it indicates that such a cast is not possible, and + * we can then fall back to the CAST DEFAULT expression. + * 3. Even if FuncExpr (for castfunc) node has three arguments, + * the second and third arguments will also be constants per + * coerce_to_target_type. Evaluating a FuncExpr whose + * arguments are all Const is safe. + */ + FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr); + + assign_expr_collations(pstate, (Node *) newexpr); + + result = (Node *) evaluate_expr((Expr *) newexpr, + targetType, + targetTypmod, + targetTypecoll, + true); + if (result == NULL) + { + /* can not coerce, set can_coerce to false */ + can_coerce = false; + cast_expr = NULL; + } + } + + Assert(can_coerce || cast_expr == NULL); + + result = makeNode(SafeTypeCastExpr); + result->source_expr = srcexpr; + result->cast_expr = cast_expr; + result->default_expr = defexpr; + result->resulttype = targetType; + result->resulttypmod = targetTypmod; + result->resultcollid = targetTypecoll; + result->location = tcast->location; + + return (Node *) result; +} + +/* + * Check coercion is error safe or not. If not then report error + */ +static void +CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid inputType, + Oid targetType) +{ + HeapTuple tuple; + bool errorsafe = true; + bool userdefined = false; + Oid inputBaseType; + Oid targetBaseType; + Oid inputElementType; + Oid inputElementBaseType; + Oid targetElementType; + Oid targetElementBaseType; + + /* + * Binary coercion cast is equivalent to no cast at all. CoerceViaIO can + * also be evaluated in an error-safe manner. So skip these two cases. + */ + if (!castexpr || IsA(castexpr, RelabelType) || + IsA(castexpr, CoerceViaIO)) + return; + + /* + * Type cast involving with some types is not error safe, do the check now. + * We need consider domain over array and array over domain scarenio. We + * already checked user-defined type cast above. + */ + inputElementType = get_element_type(inputType); + + if (OidIsValid(inputElementType)) + inputElementBaseType = getBaseType(inputElementType); + else + { + inputBaseType = getBaseType(inputType); + + inputElementBaseType = get_element_type(inputBaseType); + if (!OidIsValid(inputElementBaseType)) + inputElementBaseType = inputBaseType; + } + + targetElementType = get_element_type(targetType); + + if (OidIsValid(targetElementType)) + targetElementBaseType = getBaseType(targetElementType); + else + { + targetBaseType = getBaseType(targetType); + + targetElementBaseType = get_element_type(targetBaseType); + if (!OidIsValid(targetElementBaseType)) + targetElementBaseType = targetBaseType; + } + + if (inputElementBaseType == MONEYOID || + targetElementBaseType == MONEYOID || + (inputElementBaseType == CIRCLEOID && + targetElementBaseType == POLYGONOID)) + { + /* + * Casts involving MONEY type are not error safe. The CIRCLE to POLYGON + * cast is also unsafe because it relies on a SQL-language function; + * only C-language functions are currently supported for error safe + * casts. + */ + errorsafe = false; + } + + if (errorsafe) + { + /* Look in pg_cast */ + tuple = SearchSysCache2(CASTSOURCETARGET, + ObjectIdGetDatum(inputElementBaseType), + ObjectIdGetDatum(targetElementBaseType)); + + if (HeapTupleIsValid(tuple)) + { + Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple); + + if (castForm->oid > FirstUnpinnedObjectId) + { + errorsafe = false; + userdefined = true; + } + + ReleaseSysCache(tuple); + } + } + + if (!errorsafe) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot cast type %s to %s when %s expression is specified in %s", + format_type_be(inputType), + format_type_be(targetType), + "DEFAULT", + "CAST ... ON CONVERSION ERROR"), + userdefined + ? errhint("Safe type cast for user-defined types are not yet supported") + : errhint("Explicit cast is defined but definition is not error safe"), + parser_errposition(pstate, exprLocation(sourceexpr))); +} + + /* * Handle an explicit COLLATE clause. * @@ -3193,6 +3561,8 @@ ParseExprKindName(ParseExprKind exprKind) case EXPR_KIND_CHECK_CONSTRAINT: case EXPR_KIND_DOMAIN_CHECK: return "CHECK"; + case EXPR_KIND_CAST_DEFAULT: + return "CAST DEFAULT"; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: return "DEFAULT"; diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 778d69c6f3c2..a90705b9847e 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) case EXPR_KIND_DOMAIN_CHECK: err = _("set-returning functions are not allowed in check constraints"); break; + case EXPR_KIND_CAST_DEFAULT: + err = _("set-returning functions are not allowed in CAST DEFAULT expressions"); + break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: err = _("set-returning functions are not allowed in DEFAULT expressions"); diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 905c975d83b5..dc03cf4ce74d 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1822,6 +1822,20 @@ FigureColnameInternal(Node *node, char **name) } } break; + case T_SafeTypeCast: + strength = FigureColnameInternal(((SafeTypeCast *) node)->cast, + name); + if (strength <= 1) + { + TypeCast *node_cast; + node_cast = (TypeCast *)((SafeTypeCast *) node)->cast; + if (node_cast->typeName != NULL) + { + *name = strVal(llast(node_cast->typeName->names)); + return 1; + } + } + break; case T_CollateClause: return FigureColnameInternal(((CollateClause *) node)->arg, name); case T_GroupingFunc: diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 9d7aa6967dad..f6825bb9870e 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -19,6 +19,7 @@ #include "catalog/pg_type.h" #include "lib/stringinfo.h" #include "nodes/makefuncs.h" +#include "nodes/miscnodes.h" #include "parser/parse_type.h" #include "parser/parser.h" #include "utils/array.h" @@ -660,6 +661,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod) return OidInputFunctionCall(typinput, string, typioparam, atttypmod); } +/* error-safe version of stringTypeDatum */ +bool +stringTypeDatumSafe(Type tp, char *string, int32 atttypmod, + Node *escontext, Datum *result) +{ + Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp); + Oid typinput = typform->typinput; + Oid typioparam = getTypeIOParam(tp); + + return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod, + escontext, result); +} + /* * Given a typeid, return the type's typrelid (associated relation), if any. * Returns InvalidOid if type is not a composite type. diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 375b40b29af8..09bc01728b7e 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -4943,7 +4943,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val, assign_expr_collations(pstate, value); value = (Node *) expression_planner((Expr *) value); value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod, - partCollation); + partCollation, false); if (!IsA(value, Const)) elog(ERROR, "could not evaluate partition bound expression"); } diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index bf54655bb964..b69ecf556026 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -3289,6 +3289,14 @@ array_map(Datum arrayd, /* Apply the given expression to source element */ values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]); + /* Exit early if the evaluation fails */ + if (SOFT_ERROR_OCCURRED(exprstate->escontext)) + { + pfree(values); + pfree(nulls); + return (Datum) 0; + } + if (nulls[i]) hasnulls = true; else diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 9f85eb86da1c..17049f05cfc4 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + + /* + * Here, we cannot deparsing cast_expr directly, since + * transformTypeSafeCast may have folded it into a simple + * constant or NULL. Instead, we use source_expr and + * default_expr to reconstruct the CAST DEFAULT clause. + */ + appendStringInfoString(buf, "CAST("); + get_rule_expr(stcexpr->source_expr, context, showimplicit); + + appendStringInfo(buf, " AS %s ", + format_type_with_typemod(stcexpr->resulttype, + stcexpr->resulttypmod)); + appendStringInfoString(buf, "DEFAULT "); + get_rule_expr(stcexpr->default_expr, context, showimplicit); + appendStringInfoString(buf, " ON CONVERSION ERROR)"); + } + break; case T_JsonExpr: { JsonExpr *jexpr = (JsonExpr *) node; diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 0fe63c6bb830..c9e5d0de8ed8 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -1759,6 +1759,20 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod) return InputFunctionCall(&flinfo, str, typioparam, typmod); } +/* error-safe version of OidInputFunctionCall */ +bool +OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam, + int32 typmod, Node *escontext, + Datum *result) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return InputFunctionCallSafe(&flinfo, str, typioparam, typmod, + escontext, result); +} + char * OidOutputFunctionCall(Oid functionId, Datum val) { diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 75366203706c..937cbf3bc9bf 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -265,6 +265,7 @@ typedef enum ExprEvalOp EEOP_XMLEXPR, EEOP_JSON_CONSTRUCTOR, EEOP_IS_JSON, + EEOP_SAFETYPE_CAST, EEOP_JSONEXPR_PATH, EEOP_JSONEXPR_COERCION, EEOP_JSONEXPR_COERCION_FINISH, @@ -750,6 +751,12 @@ typedef struct ExprEvalStep JsonIsPredicate *pred; /* original expression node */ } is_json; + /* for EEOP_SAFETYPE_CAST */ + struct + { + struct SafeTypeCastState *stcstate; + } stcexpr; + /* for EEOP_JSONEXPR_PATH */ struct { diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 7cd6a49309f0..3e8ba551e734 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -324,6 +324,7 @@ ExecProcNode(PlanState *node) * prototypes from functions in execExpr.c */ extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); +extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent); extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params); extern ExprState *ExecInitQual(List *qual, PlanState *parent); extern ExprState *ExecInitCheck(List *qual, PlanState *parent); diff --git a/src/include/fmgr.h b/src/include/fmgr.h index c0dbe85ed1c3..a9357b98d704 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -750,6 +750,9 @@ extern bool DirectInputFunctionCallSafe(PGFunction func, char *str, Datum *result); extern Datum OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod); +extern bool OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam, + int32 typmod, Node *escontext, + Datum *result); extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val); extern char *OidOutputFunctionCall(Oid functionId, Datum val); extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 3968429f9919..4171ff828a85 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState ExprState *check_exprstate; /* check_expr's eval state, or NULL */ } DomainConstraintState; +typedef struct SafeTypeCastState +{ + SafeTypeCastExpr *stcexpr; + + /* + * Address to jump to when skipping all the steps to evaulate the default + * expression. + */ + int jump_end; + + /* + * For error-safe evaluation of coercions. A pointer to this is passed to + * ExecInitExprRec() when initializing the coercion expressions, see + * ExecInitSafeTypeCastExpr. + * + * Reset for each evaluation of EEOP_SAFETYPE_CAST. + */ + ErrorSaveContext escontext; + +} SafeTypeCastState; + /* * State for JsonExpr evaluation, too big to inline. * diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index bc7adba4a0fc..926ca482c344 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -400,6 +400,17 @@ typedef struct TypeCast ParseLoc location; /* token location, or -1 if unknown */ } TypeCast; +/* + * SafeTypeCast - a CAST(source_expr AS target_type) DEFAULT ON CONVERSION ERROR + * construct + */ +typedef struct SafeTypeCast +{ + NodeTag type; + Node *cast; /* TypeCast expression */ + Node *raw_default; /* untransformed DEFAULT expression */ +} SafeTypeCast; + /* * CollateClause - a COLLATE expression */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 1b4436f2ff6d..1401109fb3cd 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -769,6 +769,42 @@ typedef enum CoercionForm COERCE_SQL_SYNTAX, /* display with SQL-mandated special syntax */ } CoercionForm; +/* + * SafeTypeCastExpr - + * Transformed representation of + * CAST(expr AS typename DEFAULT expr2 ON ERROR) + * CAST(expr AS typename NULL ON ERROR) + */ +typedef struct SafeTypeCastExpr +{ + Expr xpr; + + /* transformed source expression */ + Node *source_expr; + + /* + * The transformed cast expression; NULL if we can not cocerce source + * expression to the target type + */ + Node *cast_expr pg_node_attr(query_jumble_ignore); + + /* Fall back to the default expression if cast evaluation fails */ + Node *default_expr; + + /* cast result data type */ + Oid resulttype; + + /* cast result data type typmod (usually -1) */ + int32 resulttypmod; + + /* cast result data type collation */ + Oid resultcollid; + + /* Original SafeTypeCastExpr's location */ + ParseLoc location; + +} SafeTypeCastExpr; + /* * FuncExpr - expression node for a function call * diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index 44ec5296a183..587e711596ad 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -143,7 +143,7 @@ extern void convert_saop_to_hashed_saop(Node *node); extern Node *estimate_expression_value(PlannerInfo *root, Node *node); extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, - Oid result_collation); + Oid result_collation, bool error_safe); extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info); diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index 8d775c72c599..1eca1ce727d7 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -43,11 +43,26 @@ extern Node *coerce_to_target_type(ParseState *pstate, CoercionContext ccontext, CoercionForm cformat, int location); +extern Node *coerce_to_target_type_extended(ParseState *pstate, + Node *expr, + Oid exprtype, + Oid targettype, + int32 targettypmod, + CoercionContext ccontext, + CoercionForm cformat, + int location, + Node *escontext); extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids, CoercionContext ccontext); extern Node *coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, CoercionContext ccontext, CoercionForm cformat, int location); +extern Node *coerce_type_extend(ParseState *pstate, Node *node, + Oid inputTypeId, + Oid targetTypeId, int32 targetTypeMod, + CoercionContext ccontext, CoercionForm cformat, + int location, + Node *escontext); extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, CoercionContext ccontext, CoercionForm cformat, int location, diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index f7d07c845425..9f5b32e03601 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -67,6 +67,8 @@ typedef enum ParseExprKind EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */ EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */ EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */ + EXPR_KIND_CAST_DEFAULT, /* default expression in + CAST DEFAULT ON CONVERSION ERROR */ EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */ EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */ EXPR_KIND_INDEX_EXPRESSION, /* index expression */ diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h index 0d919d8bfa22..40aca2b31c95 100644 --- a/src/include/parser/parse_type.h +++ b/src/include/parser/parse_type.h @@ -47,6 +47,8 @@ extern char *typeTypeName(Type t); extern Oid typeTypeRelid(Type typ); extern Oid typeTypeCollation(Type typ); extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod); +extern bool stringTypeDatumSafe(Type tp, char *string, int32 atttypmod, + Node *escontext, Datum *result); extern Oid typeidTypeRelid(Oid type_id); extern Oid typeOrDomainTypeRelid(Oid type_id); diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out new file mode 100644 index 000000000000..b0dc7b8faea6 --- /dev/null +++ b/src/test/regress/expected/cast.out @@ -0,0 +1,810 @@ +SET extra_float_digits = 0; +-- CAST DEFAULT ON CONVERSION ERROR +SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR); + char +------ + A +(1 row) + +--test source expression is a unknown const +VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); --error +ERROR: invalid input syntax for type integer: "error" +LINE 1: VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); + ^ +VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR)); + column1 +--------- + +(1 row) + +VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR)); + column1 +--------- + 42 +(1 row) + +SELECT CAST('a' as int DEFAULT 18 ON CONVERSION ERROR); + int4 +------ + 18 +(1 row) + +SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error +ERROR: aggregate functions are not allowed in CAST DEFAULT expressions +LINE 1: SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); + ^ +SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error +ERROR: window functions are not allowed in CAST DEFAULT expressions +LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E... + ^ +SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error +ERROR: cannot use subquery in CAST DEFAULT expression +LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION E... + ^ +SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error +ERROR: invalid input syntax for type integer: "b" +LINE 1: SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); + ^ +SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error +ERROR: invalid input syntax for type integer: "a" +LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR... + ^ +--the default expression’s collation should match the collation of the cast +--target type +VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR)); +ERROR: the collation of CAST DEFAULT expression conflicts with target type collation +LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV... + ^ +DETAIL: "default" versus "C" +VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR)); + column1 +--------- + 1 3 +(1 row) + +VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR)); + column1 +--------- + {"1 3"} +(1 row) + +-- test source expression contain subquery +SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b)) + AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR); + b +--------- + {"1 3"} +(1 row) + +SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b)))) + AS INT[] + DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +CREATE FUNCTION ret_int8() RETURNS BIGINT AS +$$ +BEGIN RETURN 2147483648; END; +$$ +LANGUAGE plpgsql IMMUTABLE; +SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error +ERROR: integer out of range +SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error +ERROR: cannot coerce CAST DEFAULT expression to type date +LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR... + ^ +-- DEFAULT expression can not be set-returning +CREATE FUNCTION ret_setint() RETURNS SETOF integer AS +$$ +BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END; +$$ +LANGUAGE plpgsql IMMUTABLE; +CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY); +INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}'); +SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error +ERROR: set-returning functions are not allowed in CAST DEFAULT expressions +LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER... + ^ +SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t; + t +----------- + {21,22,1} + {21,22,2} + {21,22,3} + {21,22,4} +(4 rows) + +SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t; + a +----------- + {12} + {21,22,2} + {21,22,3} + {13} +(4 rows) + +-- test with user-defined type, domain, array over domain, domain over array +CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL; +CREATE DOMAIN d_char3_not_null as char(3) NOT NULL; +CREATE DOMAIN d_varchar as varchar(3) NOT NULL; +CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL; +CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int); +CREATE TYPE comp2 AS (a d_varchar); +SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error +ERROR: value too long for type character varying(3) +LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION... + ^ +SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); + comp2 +------- + (123) +(1 row) + +SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error +ERROR: value for domain d_int42 violates check constraint "d_int42_check" +SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); + d_int42 +--------- + 42 +(1 row) + +SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error +ERROR: domain d_int42 does not allow null values +SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); + d_int42 +--------- + 42 +(1 row) + +SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); + comp_domain_with_typmod +------------------------- + +(1 row) + +SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR); + comp_domain_with_typmod +------------------------- + ("1 ",2) +(1 row) + +SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error +ERROR: value too long for type character(3) +LINE 1: ...ST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)'... + ^ +SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR); + array +--------- + {42,42} +(1 row) + +SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR); + array +--------- + {41,43} +(1 row) + +-- test array coerce +SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error +ERROR: invalid input syntax for type integer: "a" +SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR); + array +------- + {1} +(1 row) + +SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR); + int4 +-------- + {-789} +(1 row) + +SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR); + int4 +--------- + {-1011} +(1 row) + +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR); + array +------- + {1,2} +(1 row) + +SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok + array +--------- + {21,22} +(1 row) + +SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error +ERROR: invalid input syntax for type integer: "three" +LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ... + ^ +SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error +ERROR: invalid input syntax for type integer: "three" +LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}... + ^ +-----safe cast with geometry data type +SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR); + box +------------- + (1,2),(1,2) +(1 row) + +SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR); + point +---------------- + (NaN,Infinity) +(1 row) + +SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR); + point +------------------- + (1e+300,Infinity) +(1 row) + +SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR); + polygon +--------- + +(1 row) + +SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR); + point +---------------- + (NaN,Infinity) +(1 row) + +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR); + lseg +--------------- + [(2,2),(0,0)] +(1 row) + +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR); + polygon +--------------------------- + ((0,0),(0,2),(2,2),(2,0)) +(1 row) + +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR); + path +------ + +(1 row) + +SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR); + circle +---------------------- + <(NaN,Infinity),NaN> +(1 row) + +SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR); + point +---------------- + (NaN,Infinity) +(1 row) + +SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR); + path +--------------------- + ((2,0),(2,4),(0,0)) +(1 row) + +SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR); + box +------------- + (2,4),(0,0) +(1 row) + +SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR); + circle +---------------------- + <(NaN,Infinity),NaN> +(1 row) + +SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR); + point +------- + (5,1) +(1 row) + +SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR); + box +------------- + (3,5),(3,5) +(1 row) + +-- not supported because the cast from circle to polygon is implemented as a SQL +-- function, which cannot be error-safe. +SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR); +ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR +LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C... + ^ +HINT: Explicit cast is defined but definition is not error safe +-----safe cast from/to money type is not supported, all the following would result error +SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR); +ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR +LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E... + ^ +HINT: Explicit cast is defined but definition is not error safe +SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR); +ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR +LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E... + ^ +HINT: Explicit cast is defined but definition is not error safe +SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR); +ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR +LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO... + ^ +HINT: Explicit cast is defined but definition is not error safe +SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR); +ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR +LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO... + ^ +HINT: Explicit cast is defined but definition is not error safe +SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR); +ERROR: cannot cast type money[] to numeric[] when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR +LINE 1: SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVE... + ^ +HINT: Explicit cast is defined but definition is not error safe +-----safe cast from bytea type to other data types +SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR); + int8 +------ + 19 +(1 row) + +SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR); + int4 +------ + 20 +(1 row) + +SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR); + int2 +------ + 21 +(1 row) + +-----safe cast from bit type to other data types +SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR); + int4 +------ + 22 +(1 row) + +SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR); + int8 +------ + 23 +(1 row) + +-----safe cast from text type to other data types +select CAST('a.b.c.d'::text as regclass default NULL on conversion error); + regclass +---------- + +(1 row) + +CREATE TABLE test_safecast(col0 text); +INSERT INTO test_safecast(col0) VALUES ('oneoneone1, max=>1::int))) ON CONVERSION ERROR) as cast0, + CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2, + CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3; +\sv safecastview +CREATE OR REPLACE VIEW public.safecastview AS + SELECT CAST('1234' AS character(3) DEFAULT '-1111'::integer::character(3) ON CONVERSION ERROR) AS bpchar, + CAST(1 AS date DEFAULT '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast0, + CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1, + CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2, + CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3 +SELECT * FROM safecastview; + bpchar | cast0 | cast1 | cast2 | cast3 +--------+------------+-------+-------+------- + 123 | 2025-12-07 | {1,2} | | {1,2} +(1 row) + +CREATE VIEW safecastview1 AS +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr + DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr + DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2; +\sv safecastview1 +CREATE OR REPLACE VIEW public.safecastview1 AS + SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1, + CAST(ARRAY[ARRAY[1, 2, 1.1::integer], ARRAY['three', true, '01'::"bit"]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2 +SELECT * FROM safecastview1; + cast1 | cast2 +---------+--------- + {41,43} | {41,43} +(1 row) + +RESET datestyle; +--test CAST DEFAULT expression mutability +CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR))); +ERROR: data type xid has no default operator class for access method "btree" +HINT: You must specify an operator class for the index or define a default operator class for the data type. +CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok +SELECT pg_get_indexdef('test_safecast3_idx'::regclass); + pg_get_indexdef +------------------------------------------------------------------------------------------------------------------------------------------ + CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR))) +(1 row) + +DROP TABLE test_safecast; +DROP TABLE test_safecast1; +DROP TABLE test_safecast2; +DROP TABLE test_safecast3; +DROP TABLE tcast; +DROP FUNCTION ret_int8; +DROP FUNCTION ret_setint; +DROP TYPE comp_domain_with_typmod; +DROP TYPE comp2; +DROP DOMAIN d_varchar; +DROP DOMAIN d_int42; +DROP DOMAIN d_char3_not_null; +RESET extra_float_digits; diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out index 0e69644bca2a..0054ed0ef673 100644 --- a/src/test/regress/expected/create_cast.out +++ b/src/test/regress/expected/create_cast.out @@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now bar1234 (1 row) +SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error +ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR +LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE... + ^ +HINT: Safe type cast for user-defined types are not yet supported -- check dependencies generated for that SELECT pg_describe_object(classid, objid, objsubid) as obj, pg_describe_object(refclassid, refobjid, refobjsubid) as objref, diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out index ad8ab294ff62..5aee2c7a9fb9 100644 --- a/src/test/regress/expected/equivclass.out +++ b/src/test/regress/expected/equivclass.out @@ -95,6 +95,13 @@ create function int8alias1cmp(int8, int8alias1) returns int strict immutable language internal as 'btint8cmp'; alter operator family integer_ops using btree add function 1 int8alias1cmp (int8, int8alias1); +-- int8alias2 binary-coercible to int8. so this is ok +select cast('1'::int8 as int8alias2 default null on conversion error); + int8alias2 +------------ + 1 +(1 row) + create table ec0 (ff int8 primary key, f1 int8, f2 int8); create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2); create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2); diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 905f9bca9598..e41b368dd946 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other # psql depends on create_am # amutils depends on geometry, create_index_spgist, hash_index, brin -test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252 +test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252 cast # ---------- # Run these alone so they don't run out of parallel workers diff --git a/src/test/regress/sql/cast.sql b/src/test/regress/sql/cast.sql new file mode 100644 index 000000000000..9b6f3cc06495 --- /dev/null +++ b/src/test/regress/sql/cast.sql @@ -0,0 +1,350 @@ +SET extra_float_digits = 0; + +-- CAST DEFAULT ON CONVERSION ERROR +SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR); + +--test source expression is a unknown const +VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); --error +VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR)); +VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR)); +SELECT CAST('a' as int DEFAULT 18 ON CONVERSION ERROR); +SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error +SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error +SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error +SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error +SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error + +--the default expression’s collation should match the collation of the cast +--target type +VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR)); + +VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR)); +VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR)); + +-- test source expression contain subquery +SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b)) + AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR); +SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b)))) + AS INT[] + DEFAULT NULL ON CONVERSION ERROR); + +CREATE FUNCTION ret_int8() RETURNS BIGINT AS +$$ +BEGIN RETURN 2147483648; END; +$$ +LANGUAGE plpgsql IMMUTABLE; + +SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error +SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error + +-- DEFAULT expression can not be set-returning +CREATE FUNCTION ret_setint() RETURNS SETOF integer AS +$$ +BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END; +$$ +LANGUAGE plpgsql IMMUTABLE; + +CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY); +INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}'); +SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error +SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t; +SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t; + +-- test with user-defined type, domain, array over domain, domain over array +CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL; +CREATE DOMAIN d_char3_not_null as char(3) NOT NULL; +CREATE DOMAIN d_varchar as varchar(3) NOT NULL; +CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL; +CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int); +CREATE TYPE comp2 AS (a d_varchar); +SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error +SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); +SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error +SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); +SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error +SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); +SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR); +SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error + +SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR); + +-- test array coerce +SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error +SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR); +SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR); +SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok +SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error +SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error + +-----safe cast with geometry data type +SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR); + +-- not supported because the cast from circle to polygon is implemented as a SQL +-- function, which cannot be error-safe. +SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR); + +-----safe cast from/to money type is not supported, all the following would result error +SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR); + +-----safe cast from bytea type to other data types +SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR); +SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR); +SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR); + +-----safe cast from bit type to other data types +SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR); +SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR); + +-----safe cast from text type to other data types +select CAST('a.b.c.d'::text as regclass default NULL on conversion error); + +CREATE TABLE test_safecast(col0 text); +INSERT INTO test_safecast(col0) VALUES ('one1, max=>1::int))) ON CONVERSION ERROR) as cast0, + CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2, + CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3; +\sv safecastview +SELECT * FROM safecastview; + +CREATE VIEW safecastview1 AS +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr + DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr + DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2; +\sv safecastview1 +SELECT * FROM safecastview1; + +RESET datestyle; + +--test CAST DEFAULT expression mutability +CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); +CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR))); +CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok +SELECT pg_get_indexdef('test_safecast3_idx'::regclass); + +DROP TABLE test_safecast; +DROP TABLE test_safecast1; +DROP TABLE test_safecast2; +DROP TABLE test_safecast3; +DROP TABLE tcast; +DROP FUNCTION ret_int8; +DROP FUNCTION ret_setint; +DROP TYPE comp_domain_with_typmod; +DROP TYPE comp2; +DROP DOMAIN d_varchar; +DROP DOMAIN d_int42; +DROP DOMAIN d_char3_not_null; +RESET extra_float_digits; diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql index 32187853cc7f..0a15a795d87c 100644 --- a/src/test/regress/sql/create_cast.sql +++ b/src/test/regress/sql/create_cast.sql @@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$; CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT; SELECT 1234::int4::casttesttype; -- Should work now +SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error -- check dependencies generated for that SELECT pg_describe_object(classid, objid, objsubid) as obj, diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql index 7fc2159349b6..5ad1d26311d4 100644 --- a/src/test/regress/sql/equivclass.sql +++ b/src/test/regress/sql/equivclass.sql @@ -98,6 +98,9 @@ create function int8alias1cmp(int8, int8alias1) returns int alter operator family integer_ops using btree add function 1 int8alias1cmp (int8, int8alias1); +-- int8alias2 binary-coercible to int8. so this is ok +select cast('1'::int8 as int8alias2 default null on conversion error); + create table ec0 (ff int8 primary key, f1 int8, f2 int8); create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2); create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3451538565e8..e4ecd1054d73 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2687,6 +2687,9 @@ STRLEN SV SYNCHRONIZATION_BARRIER SYSTEM_INFO +SafeTypeCast +SafeTypeCastExpr +SafeTypeCastState SampleScan SampleScanGetSampleSize_function SampleScanState From 48e0d24da1c9775f3bdba59269db5c78e02dbcbc Mon Sep 17 00:00:00 2001 From: jian he Date: Wed, 10 Dec 2025 16:15:37 +0800 Subject: [PATCH 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 7b0619893527..5c87820388f8 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 33da19d8df4d..be3287154927 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 647eea191424..e9f8454c6620 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 99794497d47e..62f83d749f97 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 1836c9acf39a..2622137cbf96 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 0a7056015b74..cbb1a139e690 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 4294d14ceb51..451c2ed81870 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 34e3918811cd..f5166679783d 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 efef91292a33..8fa46630d6de 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 2fc634429802..8fca3534f325 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 bad75bc1dce5..888d7142e429 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 32af9ea061c2..24d1dc6de0bc 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 1773c9c54916..4116b1708b05 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 8a435cd93dbf..0bc9373c7f25 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 be6ffd6ddb09..60cea5ad4634 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 dd0809e4b35a..a041d3fca1cc 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -354,7 +354,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 @@ -777,7 +777,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROUTINES ROW ROWS RULE - SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT + SAFE SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SPLIT SOURCE SQL_P STABLE STANDALONE_P @@ -9343,14 +9343,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; } @@ -9361,6 +9362,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; @@ -9373,6 +9375,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; @@ -9385,6 +9388,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 { @@ -18161,6 +18167,7 @@ unreserved_keyword: | ROUTINES | ROWS | RULE + | SAFE | SAVEPOINT | SCALAR | SCHEMA @@ -18799,6 +18806,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 a10ec64a708d..1e1fa5b1f200 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 fbfd669587f0..ca52cfcd0862 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 6a0ca3371534..bf47544f6759 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 926ca482c344..c78b966e80ff 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -4183,6 +4183,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 9fde58f541ca..67a187726695 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -397,6 +397,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 0054ed0ef673..1e32c041b9f1 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 a357e1d0c0e1..81ea244859f8 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 0a15a795d87c..30a0ff077c94 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