Re: Fix overflow in DecodeInterval - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Fix overflow in DecodeInterval |
Date | |
Msg-id | 1523268.1648926504@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Fix overflow in DecodeInterval (Joseph Koshakow <koshy44@gmail.com>) |
List | pgsql-hackers |
Joseph Koshakow <koshy44@gmail.com> writes: > Ok I actually remember now, the issue is with the rounding > code in AdjustFractMicroseconds. > ... > I believe it's possible for `frac -= usec;` to result in a value greater > than 1 or less than -1 due to the lossiness of int64 to double > conversions. I think it's not, at least not for the interesting range of possible values in this code. Given that abs(frac) < 1 to start with, the abs value of usec can't exceed the value of scale, which is at most USECS_PER_DAY so it's at most 37 or so bits, which is well within the exact range for any sane implementation of double. It would take a very poor floating-point implementation to not get the right answer here. (And we're largely assuming IEEE-compliant floats these days.) Anyway, the other issue indeed turns out to be easy to fix. Attached is a v11 that deals with that. If the cfbot doesn't complain about it, I'll push this later today. regards, tom lane diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index ba0ec35ac5..462f2ed7a8 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -21,6 +21,7 @@ #include "access/htup_details.h" #include "access/xact.h" #include "catalog/pg_type.h" +#include "common/int.h" #include "common/string.h" #include "funcapi.h" #include "miscadmin.h" @@ -37,17 +38,31 @@ static int DecodeNumber(int flen, char *field, bool haveTextMonth, static int DecodeNumberField(int len, char *str, int fmask, int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits); +static int DecodeTimeCommon(char *str, int fmask, int range, + int *tmask, struct pg_itm *itm); static int DecodeTime(char *str, int fmask, int range, int *tmask, struct pg_tm *tm, fsec_t *fsec); +static int DecodeTimeForInterval(char *str, int fmask, int range, + int *tmask, struct pg_itm_in *itm_in); static const datetkn *datebsearch(const char *key, const datetkn *base, int nel); static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits, struct pg_tm *tm); static char *AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros); -static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, - int scale); -static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, - int scale); +static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum); +static bool AdjustFractMicroseconds(double frac, int64 scale, + struct pg_itm_in *itm_in); +static bool AdjustFractDays(double frac, int scale, + struct pg_itm_in *itm_in); +static bool AdjustFractYears(double frac, int scale, + struct pg_itm_in *itm_in); +static bool AdjustMicroseconds(int64 val, double fval, int64 scale, + struct pg_itm_in *itm_in); +static bool AdjustDays(int64 val, int scale, + struct pg_itm_in *itm_in); +static bool AdjustMonths(int64 val, struct pg_itm_in *itm_in); +static bool AdjustYears(int64 val, int scale, + struct pg_itm_in *itm_in); static int DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp, pg_time_t *tp); static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, @@ -425,7 +440,7 @@ GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp) * Returns a pointer to the new end of string. No NUL terminator is put * there; callers are responsible for NUL terminating str themselves. * - * Note that any sign is stripped from the input seconds values. + * Note that any sign is stripped from the input sec and fsec values. */ static char * AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros) @@ -471,7 +486,7 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros) /* * If we still have a non-zero value then precision must have not been - * enough to print the number. We punt the problem to pg_ltostr(), + * enough to print the number. We punt the problem to pg_ultostr(), * which will generate a correct answer in the minimum valid width. */ if (value) @@ -496,39 +511,163 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec) return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true); } + +/* + * Add val * multiplier to *sum. + * Returns true if successful, false on overflow. + */ +static bool +int64_multiply_add(int64 val, int64 multiplier, int64 *sum) +{ + int64 product; + + if (pg_mul_s64_overflow(val, multiplier, &product) || + pg_add_s64_overflow(*sum, product, sum)) + return false; + return true; +} + /* - * Multiply frac by scale (to produce seconds) and add to *tm & *fsec. - * We assume the input frac is less than 1 so overflow is not an issue. + * Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec. + * Returns true if successful, false if itm_in overflows. */ -static void -AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale) +static bool +AdjustFractMicroseconds(double frac, int64 scale, + struct pg_itm_in *itm_in) { - int sec; + int64 usec; + /* Fast path for common case */ if (frac == 0) - return; + return true; + + /* + * We assume the input frac has abs value less than 1, so overflow of frac + * or usec is not an issue for interesting values of scale. + */ frac *= scale; - sec = (int) frac; - tm->tm_sec += sec; - frac -= sec; - *fsec += rint(frac * 1000000); + usec = (int64) frac; + + /* Round off any fractional microsecond */ + frac -= usec; + if (frac > 0.5) + usec++; + else if (frac < -0.5) + usec--; + + return !pg_add_s64_overflow(itm_in->tm_usec, usec, &itm_in->tm_usec); } -/* As above, but initial scale produces days */ -static void -AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale) +/* + * Multiply frac by scale (to produce days). Add the integral part of the + * result to itm_in->tm_mday, the fractional part to itm_in->tm_usec. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustFractDays(double frac, int scale, + struct pg_itm_in *itm_in) { int extra_days; + /* Fast path for common case */ if (frac == 0) - return; + return true; + + /* + * We assume the input frac has abs value less than 1, so overflow of frac + * or extra_days is not an issue. + */ frac *= scale; extra_days = (int) frac; - tm->tm_mday += extra_days; + + /* ... but this could overflow, if tm_mday is already nonzero */ + if (pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday)) + return false; + + /* Handle any fractional day */ frac -= extra_days; - AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY); + return AdjustFractMicroseconds(frac, USECS_PER_DAY, itm_in); +} + +/* + * Multiply frac by scale (to produce years), then further scale up to months. + * Add the integral part of the result to itm_in->tm_mon, discarding any + * fractional part. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustFractYears(double frac, int scale, + struct pg_itm_in *itm_in) +{ + /* + * As above, we assume abs(frac) < 1, so this can't overflow for any + * interesting value of scale. + */ + int extra_months = (int) rint(frac * scale * MONTHS_PER_YEAR); + + return !pg_add_s32_overflow(itm_in->tm_mon, extra_months, &itm_in->tm_mon); +} + +/* + * Add (val + fval) * scale to itm_in->tm_usec. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustMicroseconds(int64 val, double fval, int64 scale, + struct pg_itm_in *itm_in) +{ + /* Handle the integer part */ + if (!int64_multiply_add(val, scale, &itm_in->tm_usec)) + return false; + /* Handle the float part */ + return AdjustFractMicroseconds(fval, scale, itm_in); +} + +/* + * Multiply val by scale (to produce days) and add to itm_in->tm_mday. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustDays(int64 val, int scale, struct pg_itm_in *itm_in) +{ + int days; + + if (val < INT_MIN || val > INT_MAX) + return false; + return !pg_mul_s32_overflow((int32) val, scale, &days) && + !pg_add_s32_overflow(itm_in->tm_mday, days, &itm_in->tm_mday); +} + +/* + * Add val to itm_in->tm_mon (no need for scale here, as val is always + * in months already). + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustMonths(int64 val, struct pg_itm_in *itm_in) +{ + if (val < INT_MIN || val > INT_MAX) + return false; + return !pg_add_s32_overflow(itm_in->tm_mon, (int32) val, &itm_in->tm_mon); } +/* + * Multiply val by scale (to produce years) and add to itm_in->tm_year. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustYears(int64 val, int scale, + struct pg_itm_in *itm_in) +{ + int years; + + if (val < INT_MIN || val > INT_MAX) + return false; + return !pg_mul_s32_overflow((int32) val, scale, &years) && + !pg_add_s32_overflow(itm_in->tm_year, years, &itm_in->tm_year); +} + + /* Fetch a fractional-second value with suitable error checking */ static int ParseFractionalSecond(char *cp, fsec_t *fsec) @@ -2548,79 +2687,143 @@ ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc, } -/* DecodeTime() +/* DecodeTimeCommon() * Decode time string which includes delimiters. * Return 0 if okay, a DTERR code if not. + * tmask and itm are output parameters. * - * Only check the lower limit on hours, since this same code can be - * used to represent time spans. + * This code is shared between the timestamp and interval cases. + * We return a struct pg_itm (of which only the tm_usec, tm_sec, tm_min, + * and tm_hour fields are used) and let the wrapper functions below + * convert and range-check as necessary. */ static int -DecodeTime(char *str, int fmask, int range, - int *tmask, struct pg_tm *tm, fsec_t *fsec) +DecodeTimeCommon(char *str, int fmask, int range, + int *tmask, struct pg_itm *itm) { char *cp; int dterr; + fsec_t fsec = 0; *tmask = DTK_TIME_M; errno = 0; - tm->tm_hour = strtoint(str, &cp, 10); + itm->tm_hour = strtoi64(str, &cp, 10); if (errno == ERANGE) return DTERR_FIELD_OVERFLOW; if (*cp != ':') return DTERR_BAD_FORMAT; errno = 0; - tm->tm_min = strtoint(cp + 1, &cp, 10); + itm->tm_min = strtoint(cp + 1, &cp, 10); if (errno == ERANGE) return DTERR_FIELD_OVERFLOW; if (*cp == '\0') { - tm->tm_sec = 0; - *fsec = 0; + itm->tm_sec = 0; /* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */ if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND))) { - tm->tm_sec = tm->tm_min; - tm->tm_min = tm->tm_hour; - tm->tm_hour = 0; + if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN) + return DTERR_FIELD_OVERFLOW; + itm->tm_sec = itm->tm_min; + itm->tm_min = (int) itm->tm_hour; + itm->tm_hour = 0; } } else if (*cp == '.') { /* always assume mm:ss.sss is MINUTE TO SECOND */ - dterr = ParseFractionalSecond(cp, fsec); + dterr = ParseFractionalSecond(cp, &fsec); if (dterr) return dterr; - tm->tm_sec = tm->tm_min; - tm->tm_min = tm->tm_hour; - tm->tm_hour = 0; + if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN) + return DTERR_FIELD_OVERFLOW; + itm->tm_sec = itm->tm_min; + itm->tm_min = (int) itm->tm_hour; + itm->tm_hour = 0; } else if (*cp == ':') { errno = 0; - tm->tm_sec = strtoint(cp + 1, &cp, 10); + itm->tm_sec = strtoint(cp + 1, &cp, 10); if (errno == ERANGE) return DTERR_FIELD_OVERFLOW; - if (*cp == '\0') - *fsec = 0; - else if (*cp == '.') + if (*cp == '.') { - dterr = ParseFractionalSecond(cp, fsec); + dterr = ParseFractionalSecond(cp, &fsec); if (dterr) return dterr; } - else + else if (*cp != '\0') return DTERR_BAD_FORMAT; } else return DTERR_BAD_FORMAT; - /* do a sanity check */ - if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 || - tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE || - *fsec < INT64CONST(0) || - *fsec > USECS_PER_SEC) + /* do a sanity check; but caller must check the range of tm_hour */ + if (itm->tm_hour < 0 || + itm->tm_min < 0 || itm->tm_min > MINS_PER_HOUR - 1 || + itm->tm_sec < 0 || itm->tm_sec > SECS_PER_MINUTE || + fsec < 0 || fsec > USECS_PER_SEC) + return DTERR_FIELD_OVERFLOW; + + itm->tm_usec = (int) fsec; + + return 0; +} + +/* DecodeTime() + * Decode time string which includes delimiters. + * Return 0 if okay, a DTERR code if not. + * + * This version is used for timestamps. The results are returned into + * the tm_hour/tm_min/tm_sec fields of *tm, and microseconds into *fsec. + */ +static int +DecodeTime(char *str, int fmask, int range, + int *tmask, struct pg_tm *tm, fsec_t *fsec) +{ + struct pg_itm itm; + int dterr; + + dterr = DecodeTimeCommon(str, fmask, range, + tmask, &itm); + if (dterr) + return dterr; + + if (itm.tm_hour > INT_MAX) + return DTERR_FIELD_OVERFLOW; + tm->tm_hour = (int) itm.tm_hour; + tm->tm_min = itm.tm_min; + tm->tm_sec = itm.tm_sec; + *fsec = itm.tm_usec; + + return 0; +} + +/* DecodeTimeForInterval() + * Decode time string which includes delimiters. + * Return 0 if okay, a DTERR code if not. + * + * This version is used for intervals. The results are returned into + * itm_in->tm_usec. + */ +static int +DecodeTimeForInterval(char *str, int fmask, int range, + int *tmask, struct pg_itm_in *itm_in) +{ + struct pg_itm itm; + int dterr; + + dterr = DecodeTimeCommon(str, fmask, range, + tmask, &itm); + if (dterr) + return dterr; + + itm_in->tm_usec = itm.tm_usec; + if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) || + !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) || + !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec)) return DTERR_FIELD_OVERFLOW; return 0; @@ -3064,27 +3267,24 @@ DecodeSpecial(int field, char *lowtoken, int *val) } -/* ClearPgTm +/* ClearPgItmIn * - * Zero out a pg_tm and associated fsec_t + * Zero out a pg_itm_in */ static inline void -ClearPgTm(struct pg_tm *tm, fsec_t *fsec) +ClearPgItmIn(struct pg_itm_in *itm_in) { - tm->tm_year = 0; - tm->tm_mon = 0; - tm->tm_mday = 0; - tm->tm_hour = 0; - tm->tm_min = 0; - tm->tm_sec = 0; - *fsec = 0; + itm_in->tm_usec = 0; + itm_in->tm_mday = 0; + itm_in->tm_mon = 0; + itm_in->tm_year = 0; } /* DecodeInterval() * Interpret previously parsed fields for general time interval. * Returns 0 if successful, DTERR code if bogus input detected. - * dtype, tm, fsec are output parameters. + * dtype and itm_in are output parameters. * * Allow "date" field DTK_DATE since this could be just * an unsigned floating point number. - thomas 1997-11-16 @@ -3094,21 +3294,53 @@ ClearPgTm(struct pg_tm *tm, fsec_t *fsec) */ int DecodeInterval(char **field, int *ftype, int nf, int range, - int *dtype, struct pg_tm *tm, fsec_t *fsec) + int *dtype, struct pg_itm_in *itm_in) { + bool force_negative = false; bool is_before = false; char *cp; int fmask = 0, tmask, - type; + type, + uval; int i; int dterr; - int val; + int64 val; double fval; *dtype = DTK_DELTA; type = IGNORE_DTF; - ClearPgTm(tm, fsec); + ClearPgItmIn(itm_in); + + /*---------- + * The SQL standard defines the interval literal + * '-1 1:00:00' + * to mean "negative 1 days and negative 1 hours", while Postgres + * traditionally treats this as meaning "negative 1 days and positive + * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign + * to all fields if there are no other explicit signs. + * + * We leave the signs alone if there are additional explicit signs. + * This protects us against misinterpreting postgres-style dump output, + * since the postgres-style output code has always put an explicit sign on + * all fields following a negative field. But note that SQL-spec output + * is ambiguous and can be misinterpreted on load! (So it's best practice + * to dump in postgres style, not SQL style.) + *---------- + */ + if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-') + { + force_negative = true; + /* Check for additional explicit signs */ + for (i = 1; i < nf; i++) + { + if (*field[i] == '-' || *field[i] == '+') + { + force_negative = false; + break; + } + } + } /* read through list backwards to pick up units before values */ for (i = nf - 1; i >= 0; i--) @@ -3116,10 +3348,13 @@ DecodeInterval(char **field, int *ftype, int nf, int range, switch (ftype[i]) { case DTK_TIME: - dterr = DecodeTime(field[i], fmask, range, - &tmask, tm, fsec); + dterr = DecodeTimeForInterval(field[i], fmask, range, + &tmask, itm_in); if (dterr) return dterr; + if (force_negative && + itm_in->tm_usec > 0) + itm_in->tm_usec = -itm_in->tm_usec; type = DTK_DAY; break; @@ -3137,18 +3372,21 @@ DecodeInterval(char **field, int *ftype, int nf, int range, * like DTK_TIME case above, plus handling the sign. */ if (strchr(field[i] + 1, ':') != NULL && - DecodeTime(field[i] + 1, fmask, range, - &tmask, tm, fsec) == 0) + DecodeTimeForInterval(field[i] + 1, fmask, range, + &tmask, itm_in) == 0) { if (*field[i] == '-') { - /* flip the sign on all fields */ - tm->tm_hour = -tm->tm_hour; - tm->tm_min = -tm->tm_min; - tm->tm_sec = -tm->tm_sec; - *fsec = -(*fsec); + /* flip the sign on time field */ + if (itm_in->tm_usec == PG_INT64_MIN) + return DTERR_FIELD_OVERFLOW; + itm_in->tm_usec = -itm_in->tm_usec; } + if (force_negative && + itm_in->tm_usec > 0) + itm_in->tm_usec = -itm_in->tm_usec; + /* * Set the next type to be a day, if units are not * specified. This handles the case of '1 +02:03' since we @@ -3204,7 +3442,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range, } errno = 0; - val = strtoint(field[i], &cp, 10); + val = strtoi64(field[i], &cp, 10); if (errno == ERANGE) return DTERR_FIELD_OVERFLOW; @@ -3221,10 +3459,10 @@ DecodeInterval(char **field, int *ftype, int nf, int range, type = DTK_MONTH; if (*field[i] == '-') val2 = -val2; - if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX || - ((double) val * MONTHS_PER_YEAR + val2) < INT_MIN) + if (pg_mul_s64_overflow(val, MONTHS_PER_YEAR, &val)) + return DTERR_FIELD_OVERFLOW; + if (pg_add_s64_overflow(val, val2, &val)) return DTERR_FIELD_OVERFLOW; - val = val * MONTHS_PER_YEAR + val2; fval = 0; } else if (*cp == '.') @@ -3244,24 +3482,32 @@ DecodeInterval(char **field, int *ftype, int nf, int range, tmask = 0; /* DTK_M(type); */ + if (force_negative) + { + /* val and fval should be of same sign, but test anyway */ + if (val > 0) + val = -val; + if (fval > 0) + fval = -fval; + } + switch (type) { case DTK_MICROSEC: - *fsec += rint(val + fval); + if (!AdjustMicroseconds(val, fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(MICROSECOND); break; case DTK_MILLISEC: - /* avoid overflowing the fsec field */ - tm->tm_sec += val / 1000; - val -= (val / 1000) * 1000; - *fsec += rint((val + fval) * 1000); + if (!AdjustMicroseconds(val, fval, 1000, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(MILLISECOND); break; case DTK_SECOND: - tm->tm_sec += val; - *fsec += rint(fval * 1000000); + if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in)) + return DTERR_FIELD_OVERFLOW; /* * If any subseconds were specified, consider this @@ -3274,57 +3520,64 @@ DecodeInterval(char **field, int *ftype, int nf, int range, break; case DTK_MINUTE: - tm->tm_min += val; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE); + if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(MINUTE); break; case DTK_HOUR: - tm->tm_hour += val; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR); + if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(HOUR); type = DTK_DAY; /* set for next field */ break; case DTK_DAY: - tm->tm_mday += val; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY); + if (!AdjustDays(val, 1, itm_in) || + !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(DAY); break; case DTK_WEEK: - tm->tm_mday += val * 7; - AdjustFractDays(fval, tm, fsec, 7); + if (!AdjustDays(val, 7, itm_in) || + !AdjustFractDays(fval, 7, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(WEEK); break; case DTK_MONTH: - tm->tm_mon += val; - AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH); + if (!AdjustMonths(val, itm_in) || + !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(MONTH); break; case DTK_YEAR: - tm->tm_year += val; - tm->tm_mon += rint(fval * MONTHS_PER_YEAR); + if (!AdjustYears(val, 1, itm_in) || + !AdjustFractYears(fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(YEAR); break; case DTK_DECADE: - tm->tm_year += val * 10; - tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10); + if (!AdjustYears(val, 10, itm_in) || + !AdjustFractYears(fval, 10, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(DECADE); break; case DTK_CENTURY: - tm->tm_year += val * 100; - tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100); + if (!AdjustYears(val, 100, itm_in) || + !AdjustFractYears(fval, 100, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(CENTURY); break; case DTK_MILLENNIUM: - tm->tm_year += val * 1000; - tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000); + if (!AdjustYears(val, 1000, itm_in) || + !AdjustFractYears(fval, 1000, itm_in)) + return DTERR_FIELD_OVERFLOW; tmask = DTK_M(MILLENNIUM); break; @@ -3335,7 +3588,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range, case DTK_STRING: case DTK_SPECIAL: - type = DecodeUnits(i, field[i], &val); + type = DecodeUnits(i, field[i], &uval); if (type == IGNORE_DTF) continue; @@ -3343,17 +3596,17 @@ DecodeInterval(char **field, int *ftype, int nf, int range, switch (type) { case UNITS: - type = val; + type = uval; break; case AGO: is_before = true; - type = val; + type = uval; break; case RESERV: tmask = (DTK_DATE_M | DTK_TIME_M); - *dtype = val; + *dtype = uval; break; default: @@ -3374,79 +3627,19 @@ DecodeInterval(char **field, int *ftype, int nf, int range, if (fmask == 0) return DTERR_BAD_FORMAT; - /* ensure fractional seconds are fractional */ - if (*fsec != 0) - { - int sec; - - sec = *fsec / USECS_PER_SEC; - *fsec -= sec * USECS_PER_SEC; - tm->tm_sec += sec; - } - - /*---------- - * The SQL standard defines the interval literal - * '-1 1:00:00' - * to mean "negative 1 days and negative 1 hours", while Postgres - * traditionally treats this as meaning "negative 1 days and positive - * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign - * to all fields if there are no other explicit signs. - * - * We leave the signs alone if there are additional explicit signs. - * This protects us against misinterpreting postgres-style dump output, - * since the postgres-style output code has always put an explicit sign on - * all fields following a negative field. But note that SQL-spec output - * is ambiguous and can be misinterpreted on load! (So it's best practice - * to dump in postgres style, not SQL style.) - *---------- - */ - if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-') - { - /* Check for additional explicit signs */ - bool more_signs = false; - - for (i = 1; i < nf; i++) - { - if (*field[i] == '-' || *field[i] == '+') - { - more_signs = true; - break; - } - } - - if (!more_signs) - { - /* - * Rather than re-determining which field was field[0], just force - * 'em all negative. - */ - if (*fsec > 0) - *fsec = -(*fsec); - if (tm->tm_sec > 0) - tm->tm_sec = -tm->tm_sec; - if (tm->tm_min > 0) - tm->tm_min = -tm->tm_min; - if (tm->tm_hour > 0) - tm->tm_hour = -tm->tm_hour; - if (tm->tm_mday > 0) - tm->tm_mday = -tm->tm_mday; - if (tm->tm_mon > 0) - tm->tm_mon = -tm->tm_mon; - if (tm->tm_year > 0) - tm->tm_year = -tm->tm_year; - } - } - /* finally, AGO negates everything */ if (is_before) { - *fsec = -(*fsec); - tm->tm_sec = -tm->tm_sec; - tm->tm_min = -tm->tm_min; - tm->tm_hour = -tm->tm_hour; - tm->tm_mday = -tm->tm_mday; - tm->tm_mon = -tm->tm_mon; - tm->tm_year = -tm->tm_year; + if (itm_in->tm_usec == PG_INT64_MIN || + itm_in->tm_mday == INT_MIN || + itm_in->tm_mon == INT_MIN || + itm_in->tm_year == INT_MIN) + return DTERR_FIELD_OVERFLOW; + + itm_in->tm_usec = -itm_in->tm_usec; + itm_in->tm_mday = -itm_in->tm_mday; + itm_in->tm_mon = -itm_in->tm_mon; + itm_in->tm_year = -itm_in->tm_year; } return 0; @@ -3460,26 +3653,35 @@ DecodeInterval(char **field, int *ftype, int nf, int range, * Returns 0 or DTERR code. */ static int -ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart) +ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart) { - double val; + int sign = 1; - if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.')) - return DTERR_BAD_FORMAT; + *ipart = 0; + *fpart = 0.0; + + /* Parse sign if there is any */ + if (*str == '-') + { + sign = -1; + str++; + } + + *endptr = str; errno = 0; - val = strtod(str, endptr); - /* did we not see anything that looks like a double? */ + + /* Parse int64 part if there is any */ + if (isdigit((unsigned char) **endptr)) + *ipart = strtoi64(*endptr, endptr, 10) * sign; + + /* Parse fractional part if there is any */ + if (**endptr == '.') + *fpart = strtod(*endptr, endptr) * sign; + + /* did we not see anything that looks like a number? */ if (*endptr == str || errno != 0) return DTERR_BAD_FORMAT; - /* watch out for overflow */ - if (val < INT_MIN || val > INT_MAX) - return DTERR_FIELD_OVERFLOW; - /* be very sure we truncate towards zero (cf dtrunc()) */ - if (val >= 0) - *ipart = (int) floor(val); - else - *ipart = (int) -floor(-val); - *fpart = val - *ipart; + return 0; } @@ -3508,7 +3710,7 @@ ISO8601IntegerWidth(char *fieldstart) * Returns 0 if successful, DTERR code if bogus input detected. * Note: error code should be DTERR_BAD_FORMAT if input doesn't look like * ISO8601, otherwise this could cause unexpected error messages. - * dtype, tm, fsec are output parameters. + * dtype and itm_in are output parameters. * * A couple exceptions from the spec: * - a week field ('W') may coexist with other units @@ -3516,13 +3718,13 @@ ISO8601IntegerWidth(char *fieldstart) */ int DecodeISO8601Interval(char *str, - int *dtype, struct pg_tm *tm, fsec_t *fsec) + int *dtype, struct pg_itm_in *itm_in) { bool datepart = true; bool havefield = false; *dtype = DTK_DELTA; - ClearPgTm(tm, fsec); + ClearPgItmIn(itm_in); if (strlen(str) < 2 || str[0] != 'P') return DTERR_BAD_FORMAT; @@ -3531,7 +3733,7 @@ DecodeISO8601Interval(char *str, while (*str) { char *fieldstart; - int val; + int64 val; double fval; char unit; int dterr; @@ -3560,29 +3762,34 @@ DecodeISO8601Interval(char *str, switch (unit) /* before T: Y M W D */ { case 'Y': - tm->tm_year += val; - tm->tm_mon += rint(fval * MONTHS_PER_YEAR); + if (!AdjustYears(val, 1, itm_in) || + !AdjustFractYears(fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; break; case 'M': - tm->tm_mon += val; - AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH); + if (!AdjustMonths(val, itm_in) || + !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in)) + return DTERR_FIELD_OVERFLOW; break; case 'W': - tm->tm_mday += val * 7; - AdjustFractDays(fval, tm, fsec, 7); + if (!AdjustDays(val, 7, itm_in) || + !AdjustFractDays(fval, 7, itm_in)) + return DTERR_FIELD_OVERFLOW; break; case 'D': - tm->tm_mday += val; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY); + if (!AdjustDays(val, 1, itm_in) || + !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in)) + return DTERR_FIELD_OVERFLOW; break; case 'T': /* ISO 8601 4.4.3.3 Alternative Format / Basic */ case '\0': if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield) { - tm->tm_year += val / 10000; - tm->tm_mon += (val / 100) % 100; - tm->tm_mday += val % 100; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY); + if (!AdjustYears(val / 10000, 1, itm_in) || + !AdjustMonths((val / 100) % 100, itm_in) || + !AdjustDays(val % 100, 1, itm_in) || + !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in)) + return DTERR_FIELD_OVERFLOW; if (unit == '\0') return 0; datepart = false; @@ -3596,8 +3803,9 @@ DecodeISO8601Interval(char *str, if (havefield) return DTERR_BAD_FORMAT; - tm->tm_year += val; - tm->tm_mon += rint(fval * MONTHS_PER_YEAR); + if (!AdjustYears(val, 1, itm_in) || + !AdjustFractYears(fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; if (unit == '\0') return 0; if (unit == 'T') @@ -3610,8 +3818,9 @@ DecodeISO8601Interval(char *str, dterr = ParseISO8601Number(str, &str, &val, &fval); if (dterr) return dterr; - tm->tm_mon += val; - AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH); + if (!AdjustMonths(val, itm_in) || + !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in)) + return DTERR_FIELD_OVERFLOW; if (*str == '\0') return 0; if (*str == 'T') @@ -3627,8 +3836,9 @@ DecodeISO8601Interval(char *str, dterr = ParseISO8601Number(str, &str, &val, &fval); if (dterr) return dterr; - tm->tm_mday += val; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY); + if (!AdjustDays(val, 1, itm_in) || + !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in)) + return DTERR_FIELD_OVERFLOW; if (*str == '\0') return 0; if (*str == 'T') @@ -3648,24 +3858,25 @@ DecodeISO8601Interval(char *str, switch (unit) /* after T: H M S */ { case 'H': - tm->tm_hour += val; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR); + if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in)) + return DTERR_FIELD_OVERFLOW; break; case 'M': - tm->tm_min += val; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE); + if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in)) + return DTERR_FIELD_OVERFLOW; break; case 'S': - tm->tm_sec += val; - AdjustFractSeconds(fval, tm, fsec, 1); + if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in)) + return DTERR_FIELD_OVERFLOW; break; case '\0': /* ISO 8601 4.4.3.3 Alternative Format */ if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield) { - tm->tm_hour += val / 10000; - tm->tm_min += (val / 100) % 100; - tm->tm_sec += val % 100; - AdjustFractSeconds(fval, tm, fsec, 1); + if (!AdjustMicroseconds(val / 10000, 0, USECS_PER_HOUR, itm_in) || + !AdjustMicroseconds((val / 100) % 100, 0, USECS_PER_MINUTE, itm_in) || + !AdjustMicroseconds(val % 100, 0, USECS_PER_SEC, itm_in) || + !AdjustFractMicroseconds(fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; return 0; } /* Else fall through to extended alternative format */ @@ -3675,16 +3886,16 @@ DecodeISO8601Interval(char *str, if (havefield) return DTERR_BAD_FORMAT; - tm->tm_hour += val; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR); + if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in)) + return DTERR_FIELD_OVERFLOW; if (unit == '\0') return 0; dterr = ParseISO8601Number(str, &str, &val, &fval); if (dterr) return dterr; - tm->tm_min += val; - AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE); + if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in)) + return DTERR_FIELD_OVERFLOW; if (*str == '\0') return 0; if (*str != ':') @@ -3694,8 +3905,8 @@ DecodeISO8601Interval(char *str, dterr = ParseISO8601Number(str, &str, &val, &fval); if (dterr) return dterr; - tm->tm_sec += val; - AdjustFractSeconds(fval, tm, fsec, 1); + if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in)) + return DTERR_FIELD_OVERFLOW; if (*str == '\0') return 0; return DTERR_BAD_FORMAT; @@ -4166,25 +4377,25 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char /* Append an ISO-8601-style interval field, but only if value isn't zero */ static char * -AddISO8601IntPart(char *cp, int value, char units) +AddISO8601IntPart(char *cp, int64 value, char units) { if (value == 0) return cp; - sprintf(cp, "%d%c", value, units); + sprintf(cp, "%lld%c", (long long) value, units); return cp + strlen(cp); } /* Append a postgres-style interval field, but only if value isn't zero */ static char * -AddPostgresIntPart(char *cp, int value, const char *units, +AddPostgresIntPart(char *cp, int64 value, const char *units, bool *is_zero, bool *is_before) { if (value == 0) return cp; - sprintf(cp, "%s%s%d %s%s", + sprintf(cp, "%s%s%lld %s%s", (!*is_zero) ? " " : "", (*is_before && value > 0) ? "+" : "", - value, + (long long) value, units, (value != 1) ? "s" : ""); @@ -4199,7 +4410,7 @@ AddPostgresIntPart(char *cp, int value, const char *units, /* Append a verbose-style interval field, but only if value isn't zero */ static char * -AddVerboseIntPart(char *cp, int value, const char *units, +AddVerboseIntPart(char *cp, int64 value, const char *units, bool *is_zero, bool *is_before) { if (value == 0) @@ -4208,11 +4419,11 @@ AddVerboseIntPart(char *cp, int value, const char *units, if (*is_zero) { *is_before = (value < 0); - value = abs(value); + value = Abs(value); } else if (*is_before) value = -value; - sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s"); + sprintf(cp, " %lld %s%s", (long long) value, units, (value == 1) ? "" : "s"); *is_zero = false; return cp + strlen(cp); } @@ -4238,15 +4449,16 @@ AddVerboseIntPart(char *cp, int value, const char *units, * "day-time literal"s (that look like ('4 5:6:7') */ void -EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str) +EncodeInterval(struct pg_itm *itm, int style, char *str) { char *cp = str; - int year = tm->tm_year; - int mon = tm->tm_mon; - int mday = tm->tm_mday; - int hour = tm->tm_hour; - int min = tm->tm_min; - int sec = tm->tm_sec; + int year = itm->tm_year; + int mon = itm->tm_mon; + int64 mday = itm->tm_mday; /* tm_mday could be INT_MIN */ + int64 hour = itm->tm_hour; + int min = itm->tm_min; + int sec = itm->tm_sec; + int fsec = itm->tm_usec; bool is_before = false; bool is_zero = true; @@ -4306,10 +4518,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str) char sec_sign = (hour < 0 || min < 0 || sec < 0 || fsec < 0) ? '-' : '+'; - sprintf(cp, "%c%d-%d %c%d %c%d:%02d:", + sprintf(cp, "%c%d-%d %c%lld %c%lld:%02d:", year_sign, abs(year), abs(mon), - day_sign, abs(mday), - sec_sign, abs(hour), abs(min)); + day_sign, (long long) Abs(mday), + sec_sign, (long long) Abs(hour), abs(min)); cp += strlen(cp); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); *cp = '\0'; @@ -4320,14 +4532,15 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str) } else if (has_day) { - sprintf(cp, "%d %d:%02d:", mday, hour, min); + sprintf(cp, "%lld %lld:%02d:", + (long long) mday, (long long) hour, min); cp += strlen(cp); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); *cp = '\0'; } else { - sprintf(cp, "%d:%02d:", hour, min); + sprintf(cp, "%lld:%02d:", (long long) hour, min); cp += strlen(cp); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); *cp = '\0'; @@ -4377,10 +4590,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str) { bool minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0); - sprintf(cp, "%s%s%02d:%02d:", + sprintf(cp, "%s%s%02lld:%02d:", is_zero ? "" : " ", (minus ? "-" : (is_before ? "+" : "")), - abs(hour), abs(min)); + (long long) Abs(hour), abs(min)); cp += strlen(cp); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); *cp = '\0'; @@ -4668,7 +4881,7 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS) int gmtoffset; bool is_dst; unsigned char *p; - struct pg_tm tm; + struct pg_itm_in itm_in; Interval *resInterval; /* stuff done only on the first call of the function */ @@ -4761,11 +4974,11 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS) values[0] = CStringGetTextDatum(buffer); - /* Convert offset (in seconds) to an interval */ - MemSet(&tm, 0, sizeof(struct pg_tm)); - tm.tm_sec = gmtoffset; + /* Convert offset (in seconds) to an interval; can't overflow */ + MemSet(&itm_in, 0, sizeof(struct pg_itm_in)); + itm_in.tm_usec = (int64) gmtoffset * USECS_PER_SEC; resInterval = (Interval *) palloc(sizeof(Interval)); - tm2interval(&tm, 0, resInterval); + (void) itmin2interval(&itm_in, resInterval); values[1] = IntervalPGetDatum(resInterval); values[2] = BoolGetDatum(is_dst); @@ -4795,7 +5008,7 @@ pg_timezone_names(PG_FUNCTION_ARGS) fsec_t fsec; const char *tzn; Interval *resInterval; - struct pg_tm itm; + struct pg_itm_in itm_in; SetSingleFuncCall(fcinfo, 0); @@ -4831,10 +5044,11 @@ pg_timezone_names(PG_FUNCTION_ARGS) values[0] = CStringGetTextDatum(pg_get_timezone_name(tz)); values[1] = CStringGetTextDatum(tzn ? tzn : ""); - MemSet(&itm, 0, sizeof(struct pg_tm)); - itm.tm_sec = -tzoff; + /* Convert tzoff to an interval; can't overflow */ + MemSet(&itm_in, 0, sizeof(struct pg_itm_in)); + itm_in.tm_usec = (int64) -tzoff * USECS_PER_SEC; resInterval = (Interval *) palloc(sizeof(Interval)); - tm2interval(&itm, 0, resInterval); + (void) itmin2interval(&itm_in, resInterval); values[2] = IntervalPGetDatum(resInterval); values[3] = BoolGetDatum(tm.tm_isdst > 0); diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index ac74333be5..843b07d7d2 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -491,11 +491,28 @@ typedef struct /* ---------- * Datetime to char conversion + * + * To support intervals as well as timestamps, we use a custom "tm" struct + * that is almost like struct pg_tm, but has a 64-bit tm_hour field. + * We omit the tm_isdst and tm_zone fields, which are not used here. * ---------- */ +struct fmt_tm +{ + int tm_sec; + int tm_min; + int64 tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + long int tm_gmtoff; +}; + typedef struct TmToChar { - struct pg_tm tm; /* classic 'tm' struct */ + struct fmt_tm tm; /* almost the classic 'tm' struct */ fsec_t fsec; /* fractional seconds */ const char *tzn; /* timezone */ } TmToChar; @@ -504,12 +521,25 @@ typedef struct TmToChar #define tmtcTzn(_X) ((_X)->tzn) #define tmtcFsec(_X) ((_X)->fsec) +/* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */ +#define COPY_tm(_DST, _SRC) \ +do { \ + (_DST)->tm_sec = (_SRC)->tm_sec; \ + (_DST)->tm_min = (_SRC)->tm_min; \ + (_DST)->tm_hour = (_SRC)->tm_hour; \ + (_DST)->tm_mday = (_SRC)->tm_mday; \ + (_DST)->tm_mon = (_SRC)->tm_mon; \ + (_DST)->tm_year = (_SRC)->tm_year; \ + (_DST)->tm_wday = (_SRC)->tm_wday; \ + (_DST)->tm_yday = (_SRC)->tm_yday; \ + (_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \ +} while(0) + +/* Caution: this is used to zero both pg_tm and fmt_tm structs */ #define ZERO_tm(_X) \ do { \ - (_X)->tm_sec = (_X)->tm_year = (_X)->tm_min = (_X)->tm_wday = \ - (_X)->tm_hour = (_X)->tm_yday = (_X)->tm_isdst = 0; \ - (_X)->tm_mday = (_X)->tm_mon = 1; \ - (_X)->tm_zone = NULL; \ + memset(_X, 0, sizeof(*(_X))); \ + (_X)->tm_mday = (_X)->tm_mon = 1; \ } while(0) #define ZERO_tmtc(_X) \ @@ -2649,7 +2679,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col { FormatNode *n; char *s; - struct pg_tm *tm = &in->tm; + struct fmt_tm *tm = &in->tm; int i; /* cache localized days and months */ @@ -2698,16 +2728,17 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col * display time as shown on a 12-hour clock, even for * intervals */ - sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3, - tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? HOURS_PER_DAY / 2 : - tm->tm_hour % (HOURS_PER_DAY / 2)); + sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3, + tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? + (long long) (HOURS_PER_DAY / 2) : + (long long) (tm->tm_hour % (HOURS_PER_DAY / 2))); if (S_THth(n->suffix)) str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s); break; case DCH_HH24: - sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3, - tm->tm_hour); + sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3, + (long long) tm->tm_hour); if (S_THth(n->suffix)) str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s); @@ -2755,9 +2786,10 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col break; #undef DCH_to_char_fsec case DCH_SSSS: - sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR + - tm->tm_min * SECS_PER_MINUTE + - tm->tm_sec); + sprintf(s, "%lld", + (long long) (tm->tm_hour * SECS_PER_HOUR + + tm->tm_min * SECS_PER_MINUTE + + tm->tm_sec)); if (S_THth(n->suffix)) str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s); @@ -4088,7 +4120,8 @@ timestamp_to_char(PG_FUNCTION_ARGS) text *fmt = PG_GETARG_TEXT_PP(1), *res; TmToChar tmtc; - struct pg_tm *tm; + struct pg_tm tt; + struct fmt_tm *tm; int thisdate; if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt)) @@ -4097,10 +4130,11 @@ timestamp_to_char(PG_FUNCTION_ARGS) ZERO_tmtc(&tmtc); tm = tmtcTm(&tmtc); - if (timestamp2tm(dt, NULL, tm, &tmtcFsec(&tmtc), NULL, NULL) != 0) + if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + COPY_tm(tm, &tt); thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday); tm->tm_wday = (thisdate + 1) % 7; @@ -4120,7 +4154,8 @@ timestamptz_to_char(PG_FUNCTION_ARGS) *res; TmToChar tmtc; int tz; - struct pg_tm *tm; + struct pg_tm tt; + struct fmt_tm *tm; int thisdate; if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt)) @@ -4129,10 +4164,11 @@ timestamptz_to_char(PG_FUNCTION_ARGS) ZERO_tmtc(&tmtc); tm = tmtcTm(&tmtc); - if (timestamp2tm(dt, &tz, tm, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0) + if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + COPY_tm(tm, &tt); thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday); tm->tm_wday = (thisdate + 1) % 7; @@ -4156,7 +4192,9 @@ interval_to_char(PG_FUNCTION_ARGS) text *fmt = PG_GETARG_TEXT_PP(1), *res; TmToChar tmtc; - struct pg_tm *tm; + struct fmt_tm *tm; + struct pg_itm tt, + *itm = &tt; if (VARSIZE_ANY_EXHDR(fmt) <= 0) PG_RETURN_NULL(); @@ -4164,8 +4202,14 @@ interval_to_char(PG_FUNCTION_ARGS) ZERO_tmtc(&tmtc); tm = tmtcTm(&tmtc); - if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0) - PG_RETURN_NULL(); + interval2itm(*it, itm); + tmtc.fsec = itm->tm_usec; + tm->tm_sec = itm->tm_sec; + tm->tm_min = itm->tm_min; + tm->tm_hour = itm->tm_hour; + tm->tm_mday = itm->tm_mday; + tm->tm_mon = itm->tm_mon; + tm->tm_year = itm->tm_year; /* wday is meaningless, yday approximates the total span in days */ tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday; diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 2ba8d41284..f9489144c7 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -889,9 +889,8 @@ interval_in(PG_FUNCTION_ARGS) #endif int32 typmod = PG_GETARG_INT32(2); Interval *result; - fsec_t fsec; - struct pg_tm tt, - *tm = &tt; + struct pg_itm_in tt, + *itm_in = &tt; int dtype; int nf; int range; @@ -900,13 +899,10 @@ interval_in(PG_FUNCTION_ARGS) int ftype[MAXDATEFIELDS]; char workbuf[256]; - tm->tm_year = 0; - tm->tm_mon = 0; - tm->tm_mday = 0; - tm->tm_hour = 0; - tm->tm_min = 0; - tm->tm_sec = 0; - fsec = 0; + itm_in->tm_year = 0; + itm_in->tm_mon = 0; + itm_in->tm_mday = 0; + itm_in->tm_usec = 0; if (typmod >= 0) range = INTERVAL_RANGE(typmod); @@ -917,12 +913,12 @@ interval_in(PG_FUNCTION_ARGS) ftype, MAXDATEFIELDS, &nf); if (dterr == 0) dterr = DecodeInterval(field, ftype, nf, range, - &dtype, tm, &fsec); + &dtype, itm_in); /* if those functions think it's a bad format, try ISO8601 style */ if (dterr == DTERR_BAD_FORMAT) dterr = DecodeISO8601Interval(str, - &dtype, tm, &fsec); + &dtype, itm_in); if (dterr != 0) { @@ -936,7 +932,7 @@ interval_in(PG_FUNCTION_ARGS) switch (dtype) { case DTK_DELTA: - if (tm2interval(tm, fsec, result) != 0) + if (itmin2interval(itm_in, result) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))); @@ -960,15 +956,12 @@ interval_out(PG_FUNCTION_ARGS) { Interval *span = PG_GETARG_INTERVAL_P(0); char *result; - struct pg_tm tt, - *tm = &tt; - fsec_t fsec; + struct pg_itm tt, + *itm = &tt; char buf[MAXDATELEN + 1]; - if (interval2tm(*span, tm, &fsec) != 0) - elog(ERROR, "could not convert interval to tm"); - - EncodeInterval(tm, fsec, IntervalStyle, buf); + interval2itm(*span, itm); + EncodeInterval(itm, IntervalStyle, buf); result = pstrdup(buf); PG_RETURN_CSTRING(result); @@ -1960,50 +1953,77 @@ tm2timestamp(struct pg_tm *tm, fsec_t fsec, int *tzp, Timestamp *result) } -/* interval2tm() - * Convert an interval data type to a tm structure. +/* interval2itm() + * Convert an Interval to a pg_itm structure. + * Note: overflow is not possible, because the pg_itm fields are + * wide enough for all possible conversion results. */ -int -interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec) +void +interval2itm(Interval span, struct pg_itm *itm) { TimeOffset time; TimeOffset tfrac; - tm->tm_year = span.month / MONTHS_PER_YEAR; - tm->tm_mon = span.month % MONTHS_PER_YEAR; - tm->tm_mday = span.day; + itm->tm_year = span.month / MONTHS_PER_YEAR; + itm->tm_mon = span.month % MONTHS_PER_YEAR; + itm->tm_mday = span.day; time = span.time; 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"))); + itm->tm_hour = tfrac; tfrac = time / USECS_PER_MINUTE; time -= tfrac * USECS_PER_MINUTE; - tm->tm_min = tfrac; + itm->tm_min = (int) tfrac; tfrac = time / USECS_PER_SEC; - *fsec = time - (tfrac * USECS_PER_SEC); - tm->tm_sec = tfrac; + time -= tfrac * USECS_PER_SEC; + itm->tm_sec = (int) tfrac; + itm->tm_usec = (int) time; +} +/* itm2interval() + * Convert a pg_itm structure to an Interval. + * Returns 0 if OK, -1 on overflow. + */ +int +itm2interval(struct pg_itm *itm, Interval *span) +{ + int64 total_months = (int64) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon; + + if (total_months > INT_MAX || total_months < INT_MIN) + return -1; + span->month = (int32) total_months; + span->day = itm->tm_mday; + if (pg_mul_s64_overflow(itm->tm_hour, USECS_PER_HOUR, + &span->time)) + return -1; + /* tm_min, tm_sec are 32 bits, so intermediate products can't overflow */ + if (pg_add_s64_overflow(span->time, itm->tm_min * USECS_PER_MINUTE, + &span->time)) + return -1; + if (pg_add_s64_overflow(span->time, itm->tm_sec * USECS_PER_SEC, + &span->time)) + return -1; + if (pg_add_s64_overflow(span->time, itm->tm_usec, + &span->time)) + return -1; return 0; } +/* itmin2interval() + * Convert a pg_itm_in structure to an Interval. + * Returns 0 if OK, -1 on overflow. + */ int -tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span) +itmin2interval(struct pg_itm_in *itm_in, Interval *span) { - double total_months = (double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon; + int64 total_months = (int64) itm_in->tm_year * MONTHS_PER_YEAR + itm_in->tm_mon; if (total_months > INT_MAX || total_months < INT_MIN) return -1; - span->month = total_months; - span->day = tm->tm_mday; - span->time = (((((tm->tm_hour * INT64CONST(60)) + - tm->tm_min) * INT64CONST(60)) + - tm->tm_sec) * USECS_PER_SEC) + fsec; - + span->month = (int32) total_months; + span->day = itm_in->tm_mday; + span->time = itm_in->tm_usec; return 0; } @@ -3612,10 +3632,9 @@ timestamp_age(PG_FUNCTION_ARGS) Timestamp dt1 = PG_GETARG_TIMESTAMP(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); Interval *result; - fsec_t fsec, - fsec1, + fsec_t fsec1, fsec2; - struct pg_tm tt, + struct pg_itm tt, *tm = &tt; struct pg_tm tt1, *tm1 = &tt1; @@ -3628,7 +3647,7 @@ timestamp_age(PG_FUNCTION_ARGS) timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0) { /* form the symbolic difference */ - fsec = fsec1 - fsec2; + tm->tm_usec = fsec1 - fsec2; tm->tm_sec = tm1->tm_sec - tm2->tm_sec; tm->tm_min = tm1->tm_min - tm2->tm_min; tm->tm_hour = tm1->tm_hour - tm2->tm_hour; @@ -3639,7 +3658,7 @@ timestamp_age(PG_FUNCTION_ARGS) /* flip sign if necessary... */ if (dt1 < dt2) { - fsec = -fsec; + tm->tm_usec = -tm->tm_usec; tm->tm_sec = -tm->tm_sec; tm->tm_min = -tm->tm_min; tm->tm_hour = -tm->tm_hour; @@ -3649,9 +3668,9 @@ timestamp_age(PG_FUNCTION_ARGS) } /* propagate any negative fields into the next higher field */ - while (fsec < 0) + while (tm->tm_usec < 0) { - fsec += USECS_PER_SEC; + tm->tm_usec += USECS_PER_SEC; tm->tm_sec--; } @@ -3696,7 +3715,7 @@ timestamp_age(PG_FUNCTION_ARGS) /* recover sign if necessary... */ if (dt1 < dt2) { - fsec = -fsec; + tm->tm_usec = -tm->tm_usec; tm->tm_sec = -tm->tm_sec; tm->tm_min = -tm->tm_min; tm->tm_hour = -tm->tm_hour; @@ -3705,7 +3724,7 @@ timestamp_age(PG_FUNCTION_ARGS) tm->tm_year = -tm->tm_year; } - if (tm2interval(tm, fsec, result) != 0) + if (itm2interval(tm, result) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))); @@ -3731,10 +3750,9 @@ timestamptz_age(PG_FUNCTION_ARGS) TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); Interval *result; - fsec_t fsec, - fsec1, + fsec_t fsec1, fsec2; - struct pg_tm tt, + struct pg_itm tt, *tm = &tt; struct pg_tm tt1, *tm1 = &tt1; @@ -3749,7 +3767,7 @@ timestamptz_age(PG_FUNCTION_ARGS) timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0) { /* form the symbolic difference */ - fsec = fsec1 - fsec2; + tm->tm_usec = fsec1 - fsec2; tm->tm_sec = tm1->tm_sec - tm2->tm_sec; tm->tm_min = tm1->tm_min - tm2->tm_min; tm->tm_hour = tm1->tm_hour - tm2->tm_hour; @@ -3760,7 +3778,7 @@ timestamptz_age(PG_FUNCTION_ARGS) /* flip sign if necessary... */ if (dt1 < dt2) { - fsec = -fsec; + tm->tm_usec = -tm->tm_usec; tm->tm_sec = -tm->tm_sec; tm->tm_min = -tm->tm_min; tm->tm_hour = -tm->tm_hour; @@ -3770,9 +3788,9 @@ timestamptz_age(PG_FUNCTION_ARGS) } /* propagate any negative fields into the next higher field */ - while (fsec < 0) + while (tm->tm_usec < 0) { - fsec += USECS_PER_SEC; + tm->tm_usec += USECS_PER_SEC; tm->tm_sec--; } @@ -3821,7 +3839,7 @@ timestamptz_age(PG_FUNCTION_ARGS) /* recover sign if necessary... */ if (dt1 < dt2) { - fsec = -fsec; + tm->tm_usec = -tm->tm_usec; tm->tm_sec = -tm->tm_sec; tm->tm_min = -tm->tm_min; tm->tm_hour = -tm->tm_hour; @@ -3830,7 +3848,7 @@ timestamptz_age(PG_FUNCTION_ARGS) tm->tm_year = -tm->tm_year; } - if (tm2interval(tm, fsec, result) != 0) + if (itm2interval(tm, result) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))); @@ -4317,8 +4335,7 @@ interval_trunc(PG_FUNCTION_ARGS) int type, val; char *lowunits; - fsec_t fsec; - struct pg_tm tt, + struct pg_itm tt, *tm = &tt; result = (Interval *) palloc(sizeof(Interval)); @@ -4331,7 +4348,7 @@ interval_trunc(PG_FUNCTION_ARGS) if (type == UNITS) { - if (interval2tm(*interval, tm, &fsec) == 0) + interval2itm(*interval, tm); { switch (val) { @@ -4366,10 +4383,10 @@ interval_trunc(PG_FUNCTION_ARGS) tm->tm_sec = 0; /* FALL THRU */ case DTK_SECOND: - fsec = 0; + tm->tm_usec = 0; break; case DTK_MILLISEC: - fsec = (fsec / 1000) * 1000; + tm->tm_usec = (tm->tm_usec / 1000) * 1000; break; case DTK_MICROSEC: break; @@ -4382,13 +4399,11 @@ interval_trunc(PG_FUNCTION_ARGS) (val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0)); } - if (tm2interval(tm, fsec, result) != 0) + if (itm2interval(tm, result) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))); } - else - elog(ERROR, "could not convert interval to tm"); } else { @@ -5200,8 +5215,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric) int type, val; char *lowunits; - fsec_t fsec; - struct pg_tm tt, + struct pg_itm tt, *tm = &tt; lowunits = downcase_truncate_identifier(VARDATA_ANY(units), @@ -5214,12 +5228,12 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric) if (type == UNITS) { - if (interval2tm(*interval, tm, &fsec) == 0) + interval2itm(*interval, tm); { switch (val) { case DTK_MICROSEC: - intresult = tm->tm_sec * INT64CONST(1000000) + fsec; + intresult = tm->tm_sec * INT64CONST(1000000) + tm->tm_usec; break; case DTK_MILLISEC: @@ -5228,9 +5242,9 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric) * tm->tm_sec * 1000 + fsec / 1000 * = (tm->tm_sec * 1'000'000 + fsec) / 1000 */ - PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3)); + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec, 3)); else - PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0); + PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + tm->tm_usec / 1000.0); break; case DTK_SECOND: @@ -5239,9 +5253,9 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric) * tm->tm_sec + fsec / 1'000'000 * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000 */ - PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6)); + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec, 6)); else - PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0); + PG_RETURN_FLOAT8(tm->tm_sec + tm->tm_usec / 1000000.0); break; case DTK_MINUTE: @@ -5291,11 +5305,6 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric) intresult = 0; } } - else - { - elog(ERROR, "could not convert interval to tm"); - intresult = 0; - } } else if (type == RESERV && val == DTK_EPOCH) { diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h index 5fa38d20d8..d155f1b03b 100644 --- a/src/include/datatype/timestamp.h +++ b/src/include/datatype/timestamp.h @@ -40,6 +40,10 @@ typedef int64 TimestampTz; typedef int64 TimeOffset; typedef int32 fsec_t; /* fractional seconds (in microseconds) */ + +/* + * Storage format for type interval. + */ typedef struct { TimeOffset time; /* all time units other than days, months and @@ -48,6 +52,41 @@ typedef struct int32 month; /* months and years, after time for alignment */ } Interval; +/* + * Data structure representing a broken-down interval. + * + * For historical reasons, this is modeled on struct pg_tm for timestamps. + * Unlike the situation for timestamps, there's no magic interpretation + * needed for months or years: they're just zero or not. Note that fields + * can be negative; however, because of the divisions done while converting + * from struct Interval, only tm_mday could be INT_MIN. This is important + * because we may need to negate the values in some code paths. + */ +struct pg_itm +{ + int tm_usec; + int tm_sec; + int tm_min; + int64 tm_hour; /* needs to be wide */ + int tm_mday; + int tm_mon; + int tm_year; +}; + +/* + * Data structure for decoding intervals. We could just use struct pg_itm, + * but then the requirement for tm_usec to be 64 bits would propagate to + * places where it's not really needed. Also, omitting the fields that + * aren't used during decoding seems like a good error-prevention measure. + */ +struct pg_itm_in +{ + int64 tm_usec; /* needs to be wide */ + int tm_mday; + int tm_mon; + int tm_year; +}; + /* Limits on the "precision" option (typmod) for these data types */ #define MAX_TIMESTAMP_PRECISION 6 diff --git a/src/include/pgtime.h b/src/include/pgtime.h index 2977b13aab..441d7847c1 100644 --- a/src/include/pgtime.h +++ b/src/include/pgtime.h @@ -23,6 +23,8 @@ typedef int64 pg_time_t; /* + * Data structure representing a broken-down timestamp. + * * CAUTION: the IANA timezone library (src/timezone/) follows the POSIX * convention that tm_mon counts from 0 and tm_year is relative to 1900. * However, Postgres' datetime functions generally treat tm_mon as counting @@ -44,6 +46,7 @@ struct pg_tm const char *tm_zone; }; +/* These structs are opaque outside the timezone library */ typedef struct pg_tz pg_tz; typedef struct pg_tzenum pg_tzenum; diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 0d158f3e4b..0801858d60 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -300,9 +300,9 @@ extern int DecodeTimeOnly(char **field, int *ftype, int nf, int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp); extern int DecodeInterval(char **field, int *ftype, int nf, int range, - int *dtype, struct pg_tm *tm, fsec_t *fsec); + int *dtype, struct pg_itm_in *itm_in); extern int DecodeISO8601Interval(char *str, - int *dtype, struct pg_tm *tm, fsec_t *fsec); + int *dtype, struct pg_itm_in *itm_in); extern void DateTimeParseError(int dterr, const char *str, const char *datatype) pg_attribute_noreturn(); @@ -315,7 +315,7 @@ extern int DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr, extern void EncodeDateOnly(struct pg_tm *tm, int style, char *str); extern void EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str); extern void EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str); -extern void EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str); +extern void EncodeInterval(struct pg_itm *itm, int style, char *str); extern void EncodeSpecialTimestamp(Timestamp dt, char *str); extern int ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc, diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h index c1a74f8e2b..d33421d380 100644 --- a/src/include/utils/timestamp.h +++ b/src/include/utils/timestamp.h @@ -88,8 +88,9 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm, fsec_t *fsec, const char **tzn, pg_tz *attimezone); extern void dt2time(Timestamp dt, int *hour, int *min, int *sec, fsec_t *fsec); -extern int interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec); -extern int tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span); +extern void interval2itm(Interval span, struct pg_itm *itm); +extern int itm2interval(struct pg_itm *itm, Interval *span); +extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span); extern Timestamp SetEpochTimestamp(void); extern void GetEpochTime(struct pg_tm *tm); diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 9a7e2852f2..86c8d4bc99 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -928,6 +928,617 @@ select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds'; @ 0.7 secs | @ 0.7 secs | @ 0.7 secs (1 row) +-- test time fields using entire 64 bit microseconds range +select interval '2562047788.01521550194 hours'; + interval +----------------------------------- + @ 2562047788 hours 54.775807 secs +(1 row) + +select interval '-2562047788.01521550222 hours'; + interval +--------------------------------------- + @ 2562047788 hours 54.775808 secs ago +(1 row) + +select interval '153722867280.912930117 minutes'; + interval +----------------------------------- + @ 2562047788 hours 54.775807 secs +(1 row) + +select interval '-153722867280.912930133 minutes'; + interval +--------------------------------------- + @ 2562047788 hours 54.775808 secs ago +(1 row) + +select interval '9223372036854.775807 seconds'; + interval +----------------------------------- + @ 2562047788 hours 54.775807 secs +(1 row) + +select interval '-9223372036854.775808 seconds'; + interval +--------------------------------------- + @ 2562047788 hours 54.775808 secs ago +(1 row) + +select interval '9223372036854775.807 milliseconds'; + interval +----------------------------------- + @ 2562047788 hours 54.775807 secs +(1 row) + +select interval '-9223372036854775.808 milliseconds'; + interval +--------------------------------------- + @ 2562047788 hours 54.775808 secs ago +(1 row) + +select interval '9223372036854775807 microseconds'; + interval +----------------------------------- + @ 2562047788 hours 54.775807 secs +(1 row) + +select interval '-9223372036854775808 microseconds'; + interval +--------------------------------------- + @ 2562047788 hours 54.775808 secs ago +(1 row) + +select interval 'PT2562047788H54.775807S'; + interval +----------------------------------- + @ 2562047788 hours 54.775807 secs +(1 row) + +select interval 'PT-2562047788H-54.775808S'; + interval +--------------------------------------- + @ 2562047788 hours 54.775808 secs ago +(1 row) + +select interval 'PT2562047788:00:54.775807'; + interval +----------------------------------- + @ 2562047788 hours 54.775807 secs +(1 row) + +select interval 'PT2562047788.0152155019444'; + interval +----------------------------------- + @ 2562047788 hours 54.775807 secs +(1 row) + +select interval 'PT-2562047788.0152155022222'; + interval +--------------------------------------- + @ 2562047788 hours 54.775808 secs ago +(1 row) + +-- overflow each date/time field +select interval '2147483648 years'; +ERROR: interval field value out of range: "2147483648 years" +LINE 1: select interval '2147483648 years'; + ^ +select interval '-2147483649 years'; +ERROR: interval field value out of range: "-2147483649 years" +LINE 1: select interval '-2147483649 years'; + ^ +select interval '2147483648 months'; +ERROR: interval field value out of range: "2147483648 months" +LINE 1: select interval '2147483648 months'; + ^ +select interval '-2147483649 months'; +ERROR: interval field value out of range: "-2147483649 months" +LINE 1: select interval '-2147483649 months'; + ^ +select interval '2147483648 days'; +ERROR: interval field value out of range: "2147483648 days" +LINE 1: select interval '2147483648 days'; + ^ +select interval '-2147483649 days'; +ERROR: interval field value out of range: "-2147483649 days" +LINE 1: select interval '-2147483649 days'; + ^ +select interval '2562047789 hours'; +ERROR: interval field value out of range: "2562047789 hours" +LINE 1: select interval '2562047789 hours'; + ^ +select interval '-2562047789 hours'; +ERROR: interval field value out of range: "-2562047789 hours" +LINE 1: select interval '-2562047789 hours'; + ^ +select interval '153722867281 minutes'; +ERROR: interval field value out of range: "153722867281 minutes" +LINE 1: select interval '153722867281 minutes'; + ^ +select interval '-153722867281 minutes'; +ERROR: interval field value out of range: "-153722867281 minutes" +LINE 1: select interval '-153722867281 minutes'; + ^ +select interval '9223372036855 seconds'; +ERROR: interval field value out of range: "9223372036855 seconds" +LINE 1: select interval '9223372036855 seconds'; + ^ +select interval '-9223372036855 seconds'; +ERROR: interval field value out of range: "-9223372036855 seconds" +LINE 1: select interval '-9223372036855 seconds'; + ^ +select interval '9223372036854777 millisecond'; +ERROR: interval field value out of range: "9223372036854777 millisecond" +LINE 1: select interval '9223372036854777 millisecond'; + ^ +select interval '-9223372036854777 millisecond'; +ERROR: interval field value out of range: "-9223372036854777 millisecond" +LINE 1: select interval '-9223372036854777 millisecond'; + ^ +select interval '9223372036854775808 microsecond'; +ERROR: interval field value out of range: "9223372036854775808 microsecond" +LINE 1: select interval '9223372036854775808 microsecond'; + ^ +select interval '-9223372036854775809 microsecond'; +ERROR: interval field value out of range: "-9223372036854775809 microsecond" +LINE 1: select interval '-9223372036854775809 microsecond'; + ^ +select interval 'P2147483648'; +ERROR: interval field value out of range: "P2147483648" +LINE 1: select interval 'P2147483648'; + ^ +select interval 'P-2147483649'; +ERROR: interval field value out of range: "P-2147483649" +LINE 1: select interval 'P-2147483649'; + ^ +select interval 'P1-2147483647-2147483647'; +ERROR: interval out of range +LINE 1: select interval 'P1-2147483647-2147483647'; + ^ +select interval 'PT2562047789'; +ERROR: interval field value out of range: "PT2562047789" +LINE 1: select interval 'PT2562047789'; + ^ +select interval 'PT-2562047789'; +ERROR: interval field value out of range: "PT-2562047789" +LINE 1: select interval 'PT-2562047789'; + ^ +-- overflow with date/time unit aliases +select interval '2147483647 weeks'; +ERROR: interval field value out of range: "2147483647 weeks" +LINE 1: select interval '2147483647 weeks'; + ^ +select interval '-2147483648 weeks'; +ERROR: interval field value out of range: "-2147483648 weeks" +LINE 1: select interval '-2147483648 weeks'; + ^ +select interval '2147483647 decades'; +ERROR: interval field value out of range: "2147483647 decades" +LINE 1: select interval '2147483647 decades'; + ^ +select interval '-2147483648 decades'; +ERROR: interval field value out of range: "-2147483648 decades" +LINE 1: select interval '-2147483648 decades'; + ^ +select interval '2147483647 centuries'; +ERROR: interval field value out of range: "2147483647 centuries" +LINE 1: select interval '2147483647 centuries'; + ^ +select interval '-2147483648 centuries'; +ERROR: interval field value out of range: "-2147483648 centuries" +LINE 1: select interval '-2147483648 centuries'; + ^ +select interval '2147483647 millennium'; +ERROR: interval field value out of range: "2147483647 millennium" +LINE 1: select interval '2147483647 millennium'; + ^ +select interval '-2147483648 millennium'; +ERROR: interval field value out of range: "-2147483648 millennium" +LINE 1: select interval '-2147483648 millennium'; + ^ +select interval '1 week 2147483647 days'; +ERROR: interval field value out of range: "1 week 2147483647 days" +LINE 1: select interval '1 week 2147483647 days'; + ^ +select interval '-1 week -2147483648 days'; +ERROR: interval field value out of range: "-1 week -2147483648 days" +LINE 1: select interval '-1 week -2147483648 days'; + ^ +select interval '2147483647 days 1 week'; +ERROR: interval field value out of range: "2147483647 days 1 week" +LINE 1: select interval '2147483647 days 1 week'; + ^ +select interval '-2147483648 days -1 week'; +ERROR: interval field value out of range: "-2147483648 days -1 week" +LINE 1: select interval '-2147483648 days -1 week'; + ^ +select interval 'P1W2147483647D'; +ERROR: interval field value out of range: "P1W2147483647D" +LINE 1: select interval 'P1W2147483647D'; + ^ +select interval 'P-1W-2147483648D'; +ERROR: interval field value out of range: "P-1W-2147483648D" +LINE 1: select interval 'P-1W-2147483648D'; + ^ +select interval 'P2147483647D1W'; +ERROR: interval field value out of range: "P2147483647D1W" +LINE 1: select interval 'P2147483647D1W'; + ^ +select interval 'P-2147483648D-1W'; +ERROR: interval field value out of range: "P-2147483648D-1W" +LINE 1: select interval 'P-2147483648D-1W'; + ^ +select interval '1 decade 2147483647 years'; +ERROR: interval field value out of range: "1 decade 2147483647 years" +LINE 1: select interval '1 decade 2147483647 years'; + ^ +select interval '1 century 2147483647 years'; +ERROR: interval field value out of range: "1 century 2147483647 years" +LINE 1: select interval '1 century 2147483647 years'; + ^ +select interval '1 millennium 2147483647 years'; +ERROR: interval field value out of range: "1 millennium 2147483647 years" +LINE 1: select interval '1 millennium 2147483647 years'; + ^ +select interval '-1 decade -2147483648 years'; +ERROR: interval field value out of range: "-1 decade -2147483648 years" +LINE 1: select interval '-1 decade -2147483648 years'; + ^ +select interval '-1 century -2147483648 years'; +ERROR: interval field value out of range: "-1 century -2147483648 years" +LINE 1: select interval '-1 century -2147483648 years'; + ^ +select interval '-1 millennium -2147483648 years'; +ERROR: interval field value out of range: "-1 millennium -2147483648 years" +LINE 1: select interval '-1 millennium -2147483648 years'; + ^ +select interval '2147483647 years 1 decade'; +ERROR: interval field value out of range: "2147483647 years 1 decade" +LINE 1: select interval '2147483647 years 1 decade'; + ^ +select interval '2147483647 years 1 century'; +ERROR: interval field value out of range: "2147483647 years 1 century" +LINE 1: select interval '2147483647 years 1 century'; + ^ +select interval '2147483647 years 1 millennium'; +ERROR: interval field value out of range: "2147483647 years 1 millennium" +LINE 1: select interval '2147483647 years 1 millennium'; + ^ +select interval '-2147483648 years -1 decade'; +ERROR: interval field value out of range: "-2147483648 years -1 decade" +LINE 1: select interval '-2147483648 years -1 decade'; + ^ +select interval '-2147483648 years -1 century'; +ERROR: interval field value out of range: "-2147483648 years -1 century" +LINE 1: select interval '-2147483648 years -1 century'; + ^ +select interval '-2147483648 years -1 millennium'; +ERROR: interval field value out of range: "-2147483648 years -1 millennium" +LINE 1: select interval '-2147483648 years -1 millennium'; + ^ +-- overflowing with fractional fields - postgres format +select interval '0.1 millennium 2147483647 months'; +ERROR: interval field value out of range: "0.1 millennium 2147483647 months" +LINE 1: select interval '0.1 millennium 2147483647 months'; + ^ +select interval '0.1 centuries 2147483647 months'; +ERROR: interval field value out of range: "0.1 centuries 2147483647 months" +LINE 1: select interval '0.1 centuries 2147483647 months'; + ^ +select interval '0.1 decades 2147483647 months'; +ERROR: interval field value out of range: "0.1 decades 2147483647 months" +LINE 1: select interval '0.1 decades 2147483647 months'; + ^ +select interval '0.1 yrs 2147483647 months'; +ERROR: interval field value out of range: "0.1 yrs 2147483647 months" +LINE 1: select interval '0.1 yrs 2147483647 months'; + ^ +select interval '-0.1 millennium -2147483648 months'; +ERROR: interval field value out of range: "-0.1 millennium -2147483648 months" +LINE 1: select interval '-0.1 millennium -2147483648 months'; + ^ +select interval '-0.1 centuries -2147483648 months'; +ERROR: interval field value out of range: "-0.1 centuries -2147483648 months" +LINE 1: select interval '-0.1 centuries -2147483648 months'; + ^ +select interval '-0.1 decades -2147483648 months'; +ERROR: interval field value out of range: "-0.1 decades -2147483648 months" +LINE 1: select interval '-0.1 decades -2147483648 months'; + ^ +select interval '-0.1 yrs -2147483648 months'; +ERROR: interval field value out of range: "-0.1 yrs -2147483648 months" +LINE 1: select interval '-0.1 yrs -2147483648 months'; + ^ +select interval '2147483647 months 0.1 millennium'; +ERROR: interval field value out of range: "2147483647 months 0.1 millennium" +LINE 1: select interval '2147483647 months 0.1 millennium'; + ^ +select interval '2147483647 months 0.1 centuries'; +ERROR: interval field value out of range: "2147483647 months 0.1 centuries" +LINE 1: select interval '2147483647 months 0.1 centuries'; + ^ +select interval '2147483647 months 0.1 decades'; +ERROR: interval field value out of range: "2147483647 months 0.1 decades" +LINE 1: select interval '2147483647 months 0.1 decades'; + ^ +select interval '2147483647 months 0.1 yrs'; +ERROR: interval field value out of range: "2147483647 months 0.1 yrs" +LINE 1: select interval '2147483647 months 0.1 yrs'; + ^ +select interval '-2147483648 months -0.1 millennium'; +ERROR: interval field value out of range: "-2147483648 months -0.1 millennium" +LINE 1: select interval '-2147483648 months -0.1 millennium'; + ^ +select interval '-2147483648 months -0.1 centuries'; +ERROR: interval field value out of range: "-2147483648 months -0.1 centuries" +LINE 1: select interval '-2147483648 months -0.1 centuries'; + ^ +select interval '-2147483648 months -0.1 decades'; +ERROR: interval field value out of range: "-2147483648 months -0.1 decades" +LINE 1: select interval '-2147483648 months -0.1 decades'; + ^ +select interval '-2147483648 months -0.1 yrs'; +ERROR: interval field value out of range: "-2147483648 months -0.1 yrs" +LINE 1: select interval '-2147483648 months -0.1 yrs'; + ^ +select interval '0.1 months 2147483647 days'; +ERROR: interval field value out of range: "0.1 months 2147483647 days" +LINE 1: select interval '0.1 months 2147483647 days'; + ^ +select interval '-0.1 months -2147483648 days'; +ERROR: interval field value out of range: "-0.1 months -2147483648 days" +LINE 1: select interval '-0.1 months -2147483648 days'; + ^ +select interval '2147483647 days 0.1 months'; +ERROR: interval field value out of range: "2147483647 days 0.1 months" +LINE 1: select interval '2147483647 days 0.1 months'; + ^ +select interval '-2147483648 days -0.1 months'; +ERROR: interval field value out of range: "-2147483648 days -0.1 months" +LINE 1: select interval '-2147483648 days -0.1 months'; + ^ +select interval '0.5 weeks 2147483647 days'; +ERROR: interval field value out of range: "0.5 weeks 2147483647 days" +LINE 1: select interval '0.5 weeks 2147483647 days'; + ^ +select interval '-0.5 weeks -2147483648 days'; +ERROR: interval field value out of range: "-0.5 weeks -2147483648 days" +LINE 1: select interval '-0.5 weeks -2147483648 days'; + ^ +select interval '2147483647 days 0.5 weeks'; +ERROR: interval field value out of range: "2147483647 days 0.5 weeks" +LINE 1: select interval '2147483647 days 0.5 weeks'; + ^ +select interval '-2147483648 days -0.5 weeks'; +ERROR: interval field value out of range: "-2147483648 days -0.5 weeks" +LINE 1: select interval '-2147483648 days -0.5 weeks'; + ^ +select interval '0.01 months 9223372036854775807 microseconds'; +ERROR: interval field value out of range: "0.01 months 9223372036854775807 microseconds" +LINE 1: select interval '0.01 months 9223372036854775807 microsecond... + ^ +select interval '-0.01 months -9223372036854775808 microseconds'; +ERROR: interval field value out of range: "-0.01 months -9223372036854775808 microseconds" +LINE 1: select interval '-0.01 months -9223372036854775808 microseco... + ^ +select interval '9223372036854775807 microseconds 0.01 months'; +ERROR: interval field value out of range: "9223372036854775807 microseconds 0.01 months" +LINE 1: select interval '9223372036854775807 microseconds 0.01 month... + ^ +select interval '-9223372036854775808 microseconds -0.01 months'; +ERROR: interval field value out of range: "-9223372036854775808 microseconds -0.01 months" +LINE 1: select interval '-9223372036854775808 microseconds -0.01 mon... + ^ +select interval '0.1 weeks 9223372036854775807 microseconds'; +ERROR: interval field value out of range: "0.1 weeks 9223372036854775807 microseconds" +LINE 1: select interval '0.1 weeks 9223372036854775807 microseconds'... + ^ +select interval '-0.1 weeks -9223372036854775808 microseconds'; +ERROR: interval field value out of range: "-0.1 weeks -9223372036854775808 microseconds" +LINE 1: select interval '-0.1 weeks -9223372036854775808 microsecond... + ^ +select interval '9223372036854775807 microseconds 0.1 weeks'; +ERROR: interval field value out of range: "9223372036854775807 microseconds 0.1 weeks" +LINE 1: select interval '9223372036854775807 microseconds 0.1 weeks'... + ^ +select interval '-9223372036854775808 microseconds -0.1 weeks'; +ERROR: interval field value out of range: "-9223372036854775808 microseconds -0.1 weeks" +LINE 1: select interval '-9223372036854775808 microseconds -0.1 week... + ^ +select interval '0.1 days 9223372036854775807 microseconds'; +ERROR: interval field value out of range: "0.1 days 9223372036854775807 microseconds" +LINE 1: select interval '0.1 days 9223372036854775807 microseconds'; + ^ +select interval '-0.1 days -9223372036854775808 microseconds'; +ERROR: interval field value out of range: "-0.1 days -9223372036854775808 microseconds" +LINE 1: select interval '-0.1 days -9223372036854775808 microseconds... + ^ +select interval '9223372036854775807 microseconds 0.1 days'; +ERROR: interval field value out of range: "9223372036854775807 microseconds 0.1 days" +LINE 1: select interval '9223372036854775807 microseconds 0.1 days'; + ^ +select interval '-9223372036854775808 microseconds -0.1 days'; +ERROR: interval field value out of range: "-9223372036854775808 microseconds -0.1 days" +LINE 1: select interval '-9223372036854775808 microseconds -0.1 days... + ^ +-- overflowing with fractional fields - ISO8601 format +select interval 'P0.1Y2147483647M'; +ERROR: interval field value out of range: "P0.1Y2147483647M" +LINE 1: select interval 'P0.1Y2147483647M'; + ^ +select interval 'P-0.1Y-2147483648M'; +ERROR: interval field value out of range: "P-0.1Y-2147483648M" +LINE 1: select interval 'P-0.1Y-2147483648M'; + ^ +select interval 'P2147483647M0.1Y'; +ERROR: interval field value out of range: "P2147483647M0.1Y" +LINE 1: select interval 'P2147483647M0.1Y'; + ^ +select interval 'P-2147483648M-0.1Y'; +ERROR: interval field value out of range: "P-2147483648M-0.1Y" +LINE 1: select interval 'P-2147483648M-0.1Y'; + ^ +select interval 'P0.1M2147483647D'; +ERROR: interval field value out of range: "P0.1M2147483647D" +LINE 1: select interval 'P0.1M2147483647D'; + ^ +select interval 'P-0.1M-2147483648D'; +ERROR: interval field value out of range: "P-0.1M-2147483648D" +LINE 1: select interval 'P-0.1M-2147483648D'; + ^ +select interval 'P2147483647D0.1M'; +ERROR: interval field value out of range: "P2147483647D0.1M" +LINE 1: select interval 'P2147483647D0.1M'; + ^ +select interval 'P-2147483648D-0.1M'; +ERROR: interval field value out of range: "P-2147483648D-0.1M" +LINE 1: select interval 'P-2147483648D-0.1M'; + ^ +select interval 'P0.5W2147483647D'; +ERROR: interval field value out of range: "P0.5W2147483647D" +LINE 1: select interval 'P0.5W2147483647D'; + ^ +select interval 'P-0.5W-2147483648D'; +ERROR: interval field value out of range: "P-0.5W-2147483648D" +LINE 1: select interval 'P-0.5W-2147483648D'; + ^ +select interval 'P2147483647D0.5W'; +ERROR: interval field value out of range: "P2147483647D0.5W" +LINE 1: select interval 'P2147483647D0.5W'; + ^ +select interval 'P-2147483648D-0.5W'; +ERROR: interval field value out of range: "P-2147483648D-0.5W" +LINE 1: select interval 'P-2147483648D-0.5W'; + ^ +select interval 'P0.01MT2562047788H54.775807S'; +ERROR: interval field value out of range: "P0.01MT2562047788H54.775807S" +LINE 1: select interval 'P0.01MT2562047788H54.775807S'; + ^ +select interval 'P-0.01MT-2562047788H-54.775808S'; +ERROR: interval field value out of range: "P-0.01MT-2562047788H-54.775808S" +LINE 1: select interval 'P-0.01MT-2562047788H-54.775808S'; + ^ +select interval 'P0.1DT2562047788H54.775807S'; +ERROR: interval field value out of range: "P0.1DT2562047788H54.775807S" +LINE 1: select interval 'P0.1DT2562047788H54.775807S'; + ^ +select interval 'P-0.1DT-2562047788H-54.775808S'; +ERROR: interval field value out of range: "P-0.1DT-2562047788H-54.775808S" +LINE 1: select interval 'P-0.1DT-2562047788H-54.775808S'; + ^ +select interval 'PT2562047788.1H54.775807S'; +ERROR: interval field value out of range: "PT2562047788.1H54.775807S" +LINE 1: select interval 'PT2562047788.1H54.775807S'; + ^ +select interval 'PT-2562047788.1H-54.775808S'; +ERROR: interval field value out of range: "PT-2562047788.1H-54.775808S" +LINE 1: select interval 'PT-2562047788.1H-54.775808S'; + ^ +select interval 'PT2562047788H0.1M54.775807S'; +ERROR: interval field value out of range: "PT2562047788H0.1M54.775807S" +LINE 1: select interval 'PT2562047788H0.1M54.775807S'; + ^ +select interval 'PT-2562047788H-0.1M-54.775808S'; +ERROR: interval field value out of range: "PT-2562047788H-0.1M-54.775808S" +LINE 1: select interval 'PT-2562047788H-0.1M-54.775808S'; + ^ +-- overflowing with fractional fields - ISO8601 alternative format +select interval 'P0.1-2147483647-00'; +ERROR: interval field value out of range: "P0.1-2147483647-00" +LINE 1: select interval 'P0.1-2147483647-00'; + ^ +select interval 'P00-0.1-2147483647'; +ERROR: interval field value out of range: "P00-0.1-2147483647" +LINE 1: select interval 'P00-0.1-2147483647'; + ^ +select interval 'P00-0.01-00T2562047788:00:54.775807'; +ERROR: interval field value out of range: "P00-0.01-00T2562047788:00:54.775807" +LINE 1: select interval 'P00-0.01-00T2562047788:00:54.775807'; + ^ +select interval 'P00-00-0.1T2562047788:00:54.775807'; +ERROR: interval field value out of range: "P00-00-0.1T2562047788:00:54.775807" +LINE 1: select interval 'P00-00-0.1T2562047788:00:54.775807'; + ^ +select interval 'PT2562047788.1:00:54.775807'; +ERROR: interval field value out of range: "PT2562047788.1:00:54.775807" +LINE 1: select interval 'PT2562047788.1:00:54.775807'; + ^ +select interval 'PT2562047788:01.:54.775807'; +ERROR: interval field value out of range: "PT2562047788:01.:54.775807" +LINE 1: select interval 'PT2562047788:01.:54.775807'; + ^ +-- overflowing with fractional fields - SQL standard format +select interval '0.1 2562047788:0:54.775807'; +ERROR: interval field value out of range: "0.1 2562047788:0:54.775807" +LINE 1: select interval '0.1 2562047788:0:54.775807'; + ^ +select interval '0.1 2562047788:0:54.775808 ago'; +ERROR: interval field value out of range: "0.1 2562047788:0:54.775808 ago" +LINE 1: select interval '0.1 2562047788:0:54.775808 ago'; + ^ +select interval '2562047788.1:0:54.775807'; +ERROR: interval field value out of range: "2562047788.1:0:54.775807" +LINE 1: select interval '2562047788.1:0:54.775807'; + ^ +select interval '2562047788.1:0:54.775808 ago'; +ERROR: interval field value out of range: "2562047788.1:0:54.775808 ago" +LINE 1: select interval '2562047788.1:0:54.775808 ago'; + ^ +select interval '2562047788:0.1:54.775807'; +ERROR: invalid input syntax for type interval: "2562047788:0.1:54.775807" +LINE 1: select interval '2562047788:0.1:54.775807'; + ^ +select interval '2562047788:0.1:54.775808 ago'; +ERROR: invalid input syntax for type interval: "2562047788:0.1:54.775808 ago" +LINE 1: select interval '2562047788:0.1:54.775808 ago'; + ^ +-- overflowing using AGO with INT_MIN +select interval '-2147483648 months ago'; +ERROR: interval field value out of range: "-2147483648 months ago" +LINE 1: select interval '-2147483648 months ago'; + ^ +select interval '-2147483648 days ago'; +ERROR: interval field value out of range: "-2147483648 days ago" +LINE 1: select interval '-2147483648 days ago'; + ^ +select interval '-9223372036854775808 microseconds ago'; +ERROR: interval field value out of range: "-9223372036854775808 microseconds ago" +LINE 1: select interval '-9223372036854775808 microseconds ago'; + ^ +select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago'; +ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago" +LINE 1: select interval '-2147483648 months -2147483648 days -922337... + ^ +-- test that INT_MIN number is formatted properly +SET IntervalStyle to postgres; +select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; + interval +-------------------------------------------------------------------- + -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808 +(1 row) + +SET IntervalStyle to sql_standard; +select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; + interval +--------------------------------------------------- + -178956970-8 -2147483648 -2562047788:00:54.775808 +(1 row) + +SET IntervalStyle to iso_8601; +select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; + interval +----------------------------------------------------- + P-178956970Y-8M-2147483648DT-2562047788H-54.775808S +(1 row) + +SET IntervalStyle to postgres_verbose; +select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; + interval +------------------------------------------------------------------------------ + @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago +(1 row) + -- check that '30 days' equals '1 month' according to the hash function select '30 days'::interval = '1 month'::interval as t; t diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 811b581e09..f05055e03a 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -318,6 +318,190 @@ select interval '-10 mons -3 days +03:55:06.70'; select interval '1 year 2 mons 3 days 04:05:06.699999'; select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds'; +-- test time fields using entire 64 bit microseconds range +select interval '2562047788.01521550194 hours'; +select interval '-2562047788.01521550222 hours'; +select interval '153722867280.912930117 minutes'; +select interval '-153722867280.912930133 minutes'; +select interval '9223372036854.775807 seconds'; +select interval '-9223372036854.775808 seconds'; +select interval '9223372036854775.807 milliseconds'; +select interval '-9223372036854775.808 milliseconds'; +select interval '9223372036854775807 microseconds'; +select interval '-9223372036854775808 microseconds'; + +select interval 'PT2562047788H54.775807S'; +select interval 'PT-2562047788H-54.775808S'; + +select interval 'PT2562047788:00:54.775807'; + +select interval 'PT2562047788.0152155019444'; +select interval 'PT-2562047788.0152155022222'; + +-- overflow each date/time field +select interval '2147483648 years'; +select interval '-2147483649 years'; +select interval '2147483648 months'; +select interval '-2147483649 months'; +select interval '2147483648 days'; +select interval '-2147483649 days'; +select interval '2562047789 hours'; +select interval '-2562047789 hours'; +select interval '153722867281 minutes'; +select interval '-153722867281 minutes'; +select interval '9223372036855 seconds'; +select interval '-9223372036855 seconds'; +select interval '9223372036854777 millisecond'; +select interval '-9223372036854777 millisecond'; +select interval '9223372036854775808 microsecond'; +select interval '-9223372036854775809 microsecond'; + +select interval 'P2147483648'; +select interval 'P-2147483649'; +select interval 'P1-2147483647-2147483647'; +select interval 'PT2562047789'; +select interval 'PT-2562047789'; + +-- overflow with date/time unit aliases +select interval '2147483647 weeks'; +select interval '-2147483648 weeks'; +select interval '2147483647 decades'; +select interval '-2147483648 decades'; +select interval '2147483647 centuries'; +select interval '-2147483648 centuries'; +select interval '2147483647 millennium'; +select interval '-2147483648 millennium'; + +select interval '1 week 2147483647 days'; +select interval '-1 week -2147483648 days'; +select interval '2147483647 days 1 week'; +select interval '-2147483648 days -1 week'; + +select interval 'P1W2147483647D'; +select interval 'P-1W-2147483648D'; +select interval 'P2147483647D1W'; +select interval 'P-2147483648D-1W'; + +select interval '1 decade 2147483647 years'; +select interval '1 century 2147483647 years'; +select interval '1 millennium 2147483647 years'; +select interval '-1 decade -2147483648 years'; +select interval '-1 century -2147483648 years'; +select interval '-1 millennium -2147483648 years'; + +select interval '2147483647 years 1 decade'; +select interval '2147483647 years 1 century'; +select interval '2147483647 years 1 millennium'; +select interval '-2147483648 years -1 decade'; +select interval '-2147483648 years -1 century'; +select interval '-2147483648 years -1 millennium'; + +-- overflowing with fractional fields - postgres format +select interval '0.1 millennium 2147483647 months'; +select interval '0.1 centuries 2147483647 months'; +select interval '0.1 decades 2147483647 months'; +select interval '0.1 yrs 2147483647 months'; +select interval '-0.1 millennium -2147483648 months'; +select interval '-0.1 centuries -2147483648 months'; +select interval '-0.1 decades -2147483648 months'; +select interval '-0.1 yrs -2147483648 months'; + +select interval '2147483647 months 0.1 millennium'; +select interval '2147483647 months 0.1 centuries'; +select interval '2147483647 months 0.1 decades'; +select interval '2147483647 months 0.1 yrs'; +select interval '-2147483648 months -0.1 millennium'; +select interval '-2147483648 months -0.1 centuries'; +select interval '-2147483648 months -0.1 decades'; +select interval '-2147483648 months -0.1 yrs'; + +select interval '0.1 months 2147483647 days'; +select interval '-0.1 months -2147483648 days'; +select interval '2147483647 days 0.1 months'; +select interval '-2147483648 days -0.1 months'; + +select interval '0.5 weeks 2147483647 days'; +select interval '-0.5 weeks -2147483648 days'; +select interval '2147483647 days 0.5 weeks'; +select interval '-2147483648 days -0.5 weeks'; + +select interval '0.01 months 9223372036854775807 microseconds'; +select interval '-0.01 months -9223372036854775808 microseconds'; +select interval '9223372036854775807 microseconds 0.01 months'; +select interval '-9223372036854775808 microseconds -0.01 months'; + +select interval '0.1 weeks 9223372036854775807 microseconds'; +select interval '-0.1 weeks -9223372036854775808 microseconds'; +select interval '9223372036854775807 microseconds 0.1 weeks'; +select interval '-9223372036854775808 microseconds -0.1 weeks'; + +select interval '0.1 days 9223372036854775807 microseconds'; +select interval '-0.1 days -9223372036854775808 microseconds'; +select interval '9223372036854775807 microseconds 0.1 days'; +select interval '-9223372036854775808 microseconds -0.1 days'; + +-- overflowing with fractional fields - ISO8601 format +select interval 'P0.1Y2147483647M'; +select interval 'P-0.1Y-2147483648M'; +select interval 'P2147483647M0.1Y'; +select interval 'P-2147483648M-0.1Y'; + +select interval 'P0.1M2147483647D'; +select interval 'P-0.1M-2147483648D'; +select interval 'P2147483647D0.1M'; +select interval 'P-2147483648D-0.1M'; + +select interval 'P0.5W2147483647D'; +select interval 'P-0.5W-2147483648D'; +select interval 'P2147483647D0.5W'; +select interval 'P-2147483648D-0.5W'; + +select interval 'P0.01MT2562047788H54.775807S'; +select interval 'P-0.01MT-2562047788H-54.775808S'; + +select interval 'P0.1DT2562047788H54.775807S'; +select interval 'P-0.1DT-2562047788H-54.775808S'; + +select interval 'PT2562047788.1H54.775807S'; +select interval 'PT-2562047788.1H-54.775808S'; + +select interval 'PT2562047788H0.1M54.775807S'; +select interval 'PT-2562047788H-0.1M-54.775808S'; + +-- overflowing with fractional fields - ISO8601 alternative format +select interval 'P0.1-2147483647-00'; +select interval 'P00-0.1-2147483647'; +select interval 'P00-0.01-00T2562047788:00:54.775807'; +select interval 'P00-00-0.1T2562047788:00:54.775807'; +select interval 'PT2562047788.1:00:54.775807'; +select interval 'PT2562047788:01.:54.775807'; + +-- overflowing with fractional fields - SQL standard format +select interval '0.1 2562047788:0:54.775807'; +select interval '0.1 2562047788:0:54.775808 ago'; + +select interval '2562047788.1:0:54.775807'; +select interval '2562047788.1:0:54.775808 ago'; + +select interval '2562047788:0.1:54.775807'; +select interval '2562047788:0.1:54.775808 ago'; + +-- overflowing using AGO with INT_MIN +select interval '-2147483648 months ago'; +select interval '-2147483648 days ago'; +select interval '-9223372036854775808 microseconds ago'; +select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago'; + +-- test that INT_MIN number is formatted properly +SET IntervalStyle to postgres; +select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; +SET IntervalStyle to sql_standard; +select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; +SET IntervalStyle to iso_8601; +select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; +SET IntervalStyle to postgres_verbose; +select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; + -- check that '30 days' equals '1 month' according to the hash function select '30 days'::interval = '1 month'::interval as t; select interval_hash('30 days'::interval) = interval_hash('1 month'::interval) as t;
pgsql-hackers by date: