Prevent internal overflows in date-vs-timestamp and related comparisons.
authorTom Lane <[email protected]>
Wed, 7 Oct 2020 21:10:26 +0000 (17:10 -0400)
committerTom Lane <[email protected]>
Wed, 7 Oct 2020 21:10:26 +0000 (17:10 -0400)
The date-vs-timestamp, date-vs-timestamptz, and timestamp-vs-timestamptz
comparators all worked by promoting the first type to the second and
then doing a simple same-type comparison.  This works fine, except
when the conversion result is out of range, in which case we throw an
entirely avoidable error.  The sources of such failures are
(a) type date can represent dates much farther in the future than
the timestamp types can;
(b) timezone rotation might cause a just-in-range timestamp value to
become a just-out-of-range timestamptz value.

Up to now we just ignored these corner-case issues, but now we have
an actual user complaint (bug #16657 from Huss EL-Sheikh), so let's
do something about it.

It turns out that commit 52ad1e659 already built all the necessary
infrastructure to support error-free comparisons, but neglected to
actually use it in the main-line code paths.  Fix that, do a little
bit of code style review, and remove the now-duplicate logic in
jsonpath_exec.c.

Back-patch to v13 where 52ad1e659 came in.  We could take this back
further by back-patching said infrastructure, but given the small
number of complaints so far, I don't feel a great need to.

Discussion: https://postgr.es/m/16657-cde2f876d8cc7971@postgresql.org

src/backend/utils/adt/date.c
src/backend/utils/adt/jsonpath_exec.c
src/backend/utils/adt/timestamp.c
src/include/utils/date.h
src/include/utils/timestamp.h
src/test/regress/expected/horology.out
src/test/regress/sql/horology.sql

index 057051fa85506cc66ad195515a2f2d6d510cd837..a470cf890a205a93814b808aa9774fb78ad9387f 100644 (file)
@@ -554,15 +554,24 @@ date_mii(PG_FUNCTION_ARGS)
 /*
  * Promote date to timestamp.
  *
- * On overflow error is thrown if 'overflow' is NULL.  Otherwise, '*overflow'
- * is set to -1 (+1) when result value exceed lower (upper) boundary and zero
- * returned.
+ * On successful conversion, *overflow is set to zero if it's not NULL.
+ *
+ * If the date is finite but out of the valid range for timestamp, then:
+ * if overflow is NULL, we throw an out-of-range error.
+ * if overflow is not NULL, we store +1 or -1 there to indicate the sign
+ * of the overflow, and return the appropriate timestamp infinity.
+ *
+ * Note: *overflow = -1 is actually not possible currently, since both
+ * datatypes have the same lower bound, Julian day zero.
  */
 Timestamp
 date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
 {
    Timestamp   result;
 
+   if (overflow)
+       *overflow = 0;
+
    if (DATE_IS_NOBEGIN(dateVal))
        TIMESTAMP_NOBEGIN(result);
    else if (DATE_IS_NOEND(dateVal))
@@ -570,7 +579,6 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
    else
    {
        /*
-        * Date's range is wider than timestamp's, so check for boundaries.
         * Since dates have the same minimum values as timestamps, only upper
         * boundary need be checked for overflow.
         */
@@ -579,7 +587,8 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
            if (overflow)
            {
                *overflow = 1;
-               return (Timestamp) 0;
+               TIMESTAMP_NOEND(result);
+               return result;
            }
            else
            {
@@ -597,7 +606,7 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
 }
 
 /*
- * Single-argument version of date2timestamp_opt_overflow().
+ * Promote date to timestamp, throwing error for overflow.
  */
 static TimestampTz
 date2timestamp(DateADT dateVal)
@@ -608,9 +617,12 @@ date2timestamp(DateADT dateVal)
 /*
  * Promote date to timestamp with time zone.
  *
- * On overflow error is thrown if 'overflow' is NULL.  Otherwise, '*overflow'
- * is set to -1 (+1) when result value exceed lower (upper) boundary and zero
- * returned.
+ * On successful conversion, *overflow is set to zero if it's not NULL.
+ *
+ * If the date is finite but out of the valid range for timestamptz, then:
+ * if overflow is NULL, we throw an out-of-range error.
+ * if overflow is not NULL, we store +1 or -1 there to indicate the sign
+ * of the overflow, and return the appropriate timestamptz infinity.
  */
 TimestampTz
 date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
@@ -620,6 +632,9 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
               *tm = &tt;
    int         tz;
 
+   if (overflow)
+       *overflow = 0;
+
    if (DATE_IS_NOBEGIN(dateVal))
        TIMESTAMP_NOBEGIN(result);
    else if (DATE_IS_NOEND(dateVal))
@@ -627,7 +642,6 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
    else
    {
        /*
-        * Date's range is wider than timestamp's, so check for boundaries.
         * Since dates have the same minimum values as timestamps, only upper
         * boundary need be checked for overflow.
         */
@@ -636,7 +650,8 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
            if (overflow)
            {
                *overflow = 1;
-               return (TimestampTz) 0;
+               TIMESTAMP_NOEND(result);
+               return result;
            }
            else
            {
@@ -664,13 +679,15 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
            if (overflow)
            {
                if (result < MIN_TIMESTAMP)
+               {
                    *overflow = -1;
+                   TIMESTAMP_NOBEGIN(result);
+               }
                else
                {
-                   Assert(result >= END_TIMESTAMP);
                    *overflow = 1;
+                   TIMESTAMP_NOEND(result);
                }
-               return (TimestampTz) 0;
            }
            else
            {
@@ -685,7 +702,7 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
 }
 
 /*
- * Single-argument version of date2timestamptz_opt_overflow().
+ * Promote date to timestamptz, throwing error for overflow.
  */
 static TimestampTz
 date2timestamptz(DateADT dateVal)
@@ -726,16 +743,30 @@ date2timestamp_no_overflow(DateADT dateVal)
  * Crosstype comparison functions for dates
  */
 
+int32
+date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2)
+{
+   Timestamp   dt1;
+   int         overflow;
+
+   dt1 = date2timestamp_opt_overflow(dateVal, &overflow);
+   if (overflow > 0)
+   {
+       /* dt1 is larger than any finite timestamp, but less than infinity */
+       return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
+   }
+   Assert(overflow == 0);      /* -1 case cannot occur */
+
+   return timestamp_cmp_internal(dt1, dt2);
+}
+
 Datum
 date_eq_timestamp(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    Timestamp   dt2 = PG_GETARG_TIMESTAMP(1);
-   Timestamp   dt1;
-
-   dt1 = date2timestamp(dateVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) == 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) == 0);
 }
 
 Datum
@@ -743,11 +774,8 @@ date_ne_timestamp(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    Timestamp   dt2 = PG_GETARG_TIMESTAMP(1);
-   Timestamp   dt1;
 
-   dt1 = date2timestamp(dateVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) != 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) != 0);
 }
 
 Datum
