On Sun, Jun 23, 2013 at 03:00:59PM +0200, Rok Kralj wrote:
> Hi, after studying ITERVAL and having a long chat with RhoidumToad and
> StuckMojo on #postgresql, I am presenting you 3 bugs regarding INTERVAL.
OK, I am going to merge this with the previous report/patch which fixes:
SELECT INTERVAL '2000000000 years';
ERROR: interval out of range
LINE 1: SELECT INTERVAL '2000000000 years';
and
SELECT INTERVAL '2000000000-3 years';
ERROR: interval field value out of range: "2000000000-3 years"
LINE 1: SELECT INTERVAL '2000000000-3 years';
> As far as I understand, the Interval struct (binary internal representation)
> consists of:
>
> int32 months
> int32 days
> int64 microseconds
>
> 1. OUTPUT ERRORS: Since 2^63 microseconds equals 2,562,047,788 > 2^31 hours,
> the overflow in pg_tm when displaying the value causes overflow. The value of
> Interval struct is actually correct, error happens only on displaying it.
>
> SELECT INTERVAL '2147483647 hours' + INTERVAL '5 hours'
> "-2147483644:00:00"
Fixed:
SELECT INTERVAL '2147483647 hours' + INTERVAL '5 hours';
ERROR: interval out of range
> Even wireder:
>
> SELECT INTERVAL '2147483647 hours' + '1 hour'
> "--2147483648:00:00"
Fixed:
SELECT INTERVAL '2147483647 hours' + '1 hour';
ERROR: interval out of range
> notice the double minus? Don't ask how I came across this two bugs.
>
> 2. OPERATION ERRORS: When summing two intervals, the user is not notified when
> overflow occurs:
>
> SELECT INT '2147483647' + INT '1'
> ERROR: integer out of range
>
> SELECT INTERVAL '2147483647 days' + INTERVAL '1 day'
> "-2147483648 days"
>
> This should be analogous.
Fixed:
SELECT INTERVAL '2147483647 days' + INTERVAL '1 day';
ERROR: interval out of range
> 3. PARSER / INPUT ERRORS:
>
> This is perhaps the hardest one to explain, since this is an architectural
> flaw. You are checking the overflows when parsing string -> pg_tm struct.
> However, at this point, the parser doesn't know, that weeks and days are going
> to get combined, or years are going to get converted to months, for example.
>
> Unawarness of underlying Interval struct causes two types of suberrors:
>
> a) False positive
>
> SELECT INTERVAL '2147483648 microseconds'
> ERROR: interval field value out of range: "2147483648 microseconds"
>
> This is not right. Microseconds are internally stored as 64 bit signed
> integer.
> The catch is: this amount of microseconds is representable in Interval data
> structure, this shouldn't be an error.
I don't see a way to fix this as we are testing too early to know what
type of value it is, as you stated.
> b) False negative
>
> SELECT INTERVAL '1000000000 years'
> "-73741824 years"
>
> We don't catch errors like this, because parser only checks for overflow in
> pg_tm. If overflow laters happens in Interval, we don't seem to care.
Fixed:
SELECT INTERVAL '1000000000 years';
ERROR: interval out of range
LINE 1: SELECT INTERVAL '1000000000 years';
> 4. POSSIBLE SOLUTIONS:
>
> a) Move the overflow checking just after the conversion of pg_tm -> Interval
> is
> made. This way, you can accurately predict if the result is really not
> store-able.
>
> b) Because of 1), you have to declare tm_hour as int64, if you want to use
> that
> for the output. But, why not use Interval struct for printing directly,
> without
> intermediate pg_tm?
>
> 5. Offtopic: Correct the documentation, INTERVAL data size is 16 bytes, not
> 12.
Fixed.
Patch attached.
--
Bruce Momjian <[email protected]> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ Everyone has their own god. +
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
new file mode 100644
index 6bf4cf6..b7d7d80
*** a/doc/src/sgml/datatype.sgml
--- b/doc/src/sgml/datatype.sgml
*************** SELECT E'\\xDEADBEEF';
*** 1587,1593 ****
</row>
<row>
<entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry>
! <entry>12 bytes</entry>
<entry>time interval</entry>
<entry>-178000000 years</entry>
<entry>178000000 years</entry>
--- 1587,1593 ----
</row>
<row>
<entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry>
! <entry>16 bytes</entry>
<entry>time interval</entry>
<entry>-178000000 years</entry>
<entry>178000000 years</entry>
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
new file mode 100644
index a61b40e..ae4e9e7
*** a/src/backend/utils/adt/datetime.c
--- b/src/backend/utils/adt/datetime.c
*************** DecodeInterval(char **field, int *ftype,
*** 2976,2981 ****
--- 2976,2983 ----
type = DTK_MONTH;
if (*field[i] == '-')
val2 = -val2;
+ if (((float)val * MONTHS_PER_YEAR + val2) > INT_MAX)
+ return DTERR_FIELD_OVERFLOW;
val = val * MONTHS_PER_YEAR + val2;
fval = 0;
}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
new file mode 100644
index 4581862..8b28b63
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
***************
*** 41,46 ****
--- 41,47 ----
#error -ffast-math is known to break this code
#endif
+ #define SAMESIGN(a,b) (((a) < 0) == ((b) < 0))
/* Set at postmaster start */
TimestampTz PgStartTime;
*************** interval2tm(Interval span, struct pg_tm
*** 1694,1700 ****
#ifdef HAVE_INT64_TIMESTAMP
tfrac = time / USECS_PER_HOUR;
time -= tfrac * USECS_PER_HOUR;
! tm->tm_hour = tfrac; /* could overflow ... */
tfrac = time / USECS_PER_MINUTE;
time -= tfrac * USECS_PER_MINUTE;
tm->tm_min = tfrac;
--- 1695,1705 ----
#ifdef HAVE_INT64_TIMESTAMP
tfrac = time / USECS_PER_HOUR;
time -= tfrac * USECS_PER_HOUR;
! tm->tm_hour = tfrac;
! if (!SAMESIGN(tm->tm_hour, tfrac))
! ereport(ERROR,
! (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
! errmsg("interval out of range")));
tfrac = time / USECS_PER_MINUTE;
time -= tfrac * USECS_PER_MINUTE;
tm->tm_min = tfrac;
*************** recalc:
*** 1725,1730 ****
--- 1730,1737 ----
int
tm2interval(struct pg_tm * tm, fsec_t fsec, Interval *span)
{
+ if ((float)tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon > INT_MAX)
+ return -1;
span->month = tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
span->day = tm->tm_mday;
#ifdef HAVE_INT64_TIMESTAMP
*************** interval_pl(PG_FUNCTION_ARGS)
*** 2872,2879 ****
--- 2879,2903 ----
result = (Interval *) palloc(sizeof(Interval));
result->month = span1->month + span2->month;
+ if (SAMESIGN(span1->month, span2->month) &&
+ !SAMESIGN(result->month, span1->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
result->day = span1->day + span2->day;
+ if (SAMESIGN(span1->day, span2->day) &&
+ !SAMESIGN(result->day, span1->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
result->time = span1->time + span2->time;
+ if (SAMESIGN(span1->time, span2->time) &&
+ !SAMESIGN(result->time, span1->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c
new file mode 100644
index efa775d..e1307e3
*** a/src/interfaces/ecpg/pgtypeslib/interval.c
--- b/src/interfaces/ecpg/pgtypeslib/interval.c
*************** recalc:
*** 1036,1041 ****
--- 1036,1043 ----
static int
tm2interval(struct tm * tm, fsec_t fsec, interval * span)
{
+ if ((float)tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon > INT_MAX)
+ return -1;
span->month = tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
#ifdef HAVE_INT64_TIMESTAMP
span->time = (((((((tm->tm_mday * INT64CONST(24)) +
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers