diff options
author | Michael Paquier <michael@paquier.xyz> | 2024-12-27 13:32:40 +0900 |
---|---|---|
committer | Michael Paquier <michael@paquier.xyz> | 2024-12-27 13:32:40 +0900 |
commit | d85ce012f99f63249bb45a78fcecb7a2383920b1 (patch) | |
tree | 08f039c5367c16204b3ed9944676f79138836708 /src/backend/utils/adt/timestamp.c | |
parent | 61cac71c23686f47c785dc69d3c15cb4304d106f (diff) | |
download | postgresql-d85ce012f99f63249bb45a78fcecb7a2383920b1.tar.gz postgresql-d85ce012f99f63249bb45a78fcecb7a2383920b1.zip |
Improve handling of date_trunc() units for infinite input values
Previously, if an infinite value was passed to date_trunc(), then the
same infinite value would always be returned regardless of the field
unit given by the caller. This commit updates the function so that an
error is returned when an invalid unit is passed to date_trunc() with an
infinite value.
This matches the behavior of date_trunc() with a finite value and
date_part() with an infinite value, making the handling of interval,
timestamp and timestamptz more consistent across the board for these two
functions.
Some tests are added to cover all these new failure cases, with an
unsupported unit and infinite values for the three data types. There
were no test cases in core that checked all these patterns up to now.
Author: Joseph Koshakow
Discussion: https://postgr.es/m/CAAvxfHc4084dGzEJR0_pBZkDuqbPGc5wn7gK_M0XR_kRiCdUJQ@mail.gmail.com
Diffstat (limited to 'src/backend/utils/adt/timestamp.c')
-rw-r--r-- | src/backend/utils/adt/timestamp.c | 121 |
1 files changed, 102 insertions, 19 deletions
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 18d7d8a108a..1b33eb6ea8d 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -4620,9 +4620,6 @@ timestamp_trunc(PG_FUNCTION_ARGS) struct pg_tm tt, *tm = &tt; - if (TIMESTAMP_NOT_FINITE(timestamp)) - PG_RETURN_TIMESTAMP(timestamp); - lowunits = downcase_truncate_identifier(VARDATA_ANY(units), VARSIZE_ANY_EXHDR(units), false); @@ -4631,6 +4628,39 @@ timestamp_trunc(PG_FUNCTION_ARGS) if (type == UNITS) { + if (TIMESTAMP_NOT_FINITE(timestamp)) + { + /* + * Errors thrown here for invalid units should exactly match those + * below, else there will be unexpected discrepancies between + * finite- and infinite-input cases. + */ + switch (val) + { + case DTK_WEEK: + case DTK_MILLENNIUM: + case DTK_CENTURY: + case DTK_DECADE: + case DTK_YEAR: + case DTK_QUARTER: + case DTK_MONTH: + case DTK_DAY: + case DTK_HOUR: + case DTK_MINUTE: + case DTK_SECOND: + case DTK_MILLISEC: + case DTK_MICROSEC: + PG_RETURN_TIMESTAMP(timestamp); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMESTAMPOID)))); + result = 0; + } + } + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), @@ -4836,6 +4866,40 @@ timestamptz_trunc_internal(text *units, TimestampTz timestamp, pg_tz *tzp) if (type == UNITS) { + if (TIMESTAMP_NOT_FINITE(timestamp)) + { + /* + * Errors thrown here for invalid units should exactly match those + * below, else there will be unexpected discrepancies between + * finite- and infinite-input cases. + */ + switch (val) + { + case DTK_WEEK: + case DTK_MILLENNIUM: + case DTK_CENTURY: + case DTK_DECADE: + case DTK_YEAR: + case DTK_QUARTER: + case DTK_MONTH: + case DTK_DAY: + case DTK_HOUR: + case DTK_MINUTE: + case DTK_SECOND: + case DTK_MILLISEC: + case DTK_MICROSEC: + PG_RETURN_TIMESTAMPTZ(timestamp); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMESTAMPTZOID)))); + result = 0; + } + } + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, tzp) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), @@ -4966,9 +5030,6 @@ timestamptz_trunc(PG_FUNCTION_ARGS) TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); TimestampTz result; - if (TIMESTAMP_NOT_FINITE(timestamp)) - PG_RETURN_TIMESTAMPTZ(timestamp); - result = timestamptz_trunc_internal(units, timestamp, session_timezone); PG_RETURN_TIMESTAMPTZ(result); @@ -4987,13 +5048,6 @@ timestamptz_trunc_zone(PG_FUNCTION_ARGS) pg_tz *tzp; /* - * timestamptz_zone() doesn't look up the zone for infinite inputs, so we - * don't do so here either. - */ - if (TIMESTAMP_NOT_FINITE(timestamp)) - PG_RETURN_TIMESTAMP(timestamp); - - /* * Look up the requested timezone. */ tzp = lookup_timezone(zone); @@ -5020,12 +5074,6 @@ interval_trunc(PG_FUNCTION_ARGS) result = (Interval *) palloc(sizeof(Interval)); - if (INTERVAL_NOT_FINITE(interval)) - { - memcpy(result, interval, sizeof(Interval)); - PG_RETURN_INTERVAL_P(result); - } - lowunits = downcase_truncate_identifier(VARDATA_ANY(units), VARSIZE_ANY_EXHDR(units), false); @@ -5034,6 +5082,41 @@ interval_trunc(PG_FUNCTION_ARGS) if (type == UNITS) { + if (INTERVAL_NOT_FINITE(interval)) + { + /* + * Errors thrown here for invalid units should exactly match those + * below, else there will be unexpected discrepancies between + * finite- and infinite-input cases. + */ + switch (val) + { + case DTK_MILLENNIUM: + case DTK_CENTURY: + case DTK_DECADE: + case DTK_YEAR: + case DTK_QUARTER: + case DTK_MONTH: + case DTK_DAY: + case DTK_HOUR: + case DTK_MINUTE: + case DTK_SECOND: + case DTK_MILLISEC: + case DTK_MICROSEC: + memcpy(result, interval, sizeof(Interval)); + PG_RETURN_INTERVAL_P(result); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(INTERVALOID)), + (val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0)); + result = 0; + } + } + interval2itm(*interval, tm); switch (val) { |