Compare commits

...

2 Commits

Author SHA1 Message Date
Tom Lane
9b41d1d634 Throw a more on-point error for functions depending on columns.
ALTER COLUMN TYPE wasn't expecting to find any pg_proc objects
depending on the column whose type is to be altered.  That indeed
wasn't possible when this code was written, but it is possible
since we introduced new-style SQL function bodies.

It's about as difficult to fix this case as it is to fix dependent
views, and we've been punting on those for years, so I don't feel
too awful about punting for functions too.  (I sure wouldn't risk
back-patching such code.)  So just throw a more user-facing error.
Also, adjust some of the existing comments to reflect that these
are all pretty much the same issue.

(This patch also fixes it so we will tolerate finding such a
dependency during ALTER COLUMN SET EXPRESSION; in that, we need
not do anything to the function, so no error is wanted.  That
problem is new in HEAD.)

Per bug #18449 from Alexander Lakhin.  Back-patch to v14 where
we added new-style SQL functions.

Discussion: https://postgr.es/m/18449-f8248467aaa294d5@postgresql.org
2024-04-28 14:34:21 -04:00
Tom Lane
e6e3ee5b7e Detect more overflows in timestamp[tz]_pl_interval.
In commit 25cd2d640 I (tgl) opined that "The additions of the months
and microseconds fields could also overflow, of course.  However,
I believe we need no additional checks there; the existing range
checks should catch such cases".  This is demonstrably wrong however
for the microseconds field, and given that discovery it seems prudent
to be paranoid about the months addition as well.

Report and patch by Joseph Koshakow.  As before, back-patch to all
supported branches.  (However, the test case doesn't work before
v15 because we didn't allow wider-than-int32 numbers in interval
literals.  A variant test could probably be built that fits within
that restriction, but it didn't seem worth the trouble.)

Discussion: https://postgr.es/m/CAAvxfHf77sRHKoEzUw9_cMYSpbpNS2C+J_+8Dq4+0oi8iKopeA@mail.gmail.com
2024-04-28 13:42:13 -04:00
4 changed files with 47 additions and 9 deletions

View File

@ -12704,8 +12704,29 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
RememberConstraintForRebuilding(foundObject.objectId, tab); RememberConstraintForRebuilding(foundObject.objectId, tab);
break; break;
case OCLASS_PROC:
/*
* A new-style SQL function can depend on a column, if that
* column is referenced in the parsed function body. Ideally
* we'd automatically update the function by deparsing and
* reparsing it, but that's risky and might well fail anyhow.
* FIXME someday.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a function or procedure"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
case OCLASS_REWRITE: case OCLASS_REWRITE:
/* XXX someday see if we can cope with revising views */
/*
* View/rule bodies have pretty much the same issues as
* function bodies. FIXME someday.
*/
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"), errmsg("cannot alter type of a column used by a view or rule"),
@ -12721,9 +12742,9 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
* specified as an update target, or because the column is * specified as an update target, or because the column is
* used in the trigger's WHEN condition. The first case would * used in the trigger's WHEN condition. The first case would
* not require any extra work, but the second case would * not require any extra work, but the second case would
* require updating the WHEN expression, which will take a * require updating the WHEN expression, which has the same
* significant amount of new code. Since we can't easily tell * issues as above. Since we can't easily tell which case
* which case applies, we punt for both. FIXME someday. * applies, we punt for both. FIXME someday.
*/ */
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@ -12795,7 +12816,6 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
RememberStatisticsForRebuilding(foundObject.objectId, tab); RememberStatisticsForRebuilding(foundObject.objectId, tab);
break; break;
case OCLASS_PROC:
case OCLASS_TYPE: case OCLASS_TYPE:
case OCLASS_CAST: case OCLASS_CAST:
case OCLASS_COLLATION: case OCLASS_COLLATION:

View File

@ -2915,7 +2915,10 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range"))); errmsg("timestamp out of range")));
tm->tm_mon += span->month; if (pg_add_s32_overflow(tm->tm_mon, span->month, &tm->tm_mon))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
if (tm->tm_mon > MONTHS_PER_YEAR) if (tm->tm_mon > MONTHS_PER_YEAR)
{ {
tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR; tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
@ -2967,7 +2970,10 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
errmsg("timestamp out of range"))); errmsg("timestamp out of range")));
} }
timestamp += span->time; if (pg_add_s64_overflow(timestamp, span->time, &timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
if (!IS_VALID_TIMESTAMP(timestamp)) if (!IS_VALID_TIMESTAMP(timestamp))
ereport(ERROR, ereport(ERROR,
@ -3029,7 +3035,10 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range"))); errmsg("timestamp out of range")));
tm->tm_mon += span->month; if (pg_add_s32_overflow(tm->tm_mon, span->month, &tm->tm_mon))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
if (tm->tm_mon > MONTHS_PER_YEAR) if (tm->tm_mon > MONTHS_PER_YEAR)
{ {
tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR; tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
@ -3088,7 +3097,10 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
errmsg("timestamp out of range"))); errmsg("timestamp out of range")));
} }
timestamp += span->time; if (pg_add_s64_overflow(timestamp, span->time, &timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
if (!IS_VALID_TIMESTAMP(timestamp)) if (!IS_VALID_TIMESTAMP(timestamp))
ereport(ERROR, ereport(ERROR,

View File

@ -375,6 +375,8 @@ SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days'
SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range"; SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range";
ERROR: timestamp out of range ERROR: timestamp out of range
SELECT timestamp without time zone '294276-12-31 23:59:59' + interval '9223372036854775807 microseconds' AS "out of range";
ERROR: timestamp out of range
SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days"; SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days";
106751991 Days 106751991 Days
------------------ ------------------
@ -637,6 +639,8 @@ SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS
SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range"; SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range";
ERROR: timestamp out of range ERROR: timestamp out of range
SELECT timestamp with time zone '294276-12-31 23:59:59 UTC' + interval '9223372036854775807 microseconds' AS "out of range";
ERROR: timestamp out of range
SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True"; SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True";
True True
------ ------

View File

@ -87,6 +87,7 @@ SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '106000000 days'
SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '107000000 days' AS "Jan 20, 288244"; SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '107000000 days' AS "Jan 20, 288244";
SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days' AS "Dec 31, 294276"; SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days' AS "Dec 31, 294276";
SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range"; SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range";
SELECT timestamp without time zone '294276-12-31 23:59:59' + interval '9223372036854775807 microseconds' AS "out of range";
SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days"; SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days";
-- Shorthand values -- Shorthand values
@ -119,6 +120,7 @@ SELECT timestamp with time zone '1999-03-01' - interval '1 second' AS "Feb 28";
SELECT timestamp with time zone '2000-03-01' - interval '1 second' AS "Feb 29"; SELECT timestamp with time zone '2000-03-01' - interval '1 second' AS "Feb 29";
SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS "Dec 31"; SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS "Dec 31";
SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range"; SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range";
SELECT timestamp with time zone '294276-12-31 23:59:59 UTC' + interval '9223372036854775807 microseconds' AS "out of range";
SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True"; SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True";
SELECT (timestamp with time zone 'today' = (timestamp with time zone 'tomorrow' - interval '1 day')) as "True"; SELECT (timestamp with time zone 'today' = (timestamp with time zone 'tomorrow' - interval '1 day')) as "True";