From 0ea64a63e40b6277327861fff3e42d774731ca03 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 15:36:35 +0800 Subject: [PATCH 01/19] 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 | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c index 6e7b914c5639..4a8adcb8204a 100644 --- a/src/backend/utils/adt/bytea.c +++ b/src/backend/utils/adt/bytea.c @@ -1027,10 +1027,14 @@ bytea_int2(PG_FUNCTION_ARGS) /* Check that the byte array is not too long */ if (len > sizeof(result)) - ereport(ERROR, + { + errsave(fcinfo->context, errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range")); + PG_RETURN_NULL(); + } + /* Convert it to an integer; most significant bytes come first */ result = 0; for (int i = 0; i < len; i++) @@ -1052,10 +1056,14 @@ bytea_int4(PG_FUNCTION_ARGS) /* Check that the byte array is not too long */ if (len > sizeof(result)) - ereport(ERROR, + { + errsave(fcinfo->context, errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range")); + PG_RETURN_NULL(); + } + /* Convert it to an integer; most significant bytes come first */ result = 0; for (int i = 0; i < len; i++) @@ -1077,10 +1085,14 @@ bytea_int8(PG_FUNCTION_ARGS) /* Check that the byte array is not too long */ if (len > sizeof(result)) - ereport(ERROR, + { + errsave(fcinfo->context, errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range")); + PG_RETURN_NULL(); + } + /* Convert it to an integer; most significant bytes come first */ result = 0; for (int i = 0; i < len; i++) From 3f9c882c4999b9fddd33146d68af7c69a68e3ea5 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 19:09:53 +0800 Subject: [PATCH 02/19] 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 | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c index 205a67dafc56..bfcea18f4b21 100644 --- a/src/backend/utils/adt/varbit.c +++ b/src/backend/utils/adt/varbit.c @@ -401,11 +401,15 @@ bit(PG_FUNCTION_ARGS) PG_RETURN_VARBIT_P(arg); if (!isExplicit) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), errmsg("bit string length %d does not match type bit(%d)", VARBITLEN(arg), len))); + PG_RETURN_NULL(); + } + rlen = VARBITTOTALLEN(len); /* set to 0 so that string is zero-padded */ result = (VarBit *) palloc0(rlen); @@ -752,11 +756,15 @@ varbit(PG_FUNCTION_ARGS) PG_RETURN_VARBIT_P(arg); if (!isExplicit) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), errmsg("bit string too long for type bit varying(%d)", len))); + PG_RETURN_NULL(); + } + rlen = VARBITTOTALLEN(len); result = (VarBit *) palloc(rlen); SET_VARSIZE(result, rlen); @@ -1591,10 +1599,14 @@ bittoint4(PG_FUNCTION_ARGS) /* Check that the bit string is not too long */ if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); + PG_RETURN_NULL(); + } + result = 0; for (r = VARBITS(arg); r < VARBITEND(arg); r++) { @@ -1671,10 +1683,14 @@ bittoint8(PG_FUNCTION_ARGS) /* Check that the bit string is not too long */ if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); + PG_RETURN_NULL(); + } + result = 0; for (r = VARBITS(arg); r < VARBITEND(arg); r++) { From 1b3f571764b0a79360b72c2fc21c4ab26c0bc5af Mon Sep 17 00:00:00 2001 From: jian he Date: Mon, 3 Nov 2025 11:58:46 +0800 Subject: [PATCH 03/19] 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 | 6 +++++- src/backend/utils/adt/xml.c | 17 ++++++++++++----- src/include/utils/xml.h | 2 +- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 0e1a74976f7d..67f4e00eac42 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 3f40c9da1a0d..a95e4bd094eb 100644 --- a/src/backend/utils/adt/varchar.c +++ b/src/backend/utils/adt/varchar.c @@ -307,10 +307,14 @@ bpchar(PG_FUNCTION_ARGS) { for (i = maxmblen; i < len; i++) if (s[i] != ' ') - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), errmsg("value too long for type character(%d)", maxlen))); + + PG_RETURN_NULL(); + } } len = maxmblen; diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 35c915573a1d..4723d13928f5 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,18 +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(); + errsave(escontext, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported XML feature"), + errdetail("This functionality requires the server to be built with libxml support.")); return NULL; #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 3b874ec41eb32158c2894b4f79df0bf4bf4744ec Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 15:49:46 +0800 Subject: [PATCH 04/19] 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 | 167 ++++++++++++++++++++++++++++++++ src/backend/utils/adt/regproc.c | 33 ++++++- src/backend/utils/adt/varlena.c | 42 ++++++++ src/include/catalog/namespace.h | 6 ++ src/include/utils/varlena.h | 1 + 5 files changed, 246 insertions(+), 3 deletions(-) diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index d23474da4fb2..a04d179c7fb2 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -640,6 +640,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, return relId; } +/* safe version of RangeVarGetRelidExtended */ +Oid +RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, + uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg, + Node *escontext) +{ + uint64 inval_count; + Oid relId; + Oid oldRelId = InvalidOid; + bool retry = false; + bool missing_ok = (flags & RVR_MISSING_OK) != 0; + + /* verify that flags do no conflict */ + Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED))); + + /* + * We check the catalog name and then ignore it. + */ + if (relation->catalogname) + { + if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0) + ereturn(escontext, InvalidOid, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cross-database references are not implemented: \"%s.%s.%s\"", + relation->catalogname, relation->schemaname, + relation->relname)); + } + + for (;;) + { + inval_count = SharedInvalidMessageCounter; + + if (relation->relpersistence == RELPERSISTENCE_TEMP) + { + if (!OidIsValid(myTempNamespace)) + relId = InvalidOid; + else + { + if (relation->schemaname) + { + Oid namespaceId; + + namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok); + + /* + * For missing_ok, allow a non-existent schema name to + * return InvalidOid. + */ + if (namespaceId != myTempNamespace) + ereturn(escontext, InvalidOid, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("temporary tables cannot specify a schema name")); + } + + relId = get_relname_relid(relation->relname, myTempNamespace); + } + } + else if (relation->schemaname) + { + Oid namespaceId; + + /* use exact schema given */ + namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok); + if (missing_ok && !OidIsValid(namespaceId)) + relId = InvalidOid; + else + relId = get_relname_relid(relation->relname, namespaceId); + } + else + { + /* search the namespace path */ + relId = RelnameGetRelid(relation->relname); + } + + if (callback) + callback(relation, relId, oldRelId, callback_arg); + + if (lockmode == NoLock) + break; + + if (retry) + { + if (relId == oldRelId) + break; + if (OidIsValid(oldRelId)) + UnlockRelationOid(oldRelId, lockmode); + } + + if (!OidIsValid(relId)) + AcceptInvalidationMessages(); + else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED))) + LockRelationOid(relId, lockmode); + else if (!ConditionalLockRelationOid(relId, lockmode)) + { + if (relation->schemaname) + ereport(DEBUG1, + errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on relation \"%s.%s\"", + relation->schemaname, relation->relname)); + else + ereport(DEBUG1, + errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on relation \"%s\"", + relation->relname)); + + return InvalidOid; + } + + if (inval_count == SharedInvalidMessageCounter) + break; + + retry = true; + oldRelId = relId; + } + + if (!OidIsValid(relId)) + { + if (relation->schemaname) + ereturn(escontext, InvalidOid, + errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s.%s\" does not exist", + relation->schemaname, relation->relname)); + else + ereturn(escontext, InvalidOid, + errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" does not exist", + relation->relname)); + } + return relId; +} + /* * RangeVarGetCreationNamespace * Given a RangeVar describing a to-be-created relation, @@ -3650,6 +3781,42 @@ makeRangeVarFromNameList(const List *names) return rel; } +/* + * makeRangeVarFromNameListSafe + * Utility routine to convert a qualified-name list into RangeVar form. + * The result maybe NULL. + */ +RangeVar * +makeRangeVarFromNameListSafe(const List *names, Node *escontext) +{ + RangeVar *rel = makeRangeVar(NULL, NULL, -1); + + switch (list_length(names)) + { + case 1: + rel->relname = strVal(linitial(names)); + break; + case 2: + rel->schemaname = strVal(linitial(names)); + rel->relname = strVal(lsecond(names)); + break; + case 3: + rel->catalogname = strVal(linitial(names)); + rel->schemaname = strVal(lsecond(names)); + rel->relname = strVal(lthird(names)); + break; + default: + errsave(escontext, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("improper relation name (too many dotted names): %s", + NameListToString(names))); + rel = NULL; + break; + } + + return rel; +} + /* * NameListToString * Utility routine to convert a qualified-name list into a string. diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c index e5c2246f2c92..059d01543357 100644 --- a/src/backend/utils/adt/regproc.c +++ b/src/backend/utils/adt/regproc.c @@ -1902,10 +1902,37 @@ text_regclass(PG_FUNCTION_ARGS) Oid result; RangeVar *rv; - rv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + if (likely(!fcinfo->context)) + { + rv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); - /* We might not even have permissions on this relation; don't lock it. */ - result = RangeVarGetRelid(rv, NoLock, false); + /* + * We might not even have permissions on this relation; don't lock it. + */ + result = RangeVarGetRelid(rv, NoLock, false); + } + else + { + List *rvnames; + + rvnames = textToQualifiedNameListSafe(relname, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + PG_RETURN_NULL(); + + rv = makeRangeVarFromNameListSafe(rvnames, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + PG_RETURN_NULL(); + + result = RangeVarGetRelidExtendedSafe(rv, + NoLock, + 0, + NULL, + NULL, + fcinfo->context); + + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + PG_RETURN_NULL(); + } PG_RETURN_OID(result); } diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index 8d735786e51b..5e2676f71803 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -2717,6 +2717,48 @@ textToQualifiedNameList(text *textval) return result; } +/* error safe version of textToQualifiedNameList */ +List * +textToQualifiedNameListSafe(text *textval, Node *escontext) +{ + char *rawname; + List *result = NIL; + List *namelist; + ListCell *l; + + /* Convert to C string (handles possible detoasting). */ + /* Note we rely on being able to modify rawname below. */ + rawname = text_to_cstring(textval); + + if (!SplitIdentifierString(rawname, '.', &namelist)) + { + errsave(escontext, + errcode(ERRCODE_INVALID_NAME), + errmsg("invalid name syntax")); + return NIL; + } + + if (namelist == NIL) + { + errsave(escontext, + errcode(ERRCODE_INVALID_NAME), + errmsg("invalid name syntax")); + return NIL; + } + + foreach(l, namelist) + { + char *curname = (char *) lfirst(l); + + result = lappend(result, makeString(pstrdup(curname))); + } + + pfree(rawname); + list_free(namelist); + + return result; +} + /* * SplitIdentifierString --- parse a string containing identifiers * 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 45b3848d138d5b5aefe622831d781923e582132c Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 19:17:00 +0800 Subject: [PATCH 05/19] 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 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c index a95e4bd094eb..fdbef272c39f 100644 --- a/src/backend/utils/adt/varchar.c +++ b/src/backend/utils/adt/varchar.c @@ -638,10 +638,14 @@ 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))); + + PG_RETURN_NULL(); + } } PG_RETURN_VARCHAR_P((VarChar *) cstring_to_text_with_len(s_data, From 7f482533b060c1e3b31e984d08f95a3e98a3b904 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 15:55:55 +0800 Subject: [PATCH 06/19] 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 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c index 3cb0ab6829ae..f34228763da4 100644 --- a/src/backend/utils/adt/network.c +++ b/src/backend/utils/adt/network.c @@ -1137,10 +1137,14 @@ 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, + { + errsave(fcinfo->context, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("could not format inet value: %m"))); + PG_RETURN_NULL(); + } + /* Add /n if not present (which it won't be) */ if (strchr(tmp, '/') == NULL) { From e74312f5c525e05ed8b1c8b297f287b7b5853997 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 19:12:12 +0800 Subject: [PATCH 07/19] 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 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c index 08e41ba4eeab..e2a7a90e42cf 100644 --- a/src/backend/utils/adt/mac8.c +++ b/src/backend/utils/adt/mac8.c @@ -550,7 +550,8 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS) result = (macaddr *) palloc0(sizeof(macaddr)); if ((addr->d != 0xFF) || (addr->e != 0xFE)) - ereport(ERROR, + { + errsave(fcinfo->context, (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 " @@ -558,6 +559,9 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS) "xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted " "from macaddr8 to macaddr."))); + PG_RETURN_NULL(); + } + result->a = addr->a; result->b = addr->b; result->c = addr->c; From d843b0f1aaf54c0135d98457bd049600d623bc21 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 16:04:07 +0800 Subject: [PATCH 08/19] 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 | 6 +++++- src/backend/utils/adt/int.c | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c index 22dbfc950b1f..7cf09d954e77 100644 --- a/src/backend/utils/adt/char.c +++ b/src/backend/utils/adt/char.c @@ -192,10 +192,14 @@ i4tochar(PG_FUNCTION_ARGS) int32 arg1 = PG_GETARG_INT32(0); if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("\"char\" out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_CHAR((int8) arg1); } diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index b5781989a64d..4d3ce23a4af9 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -350,10 +350,13 @@ i4toi2(PG_FUNCTION_ARGS) int32 arg1 = PG_GETARG_INT32(0); if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX)) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); + PG_RETURN_NULL(); + } PG_RETURN_INT16((int16) arg1); } From fcb6db815b6f4016df2911d91a3587725732cedb Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 16:38:37 +0800 Subject: [PATCH 09/19] 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 | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index bdea490202a6..3b2d100be92a 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1204,10 +1204,14 @@ int84(PG_FUNCTION_ARGS) int64 arg = PG_GETARG_INT64(0); if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX)) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_INT32((int32) arg); } @@ -1225,10 +1229,14 @@ int82(PG_FUNCTION_ARGS) int64 arg = PG_GETARG_INT64(0); if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX)) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_INT16((int16) arg); } @@ -1308,10 +1316,14 @@ i8tooid(PG_FUNCTION_ARGS) int64 arg = PG_GETARG_INT64(0); if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX)) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("OID out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_OID((Oid) arg); } From b18df55b923a7b9caf69e9dd54a0b3afc953d2b3 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 21:57:31 +0800 Subject: [PATCH 10/19] 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 | 68 +++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 2501007d981d..ce5f71109e72 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -1244,7 +1244,8 @@ numeric (PG_FUNCTION_ARGS) */ if (NUMERIC_IS_SPECIAL(num)) { - (void) apply_typmod_special(num, typmod, NULL); + if (!apply_typmod_special(num, typmod, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_NUMERIC(duplicate_numeric(num)); } @@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS) init_var(&var); set_var_from_num(num, &var); - (void) apply_typmod(&var, typmod, NULL); - new = make_result(&var); + if (!apply_typmod(&var, typmod, fcinfo->context)) + PG_RETURN_NULL(); + new = make_result_safe(&var, fcinfo->context); free_var(&var); @@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS) Numeric num2 = PG_GETARG_NUMERIC(1); Numeric res; - res = numeric_mul_safe(num1, num2, NULL); + res = numeric_mul_safe(num1, num2, fcinfo->context); + + if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context))) + PG_RETURN_NULL(); PG_RETURN_NUMERIC(res); } @@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext) Datum numeric_int4(PG_FUNCTION_ARGS) { + int32 result; Numeric num = PG_GETARG_NUMERIC(0); - PG_RETURN_INT32(numeric_int4_safe(num, NULL)); + result = numeric_int4_safe(num, fcinfo->context); + + if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context))) + PG_RETURN_NULL(); + + PG_RETURN_INT32(result); } /* @@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext) Datum numeric_int8(PG_FUNCTION_ARGS) { + int64 result; Numeric num = PG_GETARG_NUMERIC(0); - PG_RETURN_INT64(numeric_int8_safe(num, NULL)); + result = numeric_int8_safe(num, fcinfo->context); + + if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context))) + PG_RETURN_NULL(); + + PG_RETURN_INT64(result); } @@ -4489,28 +4506,38 @@ numeric_int2(PG_FUNCTION_ARGS) if (NUMERIC_IS_SPECIAL(num)) { if (NUMERIC_IS_NAN(num)) - ereport(ERROR, + errsave(fcinfo->context, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert NaN to %s", "smallint"))); else - ereport(ERROR, + errsave(fcinfo->context, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert infinity to %s", "smallint"))); + + PG_RETURN_NULL(); } /* Convert to variable format and thence to int8 */ init_var_from_num(num, &x); if (!numericvar_to_int64(&x, &val)) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); + PG_RETURN_NULL(); + } + if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX)) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); + PG_RETURN_NULL(); + } + /* Down-convert to int2 */ result = (int16) val; @@ -4572,10 +4599,14 @@ numeric_float8(PG_FUNCTION_ARGS) tmp = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num))); - - result = DirectFunctionCall1(float8in, CStringGetDatum(tmp)); - - pfree(tmp); + if (!DirectInputFunctionCallSafe(float8in, tmp, + InvalidOid, -1, + (Node *) fcinfo->context, + &result)) + { + pfree(tmp); + PG_RETURN_NULL(); + } PG_RETURN_DATUM(result); } @@ -4667,7 +4698,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 b9bed079e82e04bb80d9a14dc851c97986a55870 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 18:33:57 +0800 Subject: [PATCH 11/19] 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 | 12 ++++++++++-- src/backend/utils/adt/int8.c | 6 +++++- src/backend/utils/adt/numeric.c | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 7b97d2be6cae..dd8a2ff378b4 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -1298,10 +1298,14 @@ ftoi4(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num))) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_INT32((int32) num); } @@ -1323,10 +1327,14 @@ ftoi2(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num))) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_INT16((int16) num); } diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 3b2d100be92a..bcdb020a91ce 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1303,10 +1303,14 @@ ftoi8(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num))) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_INT64((int64) num); } diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index ce5f71109e72..8839b095f60e 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -4668,7 +4668,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 26c4f250f7f0949c50bcd79a360b5db71048a2bd Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 18:41:55 +0800 Subject: [PATCH 12/19] 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 | 29 +++++++++++++++++++++++++---- src/backend/utils/adt/int8.c | 6 +++++- src/backend/utils/adt/numeric.c | 3 ++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index dd8a2ff378b4..6747544b6797 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -1199,9 +1199,22 @@ dtof(PG_FUNCTION_ARGS) result = (float4) num; if (unlikely(isinf(result)) && !isinf(num)) - float_overflow_error(); + { + errsave(fcinfo->context, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow")); + + PG_RETURN_NULL(); + } + if (unlikely(result == 0.0f) && num != 0.0) - float_underflow_error(); + { + errsave(fcinfo->context, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: underflow")); + + PG_RETURN_NULL(); + } PG_RETURN_FLOAT4(result); } @@ -1224,10 +1237,14 @@ dtoi4(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num))) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_INT32((int32) num); } @@ -1249,10 +1266,14 @@ dtoi2(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num))) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_INT16((int16) num); } diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index bcdb020a91ce..437dbbccd4ad 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1268,10 +1268,14 @@ dtoi8(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num))) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); + PG_RETURN_NULL(); + } + PG_RETURN_INT64((int64) num); } diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 8839b095f60e..76cd9800c2a1 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -4570,7 +4570,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 4ee6e78abccae9f0ebed8eed29c203f8d3783979 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 18:42:50 +0800 Subject: [PATCH 13/19] 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 | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 344f58b92f7a..c7a3cde2d815 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -1350,7 +1350,16 @@ date_timestamp(PG_FUNCTION_ARGS) DateADT dateVal = PG_GETARG_DATEADT(0); Timestamp result; - result = date2timestamp(dateVal); + if (likely(!fcinfo->context)) + result = date2timestamp(dateVal); + else + { + int overflow; + + result = date2timestamp_opt_overflow(dateVal, &overflow); + if (overflow != 0) + PG_RETURN_NULL(); + } PG_RETURN_TIMESTAMP(result); } @@ -1435,7 +1444,16 @@ date_timestamptz(PG_FUNCTION_ARGS) DateADT dateVal = PG_GETARG_DATEADT(0); TimestampTz result; - result = date2timestamptz(dateVal); + if (likely(!fcinfo->context)) + result = date2timestamptz(dateVal); + else + { + int overflow; + result = date2timestamptz_opt_overflow(dateVal, &overflow); + + if (overflow != 0) + PG_RETURN_NULL(); + } PG_RETURN_TIMESTAMP(result); } From 1a9d380eef7a940c64fe3415709c1a6ccb7191bd Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 18:43:29 +0800 Subject: [PATCH 14/19] 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 c7a3cde2d815..4f0f3d269895 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -2180,7 +2180,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 156a4830ffda..7b565cc6d66e 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS) result = palloc(sizeof(Interval)); *result = *interval; - AdjustIntervalForTypmod(result, typmod, NULL); + if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_INTERVAL_P(result); } From 746937068527ee9e2ee17a02d2ca02b8d0e4f56e Mon Sep 17 00:00:00 2001 From: jian he Date: Mon, 6 Oct 2025 12:39:22 +0800 Subject: [PATCH 15/19] 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 | 16 +++++++++++++--- src/backend/utils/adt/timestamp.c | 17 +++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 4f0f3d269895..111bfd8f5190 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -1468,7 +1468,17 @@ timestamptz_date(PG_FUNCTION_ARGS) TimestampTz timestamp = PG_GETARG_TIMESTAMP(0); DateADT result; - result = timestamptz2date_opt_overflow(timestamp, NULL); + if (likely(!fcinfo->context)) + result = timestamptz2date_opt_overflow(timestamp, NULL); + else + { + int overflow; + result = timestamptz2date_opt_overflow(timestamp, &overflow); + + if (overflow != 0) + PG_RETURN_NULL(); + } + PG_RETURN_DATEADT(result); } @@ -2110,7 +2120,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"))); @@ -3029,7 +3039,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 7b565cc6d66e..116e3ef28fc2 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); } @@ -6507,7 +6508,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS) { TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); - PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp)); + if (likely(!fcinfo->context)) + PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp)); + else + { + int overflow; + Timestamp result; + + result = timestamptz2timestamp_opt_overflow(timestamp, &overflow); + if (overflow != 0) + PG_RETURN_NULL(); + else + PG_RETURN_TIMESTAMP(result); + } } /* From e08d08238899989c09d0ee49e70d434d61f44319 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 18:44:35 +0800 Subject: [PATCH 16/19] 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 | 13 +++++++++++-- src/backend/utils/adt/timestamp.c | 17 +++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 111bfd8f5190..c5562b563e56 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -1373,7 +1373,16 @@ timestamp_date(PG_FUNCTION_ARGS) Timestamp timestamp = PG_GETARG_TIMESTAMP(0); DateADT result; - result = timestamp2date_opt_overflow(timestamp, NULL); + if (likely(!fcinfo->context)) + result = timestamptz2date_opt_overflow(timestamp, NULL); + else + { + int overflow; + result = timestamp2date_opt_overflow(timestamp, &overflow); + + if (overflow != 0) + PG_RETURN_NULL(); + } PG_RETURN_DATEADT(result); } @@ -2089,7 +2098,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 116e3ef28fc2..7a57ac3eaf6e 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); } @@ -6430,7 +6431,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS) { Timestamp timestamp = PG_GETARG_TIMESTAMP(0); - PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp)); + if (likely(!fcinfo->context)) + PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp)); + else + { + TimestampTz result; + int overflow; + + result = timestamp2timestamptz_opt_overflow(timestamp, &overflow); + if (overflow != 0) + PG_RETURN_NULL(); + else + PG_RETURN_TIMESTAMPTZ(result); + } } /* From fcc1050693f5e4d9b82fe6f535ff8584306be566 Mon Sep 17 00:00:00 2001 From: jian he Date: Thu, 9 Oct 2025 18:58:40 +0800 Subject: [PATCH 17/19] 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 | 91 +++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 16 deletions(-) diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index da94d424d617..1ff00f72f782 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -2005,7 +2005,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 { @@ -2026,9 +2026,12 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype) for (i = 0; i < lengthof(messages); i++) if (messages[i].type == type) - ereport(ERROR, + { + errsave(escontext, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(messages[i].msg, sqltype))); + return; + } /* should be unreachable */ elog(ERROR, "unknown jsonb type: %d", (int) type); @@ -2041,7 +2044,11 @@ 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) { @@ -2050,7 +2057,11 @@ 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); @@ -2065,7 +2076,11 @@ 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) { @@ -2074,7 +2089,11 @@ 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 @@ -2095,7 +2114,11 @@ 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) { @@ -2104,7 +2127,11 @@ 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)); @@ -2122,7 +2149,11 @@ 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) { @@ -2131,7 +2162,11 @@ 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)); @@ -2149,7 +2184,11 @@ 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) { @@ -2158,7 +2197,11 @@ 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)); @@ -2176,7 +2219,11 @@ 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) { @@ -2185,7 +2232,11 @@ 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)); @@ -2203,7 +2254,11 @@ 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) { @@ -2212,7 +2267,11 @@ 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 02d05078e61ac4caf44ba124ed0efa9360b9471c Mon Sep 17 00:00:00 2001 From: jian he Date: Fri, 31 Oct 2025 08:32:32 +0800 Subject: [PATCH 18/19] 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 almost error safe: path_poly 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/access/spgist/spgproc.c | 2 +- src/backend/utils/adt/float.c | 8 +- src/backend/utils/adt/geo_ops.c | 238 ++++++++++++++++++++++++---- src/backend/utils/adt/geo_spgist.c | 2 +- src/include/utils/float.h | 107 +++++++++++++ src/include/utils/geo_decls.h | 4 +- 6 files changed, 323 insertions(+), 38 deletions(-) diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c index 660009291da4..b3e0fbc59ba7 100644 --- a/src/backend/access/spgist/spgproc.c +++ b/src/backend/access/spgist/spgproc.c @@ -51,7 +51,7 @@ point_box_distance(Point *point, BOX *box) else dy = 0.0; - return HYPOT(dx, dy); + return HYPOT(dx, dy, NULL); } /* diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 6747544b6797..06b2b6ae2953 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -772,7 +772,7 @@ float8pl(PG_FUNCTION_ARGS) float8 arg1 = PG_GETARG_FLOAT8(0); float8 arg2 = PG_GETARG_FLOAT8(1); - PG_RETURN_FLOAT8(float8_pl(arg1, arg2)); + PG_RETURN_FLOAT8(float8_pl_safe(arg1, arg2, fcinfo->context)); } Datum @@ -781,7 +781,7 @@ float8mi(PG_FUNCTION_ARGS) float8 arg1 = PG_GETARG_FLOAT8(0); float8 arg2 = PG_GETARG_FLOAT8(1); - PG_RETURN_FLOAT8(float8_mi(arg1, arg2)); + PG_RETURN_FLOAT8(float8_mi_safe(arg1, arg2, fcinfo->context)); } Datum @@ -790,7 +790,7 @@ float8mul(PG_FUNCTION_ARGS) float8 arg1 = PG_GETARG_FLOAT8(0); float8 arg2 = PG_GETARG_FLOAT8(1); - PG_RETURN_FLOAT8(float8_mul(arg1, arg2)); + PG_RETURN_FLOAT8(float8_mul_safe(arg1, arg2, fcinfo->context)); } Datum @@ -799,7 +799,7 @@ float8div(PG_FUNCTION_ARGS) float8 arg1 = PG_GETARG_FLOAT8(0); float8 arg2 = PG_GETARG_FLOAT8(1); - PG_RETURN_FLOAT8(float8_div(arg1, arg2)); + PG_RETURN_FLOAT8(float8_div_safe(arg1, arg2, fcinfo->context)); } diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c index 377a1b3f3ade..bd0318058584 100644 --- a/src/backend/utils/adt/geo_ops.c +++ b/src/backend/utils/adt/geo_ops.c @@ -78,11 +78,14 @@ 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 bool point_add_point_safe(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); static inline bool point_eq_point(Point *pt1, Point *pt2); static inline float8 point_dt(Point *pt1, Point *pt2); +static inline bool point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result); static inline float8 point_sl(Point *pt1, Point *pt2); static int point_inside(Point *p, int npts, Point *plist); @@ -109,6 +112,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 bool box_cn_safe(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 +129,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_safe(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); @@ -851,7 +855,8 @@ box_center(PG_FUNCTION_ARGS) BOX *box = PG_GETARG_BOX_P(0); Point *result = (Point *) palloc(sizeof(Point)); - box_cn(result, box); + if (!box_cn_safe(result, box, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_POINT_P(result); } @@ -875,6 +880,31 @@ box_cn(Point *center, BOX *box) center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0); } +/* safe version of box_cn */ +static bool +box_cn_safe(Point *center, BOX *box, Node* escontext) +{ + float8 x; + float8 y; + + x = float8_pl_safe(box->high.x, box->low.x, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + center->x = float8_div_safe(x, 2.0, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + y = float8_pl_safe(box->high.y, box->low.y, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + center->y = float8_div_safe(y, 2.0, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + return true; +} /* box_wd - returns the width (length) of the box * (horizontal magnitude). @@ -1276,7 +1306,7 @@ line_distance(PG_FUNCTION_ARGS) PG_RETURN_FLOAT8(float8_div(fabs(float8_mi(l1->C, float8_mul(ratio, l2->C))), - HYPOT(l1->A, l1->B))); + HYPOT(l1->A, l1->B, NULL))); } /* line_interpt() @@ -2001,9 +2031,34 @@ point_distance(PG_FUNCTION_ARGS) static inline float8 point_dt(Point *pt1, Point *pt2) { - return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y)); + return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y), NULL); +} + +/* errror safe version of point_dt */ +static inline bool +point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result) +{ + float8 x; + float8 y; + + Assert(result != NULL); + + x = float8_mi_safe(pt1->x, pt2->x, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + y = float8_mi_safe(pt1->y, pt2->y, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + *result = HYPOT(x, y, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + return true; } + Datum point_slope(PG_FUNCTION_ARGS) { @@ -2317,13 +2372,31 @@ lseg_center(PG_FUNCTION_ARGS) { LSEG *lseg = PG_GETARG_LSEG_P(0); Point *result; + float8 x; + float8 y; result = (Point *) palloc(sizeof(Point)); - result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0); - result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0); + x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + result->x = float8_div_safe(x, 2.0, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + result->y = float8_div_safe(y, 2.0, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; PG_RETURN_POINT_P(result); + +fail: + PG_RETURN_NULL(); } @@ -4115,6 +4188,27 @@ point_add_point(Point *result, Point *pt1, Point *pt2) float8_pl(pt1->y, pt2->y)); } +/* error safe version of point_add_point */ +static inline bool +point_add_point_safe(Point *result, Point *pt1, Point *pt2, + Node *escontext) +{ + float8 x; + float8 y; + + x = float8_pl_safe(pt1->x, pt2->x, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + y = float8_pl_safe(pt1->y, pt2->y, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + point_construct(result, x, y); + + return true; +} + Datum point_add(PG_FUNCTION_ARGS) { @@ -4458,10 +4552,14 @@ path_poly(PG_FUNCTION_ARGS) /* This is not very consistent --- other similar cases return NULL ... */ if (!path->closed) - ereport(ERROR, + { + errsave(fcinfo->context, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("open path cannot be converted to polygon"))); + PG_RETURN_NULL(); + } + /* * Never overflows: the old size fit in MaxAllocSize, and the new size is * just a small constant larger. @@ -4508,7 +4606,9 @@ poly_center(PG_FUNCTION_ARGS) result = (Point *) palloc(sizeof(Point)); - poly_to_circle(&circle, poly); + if (!poly_to_circle_safe(&circle, poly, fcinfo->context)) + PG_RETURN_NULL(); + *result = circle.center; PG_RETURN_POINT_P(result); @@ -5005,7 +5105,7 @@ circle_mul_pt(PG_FUNCTION_ARGS) result = (CIRCLE *) palloc(sizeof(CIRCLE)); point_mul_point(&result->center, &circle->center, point); - result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y)); + result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y, NULL)); PG_RETURN_CIRCLE_P(result); } @@ -5020,7 +5120,7 @@ circle_div_pt(PG_FUNCTION_ARGS) result = (CIRCLE *) palloc(sizeof(CIRCLE)); point_div_point(&result->center, &circle->center, point); - result->radius = float8_div(circle->radius, HYPOT(point->x, point->y)); + result->radius = float8_div(circle->radius, HYPOT(point->x, point->y, NULL)); PG_RETURN_CIRCLE_P(result); } @@ -5191,14 +5291,30 @@ circle_box(PG_FUNCTION_ARGS) box = (BOX *) palloc(sizeof(BOX)); - delta = float8_div(circle->radius, sqrt(2.0)); + delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; - box->high.x = float8_pl(circle->center.x, delta); - box->low.x = float8_mi(circle->center.x, delta); - box->high.y = float8_pl(circle->center.y, delta); - box->low.y = float8_mi(circle->center.y, delta); + box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; PG_RETURN_BOX_P(box); + +fail: + PG_RETURN_NULL(); } /* box_circle() @@ -5209,15 +5325,35 @@ box_circle(PG_FUNCTION_ARGS) { BOX *box = PG_GETARG_BOX_P(0); CIRCLE *circle; + float8 x; + float8 y; circle = (CIRCLE *) palloc(sizeof(CIRCLE)); - circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0); - circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0); + x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + circle->center.x = float8_div_safe(x, 2.0, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; - circle->radius = point_dt(&circle->center, &box->high); + y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + circle->center.y = float8_div_safe(y, 2.0, fcinfo->context); + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + goto fail; + + if (!point_dt_safe(&circle->center, &box->high, fcinfo->context, + &circle->radius)) + goto fail; PG_RETURN_CIRCLE_P(circle); + +fail: + PG_RETURN_NULL(); } @@ -5281,10 +5417,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_safe(CIRCLE *result, POLYGON *poly, Node *escontext) { int i; + float8 x; Assert(poly->npts > 0); @@ -5293,14 +5430,41 @@ 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); + { + if (!point_add_point_safe(&result->center, + &result->center, + &poly->p[i], + 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)); - result->radius = float8_div(result->radius, poly->npts); + { + if (!point_dt_safe(&poly->p[i], &result->center, escontext, &x)) + 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 @@ -5311,7 +5475,8 @@ poly_circle(PG_FUNCTION_ARGS) result = (CIRCLE *) palloc(sizeof(CIRCLE)); - poly_to_circle(result, poly); + if (!poly_to_circle_safe(result, poly, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_CIRCLE_P(result); } @@ -5516,7 +5681,7 @@ plist_same(int npts, Point *p1, Point *p2) *----------------------------------------------------------------------- */ float8 -pg_hypot(float8 x, float8 y) +pg_hypot(float8 x, float8 y, Node *escontext) { float8 yx, result; @@ -5554,9 +5719,22 @@ pg_hypot(float8 x, float8 y) result = x * sqrt(1.0 + (yx * yx)); if (unlikely(isinf(result))) - float_overflow_error(); + { + errsave(escontext, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow")); + + result = 0.0; + } + if (unlikely(result == 0.0)) - float_underflow_error(); + { + errsave(escontext, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: underflow")); + + result = 0.0; + } return result; } diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c index fec33e953724..ffffd3cd2de9 100644 --- a/src/backend/utils/adt/geo_spgist.c +++ b/src/backend/utils/adt/geo_spgist.c @@ -390,7 +390,7 @@ pointToRectBoxDistance(Point *point, RectBox *rect_box) else dy = 0; - return HYPOT(dx, dy); + return HYPOT(dx, dy, NULL); } diff --git a/src/include/utils/float.h b/src/include/utils/float.h index fc2a9cf6475f..5a7033f2a60c 100644 --- a/src/include/utils/float.h +++ b/src/include/utils/float.h @@ -109,6 +109,24 @@ float4_pl(const float4 val1, const float4 val2) return result; } +/* error safe version of float8_pl */ +static inline float8 +float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext) +{ + float8 result; + + result = val1 + val2; + if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) + { + errsave(escontext, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow")); + result = 0.0f; + } + + return result; +} + static inline float8 float8_pl(const float8 val1, const float8 val2) { @@ -133,6 +151,24 @@ float4_mi(const float4 val1, const float4 val2) return result; } +/* error safe version of float8_mi */ +static inline float8 +float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext) +{ + float8 result; + + result = val1 - val2; + if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2)) + { + errsave(escontext, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow")); + result = 0.0f; + } + + return result; +} + static inline float8 float8_mi(const float8 val1, const float8 val2) { @@ -159,6 +195,36 @@ float4_mul(const float4 val1, const float4 val2) return result; } +/* error safe version of float8_mul */ +static inline float8 +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)) + { + errsave(escontext, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow")); + + result = 0.0; + return result; + } + + if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0) + { + errsave(escontext, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: underflow")); + + result = 0.0; + return result; + } + + return result; +} + static inline float8 float8_mul(const float8 val1, const float8 val2) { @@ -189,6 +255,47 @@ float4_div(const float4 val1, const float4 val2) return result; } +/* error safe version of float8_div */ +static inline float8 +float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext) +{ + float8 result; + + if (unlikely(val2 == 0.0) && !isnan(val1)) + { + errsave(escontext, + errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero")); + + result = 0.0; + return result; + } + + result = val1 / val2; + + if (unlikely(isinf(result)) && !isinf(val1)) + { + errsave(escontext, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow")); + + result = 0.0; + return result; + } + + if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2)) + { + errsave(escontext, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: underflow")); + + result = 0.0; + return result; + } + + return result; +} + static inline float8 float8_div(const float8 val1, const float8 val2) { diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h index 8a9df75c93c9..0056a26639f3 100644 --- a/src/include/utils/geo_decls.h +++ b/src/include/utils/geo_decls.h @@ -88,7 +88,7 @@ FPge(double A, double B) #define FPge(A,B) ((A) >= (B)) #endif -#define HYPOT(A, B) pg_hypot(A, B) +#define HYPOT(A, B, escontext) pg_hypot(A, B, escontext) /*--------------------------------------------------------------------- * Point - (x,y) @@ -280,6 +280,6 @@ CirclePGetDatum(const CIRCLE *X) * in geo_ops.c */ -extern float8 pg_hypot(float8 x, float8 y); +extern float8 pg_hypot(float8 x, float8 y, Node *escontext); #endif /* GEO_DECLS_H */ From 609935a14ff760e0b45a77e56530a61adb182646 Mon Sep 17 00:00:00 2001 From: jian he Date: Tue, 4 Nov 2025 13:45:37 +0800 Subject: [PATCH 19/19] CAST(expr AS newtype DEFAULT ON ERROR) Now that the type coercion node is error-safe, we also need to ensure that when a coercion fails, it falls back to evaluating the default node. draft doc also added. We cannot simply prohibit user-defined functions in pg_cast for safe cast evaluation because CREATE CAST can also utilize built-in functions. So, to completely disallow custom casts created via CREATE CAST used in safe cast evaluation, a new field in pg_cast would unfortunately be necessary. 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 --- .../pg_stat_statements/expected/select.out | 23 +- contrib/pg_stat_statements/sql/select.sql | 5 + doc/src/sgml/catalogs.sgml | 12 + doc/src/sgml/syntax.sgml | 29 + src/backend/catalog/pg_cast.c | 1 + src/backend/executor/execExpr.c | 117 ++- src/backend/executor/execExprInterp.c | 37 + src/backend/jit/llvm/llvmjit_expr.c | 49 ++ src/backend/nodes/nodeFuncs.c | 67 ++ src/backend/optimizer/util/clauses.c | 93 +++ src/backend/parser/gram.y | 22 + src/backend/parser/parse_agg.c | 9 + src/backend/parser/parse_expr.c | 434 ++++++++++- src/backend/parser/parse_func.c | 3 + src/backend/parser/parse_target.c | 14 + src/backend/parser/parse_type.c | 14 + src/backend/utils/adt/arrayfuncs.c | 8 + src/backend/utils/adt/ruleutils.c | 22 + src/backend/utils/fmgr/fmgr.c | 13 + src/include/catalog/pg_cast.dat | 330 ++++---- src/include/catalog/pg_cast.h | 4 + src/include/executor/execExpr.h | 8 + src/include/executor/executor.h | 1 + src/include/fmgr.h | 3 + src/include/nodes/execnodes.h | 30 + src/include/nodes/parsenodes.h | 6 + src/include/nodes/primnodes.h | 33 + src/include/optimizer/optimizer.h | 2 + src/include/parser/parse_node.h | 2 + src/include/parser/parse_type.h | 2 + src/test/regress/expected/cast.out | 730 ++++++++++++++++++ src/test/regress/expected/create_cast.out | 5 + src/test/regress/expected/opr_sanity.out | 24 +- src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/cast.sql | 320 ++++++++ src/test/regress/sql/create_cast.sql | 1 + src/tools/pgindent/typedefs.list | 3 + 37 files changed, 2290 insertions(+), 188 deletions(-) create mode 100644 src/test/regress/expected/cast.out create mode 100644 src/test/regress/sql/cast.sql 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/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 6c8a0f173c97..492c99e37cdd 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<iteration count>:&l b means that the types are binary-coercible, thus no conversion is required. + + + + casterrorsafe bool + + + This indicates whether the castfunc function is error safe. + If the castfunc function is error safe, it can be used in error safe type cast. + For further details see . + + + diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 237d7306fe8a..d31aaef28750 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. + It can also be written as: + +CAST ( expression AS type ERROR ON CONVERSION ERROR ) + @@ -2160,6 +2164,31 @@ CAST ( expression AS type . + + + Safe Type Cast + +Sometimes a type cast may fail; to handle such cases, an ON ERROR clause can be +specified to avoid potential errors. The syntax is: + +CAST ( expression AS type DEFAULT expression ON CONVERSION ERROR ) + + If cast the source expression to target type fails, it falls back to + evaluating the optionally supplied default expression + specified in ON ON ERROR clause. + Currently, this only support built-in system type casts, + casts created using CREATE CAST are not supported. + + + + For example, the following query attempts to cast a text type value to integer type, + but when the conversion fails, it falls back to evaluate the supplied default expression. + +SELECT CAST(TEXT 'error' AS integer DEFAULT NULL ON CONVERSION ERROR) IS NULL; true + + + + diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c index 1773c9c54916..6fe65d24d31b 100644 --- a/src/backend/catalog/pg_cast.c +++ b/src/backend/catalog/pg_cast.c @@ -84,6 +84,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(false); tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls); diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index f1569879b529..5a4b9e8b53df 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -99,6 +99,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, static void ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state, Datum *resv, bool *resnull, ExprEvalStep *scratch); +static void ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch); static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning, ErrorSaveContext *escontext, bool omit_quotes, bool exists_coerce, @@ -170,6 +173,47 @@ ExecInitExpr(Expr *node, PlanState *parent) return state; } +/* + * ExecInitExprSafe: soft error variant of ExecInitExpr. + * + * use it only for expression nodes support soft errors, not all expression + * nodes support it. +*/ +ExprState * +ExecInitExprSafe(Expr *node, PlanState *parent) +{ + ExprState *state; + ExprEvalStep scratch = {0}; + + /* Special case: NULL expression produces a NULL ExprState pointer */ + if (node == NULL) + return NULL; + + /* Initialize ExprState with empty step list */ + state = makeNode(ExprState); + state->expr = node; + state->parent = parent; + state->ext_params = NULL; + state->escontext = makeNode(ErrorSaveContext); + state->escontext->type = T_ErrorSaveContext; + state->escontext->error_occurred = false; + state->escontext->details_wanted = false; + + /* Insert setup steps as needed */ + ExecCreateExprSetupSteps(state, (Node *) node); + + /* Compile the expression proper */ + ExecInitExprRec(node, state, &state->resvalue, &state->resnull); + + /* Finally, append a DONE step */ + scratch.opcode = EEOP_DONE_RETURN; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} + /* * ExecInitExprWithParams: prepare a standalone expression tree for execution * @@ -1701,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state, elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum)); elemstate->innermost_casenull = (bool *) palloc(sizeof(bool)); + elemstate->escontext = state->escontext; ExecInitExprRec(acoerce->elemexpr, elemstate, &elemstate->resvalue, &elemstate->resnull); @@ -2177,6 +2222,14 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + + ExecInitSafeTypeCastExpr(stcexpr, state, resv, resnull, &scratch); + break; + } + case T_CoalesceExpr: { CoalesceExpr *coalesce = (CoalesceExpr *) node; @@ -2743,7 +2796,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, /* Initialize function call parameter structure too */ InitFunctionCallInfoData(*fcinfo, flinfo, - nargs, inputcollid, NULL, NULL); + nargs, inputcollid, (Node *) state->escontext, NULL); /* Keep extra copies of this info to save an indirection at runtime */ scratch->d.func.fn_addr = flinfo->fn_addr; @@ -4741,6 +4794,68 @@ 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 + { + ExprEvalStep *as; + SafeTypeCastState *stcstate; + ErrorSaveContext *saved_escontext; + int jumps_to_end; + + 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); + stcstate->jump_error = state->steps_len; + + /* JUMP to end if false, that is, skip the ON ERROR expression. */ + jumps_to_end = state->steps_len; + scratch->opcode = EEOP_JUMP_IF_NOT_TRUE; + scratch->resvalue = &stcstate->error.value; + scratch->resnull = &stcstate->error.isnull; + scratch->d.jump.jumpdone = -1; /* set below */ + ExprEvalPushStep(state, scratch); + + /* Steps to evaluate the ON ERROR expression */ + saved_escontext = state->escontext; + state->escontext = NULL; + ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr, + state, resv, resnull); + state->escontext = saved_escontext; + + as = &state->steps[jumps_to_end]; + as->d.jump.jumpdone = state->steps_len; + + 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 67f4e00eac42..bd8b1048c5f4 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,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_SAFETYPE_CAST) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalSafeTypeCast(state, op)); + } + EEO_CASE(EEOP_JSONEXPR_PATH) { /* too complex for an inline implementation */ @@ -3644,6 +3651,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; + } } /* @@ -5183,6 +5196,30 @@ GetJsonBehaviorValueString(JsonBehavior *behavior) return pstrdup(behavior_names[behavior->btype]); } +int +ExecEvalSafeTypeCast(ExprState *state, ExprEvalStep *op) +{ + SafeTypeCastState *stcstate = op->d.stcexpr.stcstate; + + if (SOFT_ERROR_OCCURRED(&stcstate->escontext)) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + + stcstate->error.value = BoolGetDatum(true); + + /* + * Reset for next use such as for catching errors when coercing a + * expression. + */ + stcstate->escontext.error_occurred = false; + stcstate->escontext.details_wanted = false; + + return stcstate->jump_error; + } + return stcstate->jump_end; +} + /* * Checks if an error occurred in ExecEvalJsonCoercion(). If so, this sets * JsonExprState.error to trigger the ON ERROR handling steps, unless the diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 712b35df7e58..f54fe563543e 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2256,6 +2256,55 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_SAFETYPE_CAST: + { + SafeTypeCastState *stcstate = op->d.stcexpr.stcstate; + LLVMValueRef v_ret; + + /* + * Call ExecEvalSafeTypeCast(). It returns the address of + * the step to perform next. + */ + v_ret = build_EvalXFunc(b, mod, "ExecEvalSafeTypeCast", + v_state, op, v_econtext); + + /* + * Build a switch to map the return value (v_ret above), + * which is a runtime value of the step address to perform + * next to jump_error + */ + if (stcstate->jump_error >= 0) + { + LLVMValueRef v_jump_error; + LLVMValueRef v_switch; + LLVMBasicBlockRef b_done, + b_error; + + b_error = + l_bb_before_v(opblocks[opno + 1], + "op.%d.stcexpr_error", opno); + b_done = + l_bb_before_v(opblocks[opno + 1], + "op.%d.stcexpr_done", opno); + + v_switch = LLVMBuildSwitch(b, + v_ret, + b_done, + 1); + + /* Returned stcstate->jump_error? */ + v_jump_error = l_int32_const(lc, stcstate->jump_error); + LLVMAddCase(v_switch, v_jump_error, b_error); + + /* ON ERROR code */ + LLVMPositionBuilderAtEnd(b, b_error); + LLVMBuildBr(b, opblocks[stcstate->jump_error]); + + LLVMPositionBuilderAtEnd(b, b_done); + } + 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 ede838cd40c4..533e21120a64 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,15 @@ exprLocation(const Node *expr) /* just use leftmost argument's location */ loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs); break; + case T_SafeTypeCastExpr: + { + const SafeTypeCastExpr *cast_expr = (const SafeTypeCastExpr *) expr; + if (cast_expr->cast_expr) + loc = exprLocation(cast_expr->cast_expr); + else + loc = exprLocation(cast_expr->default_expr); + break; + } case T_CoalesceExpr: /* COALESCE keyword should always be the first thing */ loc = ((const CoalesceExpr *) expr)->location; @@ -2321,6 +2341,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 +3362,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 +4509,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->def_expr)) + 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 81d768ff2a26..07b8298dd9d0 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2945,6 +2945,30 @@ 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: @@ -5147,6 +5171,75 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, resultTypByVal); } +/* + * evaluate_expr_safe: error safe version of evaluate_expr + * + * We use the executor's routine ExecEvalExpr() to avoid duplication of + * code and ensure we get the same result as the executor would get. + * + * return NULL when evaulation failed. Ensure the expression expr can be evaulated + * in a soft error way. + * + * See comments on evaluate_expr too. + */ +Expr * +evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod, + Oid result_collation) +{ + EState *estate; + ExprState *exprstate; + MemoryContext oldcontext; + Datum const_val; + bool const_is_null; + int16 resultTypLen; + bool resultTypByVal; + + estate = CreateExecutorState(); + + /* We can use the estate's working context to avoid memory leaks. */ + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + /* Make sure any opfuncids are filled in. */ + fix_opfuncids((Node *) expr); + + /* + * Prepare expr for execution. (Note: we can't use ExecPrepareExpr + * because it'd result in recursively invoking eval_const_expressions.) + */ + exprstate = ExecInitExprSafe(expr, NULL); + + const_val = ExecEvalExprSwitchContext(exprstate, + GetPerTupleExprContext(estate), + &const_is_null); + + /* Get info needed about result datatype */ + get_typlenbyval(result_type, &resultTypLen, &resultTypByVal); + + /* Get back to outer memory context */ + MemoryContextSwitchTo(oldcontext); + + if (SOFT_ERROR_OCCURRED(exprstate->escontext)) + { + FreeExecutorState(estate); + return NULL; + } + + if (!const_is_null) + { + if (resultTypLen == -1) + const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(const_val)); + else + const_val = datumCopy(const_val, resultTypByVal, resultTypLen); + } + + FreeExecutorState(estate); + + return (Expr *) makeConst(result_type, result_typmod, result_collation, + resultTypLen, + const_val, const_is_null, + resultTypByVal); +} + /* * inline_set_returning_function diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a65097f19cf8..a641fc43c7d9 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -15993,6 +15993,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->def_expr = 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->def_expr = $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 3254c83cc6cd..6573e2a93eab 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -486,6 +486,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: @@ -956,6 +962,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_expr.c b/src/backend/parser/parse_expr.c index 12119f147fc1..596aac69b084 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -17,6 +17,8 @@ #include "access/htup_details.h" #include "catalog/pg_aggregate.h" +#include "catalog/pg_cast.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -37,6 +39,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 +63,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 +80,7 @@ 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 Node *transformCollateClause(ParseState *pstate, CollateClause *c); static Node *transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor); @@ -107,6 +112,8 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, int location); static Node *make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg); +static bool CoerceUnknownConstSafe(ParseState *pstate, Node *node, + Oid targetType, int32 targetTypeMod); /* @@ -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; @@ -576,6 +587,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) case EXPR_KIND_COPY_WHERE: case EXPR_KIND_GENERATED_COLUMN: case EXPR_KIND_CYCLE_MARK: + case EXPR_KIND_CAST_DEFAULT: /* okay */ break; @@ -1824,6 +1836,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_DOMAIN_CHECK: err = _("cannot use subquery in check constraint"); break; + case EXPR_KIND_CAST_DEFAULT: case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: err = _("cannot use subquery in DEFAULT expression"); @@ -2005,22 +2018,80 @@ transformSubLink(ParseState *pstate, SubLink *sublink) return result; } +/* + * Return true if successfuly coerced a Unknown Const to targetType +*/ +static bool +CoerceUnknownConstSafe(ParseState *pstate, Node *node, + Oid targetType, int32 targetTypeMod) +{ + Oid baseTypeId; + int32 baseTypeMod; + int32 inputTypeMod; + Type baseType; + char *string; + Datum datum; + bool converted; + Const *con; + + Assert(IsA(node, Const)); + Assert(exprType(node) == UNKNOWNOID); + + con = (Const *) node; + baseTypeMod = targetTypeMod; + baseTypeId = getBaseTypeAndTypmod(targetType, &baseTypeMod); + + if (baseTypeId == INTERVALOID) + inputTypeMod = baseTypeMod; + else + inputTypeMod = -1; + + baseType = typeidType(baseTypeId); + + /* + * We assume here that UNKNOWN's internal representation is the same as + * CSTRING. + */ + if (!con->constisnull) + string = DatumGetCString(con->constvalue); + else + string = NULL; + + converted = stringTypeDatumSafe(baseType, + string, + inputTypeMod, + &datum); + + ReleaseSysCache(baseType); + + return converted; +} + + /* * transformArrayExpr * * 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, the caller will pass can_coerce as NULL. + * can_coerce is not NULL only when performing parse analysis for expressions + * like CAST(DEFAULT ... ON CONVERSION ERROR). + * When can_coerce is not NULL, it should be assumed to default to true. If + * coerce array elements to the target type fails, can_coerce 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; /* @@ -2045,9 +2116,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 +2160,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, } else { + /* We obviously know the target type when performing type-safe cast */ + Assert(can_coerce == NULL); + /* Can't handle an empty array without a target type */ if (newelems == NIL) ereport(ERROR, @@ -2125,6 +2200,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, coerce_hard = false; } + coerce_type_coll = get_typcollation(coerce_type); + /* * Coerce elements to target type * @@ -2134,13 +2211,43 @@ 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); + bool expr_is_const = false; Node *newe; - if (coerce_hard) + /* + * If an UNKNOWN constant cannot be coerced to the target type, set + * can_coerce to false and coerce it to the TEXT data type instead. + */ + if (can_coerce != NULL && IsA(e, Const)) + { + expr_is_const = true; + + if (exprType(e) == UNKNOWNOID && + !CoerceUnknownConstSafe(pstate, e, coerce_type, typmod)) + { + *can_coerce = false; + + e = coerce_to_target_type(pstate, e, + UNKNOWNOID, + TEXTOID, + -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + -1); + } + } + + if (can_coerce != NULL && (!*can_coerce)) + newe = e; + else if (coerce_hard) { newe = coerce_to_target_type(pstate, e, exprType(e), @@ -2149,13 +2256,68 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, COERCION_EXPLICIT, COERCE_EXPLICIT_CAST, -1); - if (newe == NULL) + if (newe == NULL && can_coerce == 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)))); + + if (newe == NULL && can_coerce != NULL) + { + newe = e; + *can_coerce = false; + } + + if (newe != NULL && can_coerce != NULL && expr_is_const) + { + Node *origexpr = newe; + Node *result; + bool have_collate_expr = false; + + if (IsA(newe, CollateExpr)) + have_collate_expr = true; + + while (newe && IsA(newe, CollateExpr)) + newe = (Node *) ((CollateExpr *) newe)->arg; + + if (!IsA(newe, FuncExpr)) + newe = origexpr; + else + { + /* + * Use evaluate_expr_safe to pre-evaluate simple constant + * cast expressions early, in a way that tolter errors. + */ + FuncExpr *newexpr = (FuncExpr *) copyObject(newe); + + assign_expr_collations(pstate, (Node *) newexpr); + + result = (Node *) evaluate_expr_safe((Expr *) newexpr, + coerce_type, + typmod, + coerce_type_coll); + if (result == NULL) + { + newe = e; + *can_coerce = false; + } + else if (have_collate_expr) + { + /* Reinstall top CollateExpr */ + CollateExpr *coll = (CollateExpr *) origexpr; + CollateExpr *newcoll = makeNode(CollateExpr); + + newcoll->arg = (Expr *) result; + newcoll->collOid = coll->collOid; + newcoll->location = coll->location; + newe = (Node *) newcoll; + } + else + newe = result; + } + } } else newe = coerce_to_common_type(pstate, e, @@ -2743,7 +2905,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) (A_ArrayExpr *) arg, targetBaseType, elementType, - targetBaseTypmod); + targetBaseTypmod, + NULL); } else expr = transformExprRecurse(pstate, arg); @@ -2780,6 +2943,259 @@ 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 *def_expr = NULL; + Node *cast_expr = NULL; + Node *source_expr = NULL; + Oid inputType = InvalidOid; + Oid targetType; + Oid targetBaseType; + Oid typeColl; + int32 targetTypmod; + int32 targetBaseTypmod; + bool can_coerce = true; + bool expr_is_const = false; + int location; + + /* Look up the type name first */ + typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod); + targetBaseTypmod = targetTypmod; + + typeColl = get_typcollation(targetType); + + targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod); + + /* now looking at DEFAULT expression */ + def_expr = transformExpr(pstate, tc->def_expr, EXPR_KIND_CAST_DEFAULT); + + def_expr = coerce_to_target_type(pstate, def_expr, exprType(def_expr), + targetType, targetTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + exprLocation(def_expr)); + + assign_expr_collations(pstate, def_expr); + + if (def_expr == 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->def_expr), def_expr)); + + /* + * The collation of DEFAULT expression must match the collation of the + * target type. + */ + if (OidIsValid(typeColl)) + { + Oid defColl; + + defColl = exprCollation(def_expr); + + if (OidIsValid(defColl) && typeColl != 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(typeColl), + get_collation_name(defColl)), + parser_errposition(pstate, exprLocation(def_expr))); + } + + /* + * 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); + + inputType = exprType(source_expr); + if (inputType == InvalidOid) + return (Node *) NULL; /* return NULL if NULL input */ + + /* Test coerce UNKNOWN constant to the target type */ + if (can_coerce && IsA(source_expr, Const)) + { + if (exprType(source_expr) == UNKNOWNOID) + can_coerce = CoerceUnknownConstSafe(pstate, + source_expr, + targetType, + targetTypmod); + expr_is_const = true; + } + + /* + * Location of the coercion is preferentially the location of CAST symbol, + * but if there is none then use the location of the type name (this can + * happen in TypeName 'string' syntax, for instance). + */ + location = tcast->location; + if (location < 0) + location = tcast->typeName->location; + + if (can_coerce) + { + cast_expr = coerce_to_target_type(pstate, source_expr, inputType, + targetType, targetTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + location); + + if (cast_expr == NULL) + can_coerce = false; + } + + if (can_coerce) + { + bool have_collate_expr = false; + Node *origexpr = cast_expr; + + if (IsA(cast_expr, CollateExpr)) + have_collate_expr = true; + + while (cast_expr && IsA(cast_expr, CollateExpr)) + cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg; + + if (IsA(cast_expr, FuncExpr)) + { + HeapTuple tuple; + ListCell *lc; + Node *sexpr; + Node *result; + FuncExpr *fexpr = (FuncExpr *) cast_expr; + + lc = list_head(fexpr->args); + sexpr = (Node *) lfirst(lc); + + /* Look in pg_cast */ + tuple = SearchSysCache2(CASTSOURCETARGET, + ObjectIdGetDatum(exprType(sexpr)), + ObjectIdGetDatum(targetType)); + + if (HeapTupleIsValid(tuple)) + { + Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple); + if (!castForm->casterrorsafe) + 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"), + errhint("Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast"), + parser_errposition(pstate, exprLocation(source_expr))); + } + else + elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)", + format_type_be(inputType), + format_type_be(targetType)); + ReleaseSysCache(tuple); + + if (!expr_is_const) + cast_expr = origexpr; + else + { + /* + * Use evaluate_expr_safe to pre-evaluate simple constant cast + * expressions early, in a way that tolter errors. + * + * 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 the function has three arguments, the second and + * third arguments will also be constants per + * coerce_to_target_type. + */ + FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr); + + assign_expr_collations(pstate, (Node *) newexpr); + + result = (Node *) evaluate_expr_safe((Expr *) newexpr, + targetType, + targetTypmod, + typeColl); + if (result == NULL) + { + can_coerce = false; + cast_expr = NULL; + } + else if (have_collate_expr) + { + /* Reinstall top CollateExpr */ + CollateExpr *coll = (CollateExpr *) origexpr; + CollateExpr *newcoll = makeNode(CollateExpr); + + newcoll->arg = (Expr *) result; + newcoll->collOid = coll->collOid; + newcoll->location = coll->location; + cast_expr = (Node *) newcoll; + } + else + cast_expr = result; + } + } + else + /* Nothing to do, restore cast_expr to its original value */ + cast_expr = origexpr; + } + + Assert(can_coerce || cast_expr == NULL); + + result = makeNode(SafeTypeCastExpr); + result->source_expr = source_expr; + result->cast_expr = cast_expr; + result->default_expr = def_expr; + result->resulttype = targetType; + result->resulttypmod = targetTypmod; + result->resultcollid = typeColl; + + return (Node *) result; +} + /* * Handle an explicit COLLATE clause. * @@ -3193,6 +3609,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 7713bdc6af0a..d260aeec5dc4 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, Datum *result) +{ + Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp); + Oid typinput = typform->typinput; + Oid typioparam = getTypeIOParam(tp); + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod, + (Node *) &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/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index a464349ee33e..c1a7031a96cb 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 79ec136231be..5cc2f7e0cf2d 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -10558,6 +10558,28 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + + /* + * Here, we cannot simply deparse cast_expr, as it may have been + * optimized into a plain constant by transformTypeSafeCast. + * However, in this context we need the original CAST + * expression. + */ + 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..aaa4a42b1ea4 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -1759,6 +1759,19 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod) return InputFunctionCall(&flinfo, str, typioparam, typmod); } +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/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..218d81d535a3 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; /* ---------------- diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 75366203706c..34a884e3196c 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; /* original expression node */ + } stcexpr; + /* for EEOP_JSONEXPR_PATH */ struct { @@ -892,6 +899,7 @@ extern int ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalJsonCoercion(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern int ExecEvalSafeTypeCast(ExprState *state, ExprEvalStep *op); extern void ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op); extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op); extern void ExecEvalMergeSupportFunc(ExprState *state, ExprEvalStep *op, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index fa2b657fb2ff..f99fc26eb1fe 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 74fe3ea05758..991e14034d3b 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 18ae8f0d4bb8..f59494c8c6fe 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1059,6 +1059,36 @@ typedef struct DomainConstraintState ExprState *check_exprstate; /* check_expr's eval state, or NULL */ } DomainConstraintState; +typedef struct SafeTypeCastState +{ + SafeTypeCastExpr *stcexpr; + + /* Set to true if type cast cause an error. */ + NullableDatum error; + + /* + * Addresses of steps that implement DEFAULT expr ON CONVERSION ERROR for + * safe type cast. + */ + int jump_error; + + /* + * Address to jump to when skipping all the steps to evaulate the default + * expression after performing ExecEvalSafeTypeCast(). + */ + int jump_end; + + /* + * For error-safe evaluation of coercions. When DEFAULT expr ON CONVERSION + * ON ERROR is specified, 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 ecbddd12e1b3..7140093c55db 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -400,6 +400,12 @@ typedef struct TypeCast ParseLoc location; /* token location, or -1 if unknown */ } TypeCast; +typedef struct SafeTypeCast +{ + NodeTag type; + Node *cast; + Node *def_expr; /* default expr */ +} SafeTypeCast; /* * CollateClause - a COLLATE expression */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 1b4436f2ff6d..70b72ed2f409 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -769,6 +769,39 @@ 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 expression being casted */ + Node *source_expr; + + /* + * The transformed cast expression; It will NULL if can not cast source type + * to 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; + +} SafeTypeCastExpr; + /* * FuncExpr - expression node for a function call * diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index d0aa8ab0c1c0..964ec024de73 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -145,6 +145,8 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node); extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, Oid result_collation); +extern Expr *evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod, + Oid result_collation); extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info); extern List *expand_function_arguments(List *args, bool include_out_arguments, 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..12381aed64ce 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, + 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..6a0d32b41200 --- /dev/null +++ b/src/test/regress/expected/cast.out @@ -0,0 +1,730 @@ +SET extra_float_digits = 0; +-- CAST DEFAULT ON CONVERSION ERROR +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(1 AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(1::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(1::numeric AS money DEFAULT NULL ON CONVERSION E... + ^ +HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast +SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR); + char +------ + +(1 row) + +--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 OR REPLACE 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... + ^ +-- test array coerce +SELECT CAST(array[11.12] AS int[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + {11} +(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 integer[] 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 INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR); + array +------- + {1,2} +(1 row) + +SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR); + array +------------------- + {{1,2},{three,a}} +(1 row) + +-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR +CREATE OR REPLACE 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, c text default '1'); +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('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 '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(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 domain +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 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); --ok + 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); --ok + 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); --ok + 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)'... + ^ +-----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: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast +-----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: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast +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: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast +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: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast +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: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast +-----safe cast from bytea type to other data types +SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR); + int8 +------ + +(1 row) + +SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR); + int4 +------ + +(1 row) + +SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR); + int2 +------ + +(1 row) + +-----safe cast from bit type to other data types +SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR); + int4 +------ + +(1 row) + +SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR); + int8 +------ + +(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 test_safecast1, + CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2; +\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 now()::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS test_safecast1, + CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1, + CAST(ARRAY[ARRAY['1'::text, '2'::text], ARRAY['three'::text, 'a'::text]] AS text[] DEFAULT '{21,22}'::text[] ON CONVERSION ERROR) AS cast2 +CREATE VIEW safecastview1 AS +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2; +\sv safecastview1 +CREATE OR REPLACE VIEW public.safecastview1 AS + SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS d_int_arr DEFAULT '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1, + CAST(ARRAY[ARRAY[1, 2], ARRAY['three'::text, 'a'::text]] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2 +SELECT * FROM safecastview1; + cast1 | cast2 +-------+--------- + {1,2} | {21,22} +(1 row) + +--error, default expression is mutable +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 pg_temp.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; +RESET extra_float_digits; diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out index 0e69644bca2a..38eab453344d 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: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast -- 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/parallel_schedule b/src/test/regress/parallel_schedule index a0f5fab0f5df..c6936f64a878 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..9233b88309d2 --- /dev/null +++ b/src/test/regress/sql/cast.sql @@ -0,0 +1,320 @@ +SET extra_float_digits = 0; + +-- CAST DEFAULT ON CONVERSION ERROR +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(1 AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1111 AS "char" 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)); + +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 OR REPLACE 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 + +-- test array coerce +SELECT CAST(array[11.12] AS int[] DEFAULT NULL 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 integer[] 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 INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR); + +-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR +CREATE OR REPLACE 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, c text default '1'); +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('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 'b' ON CONVERSION ERROR); --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 domain +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 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); --ok +SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error +SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok +SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error +SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok +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 + +-----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); + +-----safe cast from bytea type to other data types +SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR); + +-----safe cast from bit type to other data types +SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL 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 test_safecast1, + CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2; +\sv safecastview + +CREATE VIEW safecastview1 AS +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2; +\sv safecastview1 +SELECT * FROM safecastview1; + +--error, default expression is mutable +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; +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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 018b5919cf66..c3e6432ec35f 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2662,6 +2662,9 @@ STRLEN SV SYNCHRONIZATION_BARRIER SYSTEM_INFO +SafeTypeCast +SafeTypeCastExpr +SafeTypeCastState SampleScan SampleScanGetSampleSize_function SampleScanState