@@ -755,11 +783,8 @@ date_lt_timestamp(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    Timestamp   dt2 = PG_GETARG_TIMESTAMP(1);
-   Timestamp   dt1;
 
-   dt1 = date2timestamp(dateVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) < 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) < 0);
 }
 
 Datum
@@ -767,11 +792,8 @@ date_gt_timestamp(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    Timestamp   dt2 = PG_GETARG_TIMESTAMP(1);
-   Timestamp   dt1;
 
-   dt1 = date2timestamp(dateVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) > 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) > 0);
 }
 
 Datum
@@ -779,11 +801,8 @@ date_le_timestamp(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    Timestamp   dt2 = PG_GETARG_TIMESTAMP(1);
-   Timestamp   dt1;
 
-   dt1 = date2timestamp(dateVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) <= 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) <= 0);
 }
 
 Datum
@@ -791,11 +810,8 @@ date_ge_timestamp(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    Timestamp   dt2 = PG_GETARG_TIMESTAMP(1);
-   Timestamp   dt1;
 
-   dt1 = date2timestamp(dateVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) >= 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) >= 0);
 }
 
 Datum
@@ -803,11 +819,29 @@ date_cmp_timestamp(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    Timestamp   dt2 = PG_GETARG_TIMESTAMP(1);
-   Timestamp   dt1;
 
-   dt1 = date2timestamp(dateVal);
+   PG_RETURN_INT32(date_cmp_timestamp_internal(dateVal, dt2));
+}
 
