aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/utils/adt/timestamp.c39
-rw-r--r--src/include/datatype/timestamp.h1
-rw-r--r--src/test/regress/expected/interval.out27
-rw-r--r--src/test/regress/sql/interval.sql15
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';