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()
* 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 */
/*
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';
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';