-   PG_RETURN_INT32(timestamp_cmp_internal(dt1, dt2));
+int32
+date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2)
+{
+   TimestampTz dt1;
+   int         overflow;
+
+   dt1 = date2timestamptz_opt_overflow(dateVal, &overflow);
+   if (overflow > 0)
+   {
+       /* dt1 is larger than any finite timestamp, but less than infinity */
+       return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
+   }
+   if (overflow < 0)
+   {
+       /* dt1 is less than any finite timestamp, but more than -infinity */
+       return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+   }
+
+   return timestamptz_cmp_internal(dt1, dt2);
 }
 
 Datum
@@ -815,11 +849,8 @@ date_eq_timestamptz(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
 
-   dt1 = date2timestamptz(dateVal);
-
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) == 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) == 0);
 }
 
 Datum
@@ -827,11 +858,8 @@ date_ne_timestamptz(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
 
-   dt1 = date2timestamptz(dateVal);
-
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) != 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) != 0);
 }
 
 Datum
@@ -839,11 +867,8 @@ date_lt_timestamptz(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
 
-   dt1 = date2timestamptz(dateVal);
-
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) < 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) < 0);
 }
 
 Datum
@@ -851,11 +876,8 @@ date_gt_timestamptz(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
 
-   dt1 = date2timestamptz(dateVal);
-
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) > 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) > 0);
 }
 
 Datum
@@ -863,11 +885,8 @@ date_le_timestamptz(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
 
-   dt1 = date2timestamptz(dateVal);
-
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) <= 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) <= 0);
 }
 
 Datum
@@ -875,11 +894,8 @@ date_ge_timestamptz(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
 
-   dt1 = date2timestamptz(dateVal);
-
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) >= 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) >= 0);
 }
 
 Datum
@@ -887,11 +903,8 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS)
 {
    DateADT     dateVal = PG_GETARG_DATEADT(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
-
-   dt1 = date2timestamptz(dateVal);
 
-   PG_RETURN_INT32(timestamptz_cmp_internal(dt1, dt2));
+   PG_RETURN_INT32(date_cmp_timestamptz_internal(dateVal, dt2));
 }
 
 Datum
@@ -899,11 +912,8 @@ timestamp_eq_date(PG_FUNCTION_ARGS)
 {
    Timestamp   dt1 = PG_GETARG_TIMESTAMP(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   Timestamp   dt2;
 
-   dt2 = date2timestamp(dateVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) == 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) == 0);
 }
 
 Datum
@@ -911,11 +921,8 @@ timestamp_ne_date(PG_FUNCTION_ARGS)
 {
    Timestamp   dt1 = PG_GETARG_TIMESTAMP(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   Timestamp   dt2;
-
-   dt2 = date2timestamp(dateVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) != 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) != 0);
 }
 
 Datum
@@ -923,11 +930,8 @@ timestamp_lt_date(PG_FUNCTION_ARGS)
 {
    Timestamp   dt1 = PG_GETARG_TIMESTAMP(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   Timestamp   dt2;
 
-   dt2 = date2timestamp(dateVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) < 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) > 0);
 }
 
 Datum
@@ -935,11 +939,8 @@ timestamp_gt_date(PG_FUNCTION_ARGS)
 {
    Timestamp   dt1 = PG_GETARG_TIMESTAMP(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   Timestamp   dt2;
-
-   dt2 = date2timestamp(dateVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) > 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) < 0);
 }
 
 Datum
@@ -947,11 +948,8 @@ timestamp_le_date(PG_FUNCTION_ARGS)
 {
    Timestamp   dt1 = PG_GETARG_TIMESTAMP(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   Timestamp   dt2;
 
-   dt2 = date2timestamp(dateVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) <= 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) >= 0);
 }
 
 Datum
@@ -959,11 +957,8 @@ timestamp_ge_date(PG_FUNCTION_ARGS)
 {
    Timestamp   dt1 = PG_GETARG_TIMESTAMP(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   Timestamp   dt2;
-
-   dt2 = date2timestamp(dateVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) >= 0);
+   PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) <= 0);
 }
 
 Datum
@@ -971,11 +966,8 @@ timestamp_cmp_date(PG_FUNCTION_ARGS)
 {
    Timestamp   dt1 = PG_GETARG_TIMESTAMP(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   Timestamp   dt2;
 
-   dt2 = date2timestamp(dateVal);
-
-   PG_RETURN_INT32(timestamp_cmp_internal(dt1, dt2));
+   PG_RETURN_INT32(-date_cmp_timestamp_internal(dateVal, dt1));
 }
 
 Datum
@@ -983,11 +975,8 @@ timestamptz_eq_date(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   TimestampTz dt2;
-
-   dt2 = date2timestamptz(dateVal);
 
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) == 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) == 0);
 }
 
 Datum
@@ -995,11 +984,8 @@ timestamptz_ne_date(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   TimestampTz dt2;
 
-   dt2 = date2timestamptz(dateVal);
-
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) != 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) != 0);
 }
 
 Datum
@@ -1007,11 +993,8 @@ timestamptz_lt_date(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   TimestampTz dt2;
-
-   dt2 = date2timestamptz(dateVal);
 
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) < 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) > 0);
 }
 
 Datum
@@ -1019,11 +1002,8 @@ timestamptz_gt_date(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   TimestampTz dt2;
 
-   dt2 = date2timestamptz(dateVal);
-
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) > 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) < 0);
 }
 
 Datum
@@ -1031,11 +1011,8 @@ timestamptz_le_date(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   TimestampTz dt2;
-
-   dt2 = date2timestamptz(dateVal);
 
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) <= 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) >= 0);
 }
 
 Datum
@@ -1043,11 +1020,8 @@ timestamptz_ge_date(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   TimestampTz dt2;
 
-   dt2 = date2timestamptz(dateVal);
-
-   PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) >= 0);
+   PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) <= 0);
 }
 
 Datum
@@ -1055,11 +1029,8 @@ timestamptz_cmp_date(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    DateADT     dateVal = PG_GETARG_DATEADT(1);
-   TimestampTz dt2;
-
-   dt2 = date2timestamptz(dateVal);
 
-   PG_RETURN_INT32(timestamptz_cmp_internal(dt1, dt2));
+   PG_RETURN_INT32(-date_cmp_timestamptz_internal(dateVal, dt1));
 }
 
 /*
@@ -1079,6 +1050,7 @@ in_range_date_interval(PG_FUNCTION_ARGS)
    Timestamp   valStamp;
    Timestamp   baseStamp;
 
+   /* XXX we could support out-of-range cases here, perhaps */
    valStamp = date2timestamp(val);
    baseStamp = date2timestamp(base);
 
index 28be845770a6123602b906742617dde06256e5ae..1059f34130aee61a63bf31a310eb5242f0046a7b 100644 (file)
@@ -2602,93 +2602,36 @@ castTimeToTimeTz(Datum time, bool useTz)
    return DirectFunctionCall1(time_timetz, time);
 }
 
-/*---
- * Compares 'ts1' and 'ts2' timestamp, assuming that ts1 might be overflowed
- * during cast from another datatype.
- *
- * 'overflow1' specifies overflow of 'ts1' value:
- *  0 - no overflow,
- * -1 - exceed lower boundary,
- *  1 - exceed upper boundary.
- */
-static int
-cmpTimestampWithOverflow(Timestamp ts1, int overflow1, Timestamp ts2)
-{
-   /*
-    * All the timestamps we deal with in jsonpath are produced by
-    * to_datetime() method.  So, they should be valid.
-    */
-   Assert(IS_VALID_TIMESTAMP(ts2));
-
-   /*
-    * Timestamp, which exceed lower (upper) bound, is always lower (higher)
-    * than any valid timestamp except minus (plus) infinity.
-    */
-   if (overflow1)
-   {
-       if (overflow1 < 0)
-       {
-           if (TIMESTAMP_IS_NOBEGIN(ts2))
-               return 1;
-           else
-               return -1;
-       }
-       if (overflow1 > 0)
-       {
-           if (TIMESTAMP_IS_NOEND(ts2))
-               return -1;
-           else
-               return 1;
-       }
-   }
-
-   return timestamp_cmp_internal(ts1, ts2);
-}
-
 /*
- * Compare date to timestamptz without throwing overflow error during cast.
+ * Compare date to timestamp.
+ * Note that this doesn't involve any timezone considerations.
  */
 static int
 cmpDateToTimestamp(DateADT date1, Timestamp ts2, bool useTz)
 {
-   TimestampTz ts1;
-   int         overflow = 0;
-
-   ts1 = date2timestamp_opt_overflow(date1, &overflow);
-
-   return cmpTimestampWithOverflow(ts1, overflow, ts2);
+   return date_cmp_timestamp_internal(date1, ts2);
 }
 
 /*
- * Compare date to timestamptz without throwing overflow error during cast.
+ * Compare date to timestamptz.
  */
 static int
 cmpDateToTimestampTz(DateADT date1, TimestampTz tstz2, bool useTz)
 {
-   TimestampTz tstz1;
-   int         overflow = 0;
-
    checkTimezoneIsUsedForCast(useTz, "date", "timestamptz");
 
-   tstz1 = date2timestamptz_opt_overflow(date1, &overflow);
-
-   return cmpTimestampWithOverflow(tstz1, overflow, tstz2);
+   return date_cmp_timestamptz_internal(date1, tstz2);
 }
 
 /*
- * Compare timestamp to timestamptz without throwing overflow error during cast.
+ * Compare timestamp to timestamptz.
  */
 static int
 cmpTimestampToTimestampTz(Timestamp ts1, TimestampTz tstz2, bool useTz)
 {
-   TimestampTz tstz1;
-   int         overflow = 0;
-
    checkTimezoneIsUsedForCast(useTz, "timestamp", "timestamptz");
 
-   tstz1 = timestamp2timestamptz_opt_overflow(ts1, &overflow);
-
-   return cmpTimestampWithOverflow(tstz1, overflow, tstz2);
+   return timestamp_cmp_timestamptz_internal(ts1, tstz2);
 }
 
 /*
index 4128e3a739256cd25b96b88e32e25f1d82f1140e..ea0ada704f2074df004b1ce13ec84d79049c0f7a 100644 (file)
@@ -2156,16 +2156,34 @@ timestamp_hash_extended(PG_FUNCTION_ARGS)
  * Cross-type comparison functions for timestamp vs timestamptz
  */
 
