diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/utils/adt/timestamp.c | 39 | ||||
-rw-r--r-- | src/include/datatype/timestamp.h | 1 | ||||
-rw-r--r-- | src/test/regress/expected/interval.out | 27 | ||||
-rw-r--r-- | src/test/regress/sql/interval.sql | 15 |
4 files changed, 71 insertions, 11 deletions
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index e172e906142..647b97aca69 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -1509,24 +1509,41 @@ make_interval(PG_FUNCTION_ARGS) Interval *result; /* - * Reject out-of-range inputs. We really ought to check the integer - * inputs as well, but it's not entirely clear what limits to apply. + * Reject out-of-range inputs. We reject any input values that cause + * integer overflow of the corresponding interval fields. */ if (isinf(secs) || isnan(secs)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range"))); + goto out_of_range; result = (Interval *) palloc(sizeof(Interval)); - result->month = years * MONTHS_PER_YEAR + months; - result->day = weeks * 7 + days; - secs = rint(secs * USECS_PER_SEC); - result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) + - mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) + - (int64) secs; + /* years and months -> months */ + if (pg_mul_s32_overflow(years, MONTHS_PER_YEAR, &result->month) || + pg_add_s32_overflow(result->month, months, &result->month)) + goto out_of_range; + + /* weeks and days -> days */ + if (pg_mul_s32_overflow(weeks, DAYS_PER_WEEK, &result->day) || + pg_add_s32_overflow(result->day, days, &result->day)) + goto out_of_range; + + /* hours and mins -> usecs (cannot overflow 64-bit) */ + result->time = hours * USECS_PER_HOUR + mins * USECS_PER_MINUTE; + + /* secs -> usecs */ + secs = rint(float8_mul(secs, USECS_PER_SEC)); + if (!FLOAT8_FITS_IN_INT64(secs) || + pg_add_s64_overflow(result->time, (int64) secs, &result->time)) + goto out_of_range; PG_RETURN_INTERVAL_P(result); + +out_of_range: + ereport(ERROR, + errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")); + + PG_RETURN_NULL(); /* keep compiler quiet */ } /* EncodeSpecialTimestamp() diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h index ab8ccf89ca9..1a6390585c8 100644 --- a/src/include/datatype/timestamp.h +++ b/src/include/datatype/timestamp.h @@ -114,6 +114,7 @@ struct pg_itm_in * 30 days. */ #define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */ +#define DAYS_PER_WEEK 7 #define HOURS_PER_DAY 24 /* assume no daylight savings time changes */ /* diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index c0ca8e041b1..75d19d65949 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -1587,6 +1587,33 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago" LINE 1: select interval '-2147483648 months -2147483648 days -922337... ^ +-- overflowing using make_interval +select make_interval(years := 178956971); +ERROR: interval out of range +select make_interval(years := -178956971); +ERROR: interval out of range +select make_interval(years := 1, months := 2147483647); +ERROR: interval out of range +select make_interval(years := -1, months := -2147483648); +ERROR: interval out of range +select make_interval(weeks := 306783379); +ERROR: interval out of range +select make_interval(weeks := -306783379); +ERROR: interval out of range +select make_interval(weeks := 1, days := 2147483647); +ERROR: interval out of range +select make_interval(weeks := -1, days := -2147483648); +ERROR: interval out of range +select make_interval(secs := 1e308); +ERROR: value out of range: overflow +select make_interval(secs := 1e18); +ERROR: interval out of range +select make_interval(secs := -1e18); +ERROR: interval out of range +select make_interval(mins := 1, secs := 9223372036800.0); +ERROR: interval out of range +select make_interval(mins := -1, secs := -9223372036800.0); +ERROR: interval out of range -- test that INT_MIN number is formatted properly SET IntervalStyle to postgres; select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 038fc508d0c..a0a373f08bd 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -511,6 +511,21 @@ select interval '-2147483648 days ago'; select interval '-9223372036854775808 microseconds ago'; select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago'; +-- overflowing using make_interval +select make_interval(years := 178956971); +select make_interval(years := -178956971); +select make_interval(years := 1, months := 2147483647); +select make_interval(years := -1, months := -2147483648); +select make_interval(weeks := 306783379); +select make_interval(weeks := -306783379); +select make_interval(weeks := 1, days := 2147483647); +select make_interval(weeks := -1, days := -2147483648); +select make_interval(secs := 1e308); +select make_interval(secs := 1e18); +select make_interval(secs := -1e18); +select make_interval(mins := 1, secs := 9223372036800.0); +select make_interval(mins := -1, secs := -9223372036800.0); + -- test that INT_MIN number is formatted properly SET IntervalStyle to postgres; select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; |