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  <br...@momjian.us>        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 (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to