+int32
+timestamp_cmp_timestamptz_internal(Timestamp timestampVal, TimestampTz dt2)
+{
+   TimestampTz dt1;
+   int         overflow;
+
+   dt1 = timestamp2timestamptz_opt_overflow(timestampVal, &overflow);
+   if (overflow > 0)
+   {
+       /* dt1 is larger than any finite timestamp, but less than infinity */
+       return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
+   }
+   if (overflow < 0)
+   {
+       /* dt1 is less than any finite timestamp, but more than -infinity */
+       return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+   }
+
+   return timestamptz_cmp_internal(dt1, dt2);
+}
+
 Datum
 timestamp_eq_timestamptz(PG_FUNCTION_ARGS)
 {
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
-
-   dt1 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) == 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) == 0);
 }
 
 Datum
@@ -2173,11 +2191,8 @@ timestamp_ne_timestamptz(PG_FUNCTION_ARGS)
 {
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
 
-   dt1 = timestamp2timestamptz(timestampVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) != 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) != 0);
 }
 
 Datum
@@ -2185,11 +2200,8 @@ timestamp_lt_timestamptz(PG_FUNCTION_ARGS)
 {
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
-
-   dt1 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) < 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) < 0);
 }
 
 Datum
@@ -2197,11 +2209,8 @@ timestamp_gt_timestamptz(PG_FUNCTION_ARGS)
 {
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
-
-   dt1 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) > 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) > 0);
 }
 
 Datum
@@ -2209,11 +2218,8 @@ timestamp_le_timestamptz(PG_FUNCTION_ARGS)
 {
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
-
-   dt1 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) <= 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) <= 0);
 }
 
 Datum
@@ -2221,11 +2227,8 @@ timestamp_ge_timestamptz(PG_FUNCTION_ARGS)
 {
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
-
-   dt1 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) >= 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) >= 0);
 }
 
 Datum
@@ -2233,11 +2236,8 @@ timestamp_cmp_timestamptz(PG_FUNCTION_ARGS)
 {
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(0);
    TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
-   TimestampTz dt1;
 
-   dt1 = timestamp2timestamptz(timestampVal);
-
-   PG_RETURN_INT32(timestamp_cmp_internal(dt1, dt2));
+   PG_RETURN_INT32(timestamp_cmp_timestamptz_internal(timestampVal, dt2));
 }
 
 Datum
@@ -2245,11 +2245,8 @@ timestamptz_eq_timestamp(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(1);
-   TimestampTz dt2;
-
-   dt2 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) == 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) == 0);
 }
 
 Datum
@@ -2257,11 +2254,8 @@ timestamptz_ne_timestamp(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(1);
-   TimestampTz dt2;
-
-   dt2 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) != 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) != 0);
 }
 
 Datum
@@ -2269,11 +2263,8 @@ timestamptz_lt_timestamp(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(1);
-   TimestampTz dt2;
 
-   dt2 = timestamp2timestamptz(timestampVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) < 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) > 0);
 }
 
 Datum
@@ -2281,11 +2272,8 @@ timestamptz_gt_timestamp(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(1);
-   TimestampTz dt2;
-
-   dt2 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) > 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) < 0);
 }
 
 Datum
@@ -2293,11 +2281,8 @@ timestamptz_le_timestamp(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(1);
-   TimestampTz dt2;
-
-   dt2 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) <= 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) >= 0);
 }
 
 Datum
@@ -2305,11 +2290,8 @@ timestamptz_ge_timestamp(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(1);
-   TimestampTz dt2;
 
-   dt2 = timestamp2timestamptz(timestampVal);
-
-   PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) >= 0);
+   PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) <= 0);
 }
 
 Datum
@@ -2317,11 +2299,8 @@ timestamptz_cmp_timestamp(PG_FUNCTION_ARGS)
 {
    TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
    Timestamp   timestampVal = PG_GETARG_TIMESTAMP(1);
-   TimestampTz dt2;
-
-   dt2 = timestamp2timestamptz(timestampVal);
 
-   PG_RETURN_INT32(timestamp_cmp_internal(dt1, dt2));
+   PG_RETURN_INT32(-timestamp_cmp_timestamptz_internal(timestampVal, dt1));
 }
 
 
@@ -5178,9 +5157,12 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 /*
  * Convert timestamp to timestamp with time zone.
  *
- * On overflow error is thrown if 'overflow' is NULL.  Otherwise, '*overflow'
- * is set to -1 (+1) when result value exceed lower (upper) boundary and zero
- * returned.
+ * On successful conversion, *overflow is set to zero if it's not NULL.
+ *
+ * If the timestamp is finite but out of the valid range for timestamptz, then:
+ * if overflow is NULL, we throw an out-of-range error.
+ * if overflow is not NULL, we store +1 or -1 there to indicate the sign
+ * of the overflow, and return the appropriate timestamptz infinity.
  */
 TimestampTz
 timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
@@ -5191,10 +5173,14 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
    fsec_t      fsec;
    int         tz;
 
+   if (overflow)
+       *overflow = 0;
+
    if (TIMESTAMP_NOT_FINITE(timestamp))
        return timestamp;
 
-   if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+   /* We don't expect this to fail, but check it pro forma */
+   if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) == 0)
    {
        tz = DetermineTimeZoneOffset(tm, session_timezone);
 
@@ -5207,13 +5193,16 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
        else if (overflow)
        {
            if (result < MIN_TIMESTAMP)
+           {
                *overflow = -1;
+               TIMESTAMP_NOBEGIN(result);
+           }
            else
            {
-               Assert(result >= END_TIMESTAMP);
                *overflow = 1;
+               TIMESTAMP_NOEND(result);
            }
-           return (TimestampTz) 0;
+           return result;
        }
    }
 
@@ -5225,7 +5214,7 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
 }
 
 /*
- * Single-argument version of timestamp2timestamptz_opt_overflow().
+ * Promote timestamp to timestamptz, throwing error for overflow.
  */
 static TimestampTz
 timestamp2timestamptz(Timestamp timestamp)
index 4cdb1f97cc8c42b1c6f6340035e0bdc951dd0d66..6fc491e6a6d77fa824788bf165671fc5ef9a4207 100644 (file)
@@ -72,6 +72,9 @@ extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
 extern Timestamp date2timestamp_opt_overflow(DateADT dateVal, int *overflow);
 extern TimestampTz date2timestamptz_opt_overflow(DateADT dateVal, int *overflow);
+extern int32 date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2);
+extern int32 date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2);
+
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
index 03a1de569f06fa8269aeb8eb324a4b4ce2a7e41f..16c3fd8ec977b35ac68b4655bf5b8cdfb91a9fc6 100644 (file)
@@ -99,6 +99,8 @@ extern int    timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 
 extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
                                                      int *overflow);
+extern int32 timestamp_cmp_timestamptz_internal(Timestamp timestampVal,
+                                               TimestampTz dt2);
 
 extern int isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
index 7f82dcfbfef304504f8c1233411c5bf7a619d20f..d56decd9940001f5c2be8a609a5d4f681537c64c 100644 (file)
@@ -2,6 +2,12 @@
 -- HOROLOGY
 --
 SET DateStyle = 'Postgres, MDY';
+SHOW TimeZone;  -- Many of these tests depend on the prevailing setting
+ TimeZone 
+----------
+ PST8PDT
+(1 row)
+
 --
 -- Test various input formats
 --
@@ -2076,6 +2082,72 @@ SELECT '' AS "16", f1 AS "timestamp", date(f1) AS date
 
 DROP TABLE TEMP_TIMESTAMP;
 --
+-- Comparisons between datetime types, especially overflow cases
+---
+SELECT '2202020-10-05'::date::timestamp;  -- fail
+ERROR:  date out of range for timestamp
+SELECT '2202020-10-05'::date > '2020-10-05'::timestamp as t;
+ t 
+---
+ t
+(1 row)
+
+SELECT '2020-10-05'::timestamp > '2202020-10-05'::date as f;
+ f 
+---
+ f
+(1 row)
+
+SELECT '2202020-10-05'::date::timestamptz;  -- fail
+ERROR:  date out of range for timestamp
+SELECT '2202020-10-05'::date > '2020-10-05'::timestamptz as t;
+ t 
+---
+ t
+(1 row)
+
+SELECT '2020-10-05'::timestamptz > '2202020-10-05'::date as f;
+ f 
+---
+ f
+(1 row)
+
+-- This conversion may work depending on timezone
+SELECT '4714-11-24 BC'::date::timestamptz;
+           timestamptz           
+---------------------------------
+ Mon Nov 24 00:00:00 4714 PST BC
+(1 row)
+
+SET TimeZone = 'UTC-2';
+SELECT '4714-11-24 BC'::date::timestamptz;  -- fail
+ERROR:  date out of range for timestamp
+SELECT '4714-11-24 BC'::date < '2020-10-05'::timestamptz as t;
+ t 
+---
+ t
+(1 row)
+
+SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::date as t;
+ t 
+---
+ t
+(1 row)
+
+SELECT '4714-11-24 BC'::timestamp < '2020-10-05'::timestamptz as t;
+ t 
+---
+ t
+(1 row)
+
+SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::timestamp as t;
+ t 
+---
+ t
+(1 row)
+
+RESET TimeZone;
+--
 -- Formats
 --
 SET DateStyle TO 'US,Postgres';
index fed21a53c867b3cffac0f319d707faf57db5d9c3..fa92a80d0e661ee84a4d0fa96ea121193885f9e9 100644 (file)
@@ -3,6 +3,8 @@
 --
 SET DateStyle = 'Postgres, MDY';
 
+SHOW TimeZone;  -- Many of these tests depend on the prevailing setting
+
 --
 -- Test various input formats
 --
@@ -279,6 +281,31 @@ SELECT '' AS "16", f1 AS "timestamp", date(f1) AS date
 
 DROP TABLE TEMP_TIMESTAMP;
 
+--
+-- Comparisons between datetime types, especially overflow cases
+---
+
+SELECT '2202020-10-05'::date::timestamp;  -- fail
+SELECT '2202020-10-05'::date > '2020-10-05'::timestamp as t;
+SELECT '2020-10-05'::timestamp > '2202020-10-05'::date as f;
+
+SELECT '2202020-10-05'::date::timestamptz;  -- fail
+SELECT '2202020-10-05'::date > '2020-10-05'::timestamptz as t;
+SELECT '2020-10-05'::timestamptz > '2202020-10-05'::date as f;
+
+-- This conversion may work depending on timezone
+SELECT '4714-11-24 BC'::date::timestamptz;
+SET TimeZone = 'UTC-2';
+SELECT '4714-11-24 BC'::date::timestamptz;  -- fail
+
+SELECT '4714-11-24 BC'::date < '2020-10-05'::timestamptz as t;
+SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::date as t;
+
+SELECT '4714-11-24 BC'::timestamp < '2020-10-05'::timestamptz as t;
+SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::timestamp as t;
+
+RESET TimeZone;
+
 --
 -- Formats
 --