提交 63e0d612 编写于 作者: N Neil Conway

Adjust datetime parsing to be more robust. We now pass the length of the

working buffer into ParseDateTime() and reject too-long input there,
rather than checking the length of the input string before calling
ParseDateTime(). The old method was bogus because ParseDateTime() can use
a variable amount of working space, depending on the content of the
input string (e.g. how many fields need to be NUL terminated). This fixes
a minor stack overrun -- I don't _think_ it's exploitable, although I
won't claim to be an expert.

Along the way, fix a bug reported by Mark Dilger: the working buffer
allocated by interval_in() was too short, which resulted in rejecting
some perfectly valid interval input values. I added a regression test for
this fix.
上级 15e4d1e2
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.108 2005/05/24 02:09:45 momjian Exp $ * $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.109 2005/05/26 02:04:13 neilc Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -65,12 +65,10 @@ date_in(PG_FUNCTION_ARGS) ...@@ -65,12 +65,10 @@ date_in(PG_FUNCTION_ARGS)
int dterr; int dterr;
char *field[MAXDATEFIELDS]; char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + 1]; char workbuf[MAXDATELEN + 1];
if (strlen(str) >= sizeof(lowstr)) dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
dterr = DTERR_BAD_FORMAT; field, ftype, MAXDATEFIELDS, &nf);
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0) if (dterr == 0)
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp); dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
if (dterr != 0) if (dterr != 0)
...@@ -894,15 +892,13 @@ time_in(PG_FUNCTION_ARGS) ...@@ -894,15 +892,13 @@ time_in(PG_FUNCTION_ARGS)
int tz; int tz;
int nf; int nf;
int dterr; int dterr;
char lowstr[MAXDATELEN + 1]; char workbuf[MAXDATELEN + 1];
char *field[MAXDATEFIELDS]; char *field[MAXDATEFIELDS];
int dtype; int dtype;
int ftype[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr)) dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
dterr = DTERR_BAD_FORMAT; field, ftype, MAXDATEFIELDS, &nf);
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0) if (dterr == 0)
dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz); dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0) if (dterr != 0)
...@@ -1733,15 +1729,13 @@ timetz_in(PG_FUNCTION_ARGS) ...@@ -1733,15 +1729,13 @@ timetz_in(PG_FUNCTION_ARGS)
int tz; int tz;
int nf; int nf;
int dterr; int dterr;
char lowstr[MAXDATELEN + 1]; char workbuf[MAXDATELEN + 1];
char *field[MAXDATEFIELDS]; char *field[MAXDATEFIELDS];
int dtype; int dtype;
int ftype[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr)) dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
dterr = DTERR_BAD_FORMAT; field, ftype, MAXDATEFIELDS, &nf);
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0) if (dterr == 0)
dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz); dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0) if (dterr != 0)
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.144 2005/05/24 02:09:45 momjian Exp $ * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.145 2005/05/26 02:04:13 neilc Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -699,21 +699,23 @@ TrimTrailingZeros(char *str) ...@@ -699,21 +699,23 @@ TrimTrailingZeros(char *str)
} }
} }
/* ParseDateTime() /* ParseDateTime()
* Break string into tokens based on a date/time context. * Break string into tokens based on a date/time context.
* Returns 0 if successful, DTERR code if bogus input detected. * Returns 0 if successful, DTERR code if bogus input detected.
* *
* timestr - the input string * timestr - the input string
* lowstr - workspace for field string storage (must be large enough for * workbuf - workspace for field string storage. This must be
* a copy of the input string, including trailing null) * larger than the largest legal input for this datetime type --
* some additional space will be needed to NUL terminate fields.
* buflen - the size of workbuf
* field[] - pointers to field strings are returned in this array * field[] - pointers to field strings are returned in this array
* ftype[] - field type indicators are returned in this array * ftype[] - field type indicators are returned in this array
* maxfields - dimensions of the above two arrays * maxfields - dimensions of the above two arrays
* *numfields - set to the actual number of fields detected * *numfields - set to the actual number of fields detected
* *
* The fields extracted from the input are stored as separate, null-terminated * The fields extracted from the input are stored as separate,
* strings in the workspace at lowstr. Any text is converted to lower case. * null-terminated strings in the workspace at workbuf. Any text is
* converted to lower case.
* *
* Several field types are assigned: * Several field types are assigned:
* DTK_NUMBER - digits and (possibly) a decimal point * DTK_NUMBER - digits and (possibly) a decimal point
...@@ -729,12 +731,27 @@ TrimTrailingZeros(char *str) ...@@ -729,12 +731,27 @@ TrimTrailingZeros(char *str)
* DTK_DATE can hold Posix time zones (GMT-8) * DTK_DATE can hold Posix time zones (GMT-8)
*/ */
int int
ParseDateTime(const char *timestr, char *lowstr, ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
char **field, int *ftype, int maxfields, int *numfields) char **field, int *ftype, int maxfields, int *numfields)
{ {
int nf = 0; int nf = 0;
const char *cp = timestr; const char *cp = timestr;
char *lp = lowstr; char *bufp = workbuf;
const char *bufend = workbuf + buflen;
/*
* Set the character pointed-to by "bufptr" to "newchar", and
* increment "bufptr". "end" gives the end of the buffer -- we
* return an error if there is no space left to append a character
* to the buffer. Note that "bufptr" is evaluated twice.
*/
#define APPEND_CHAR(bufptr, end, newchar) \
do \
{ \
if (((bufptr) + 1) >= (end)) \
return DTERR_BAD_FORMAT; \
*(bufptr)++ = newchar; \
} while (0)
/* outer loop through fields */ /* outer loop through fields */
while (*cp != '\0') while (*cp != '\0')
...@@ -749,23 +766,23 @@ ParseDateTime(const char *timestr, char *lowstr, ...@@ -749,23 +766,23 @@ ParseDateTime(const char *timestr, char *lowstr,
/* Record start of current field */ /* Record start of current field */
if (nf >= maxfields) if (nf >= maxfields)
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
field[nf] = lp; field[nf] = bufp;
/* leading digit? then date or time */ /* leading digit? then date or time */
if (isdigit((unsigned char) *cp)) if (isdigit((unsigned char) *cp))
{ {
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
while (isdigit((unsigned char) *cp)) while (isdigit((unsigned char) *cp))
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
/* time field? */ /* time field? */
if (*cp == ':') if (*cp == ':')
{ {
ftype[nf] = DTK_TIME; ftype[nf] = DTK_TIME;
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
while (isdigit((unsigned char) *cp) || while (isdigit((unsigned char) *cp) ||
(*cp == ':') || (*cp == '.')) (*cp == ':') || (*cp == '.'))
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
} }
/* date field? allow embedded text month */ /* date field? allow embedded text month */
else if (*cp == '-' || *cp == '/' || *cp == '.') else if (*cp == '-' || *cp == '/' || *cp == '.')
...@@ -773,13 +790,13 @@ ParseDateTime(const char *timestr, char *lowstr, ...@@ -773,13 +790,13 @@ ParseDateTime(const char *timestr, char *lowstr,
/* save delimiting character to use later */ /* save delimiting character to use later */
char delim = *cp; char delim = *cp;
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
/* second field is all digits? then no embedded text month */ /* second field is all digits? then no embedded text month */
if (isdigit((unsigned char) *cp)) if (isdigit((unsigned char) *cp))
{ {
ftype[nf] = ((delim == '.') ? DTK_NUMBER : DTK_DATE); ftype[nf] = ((delim == '.') ? DTK_NUMBER : DTK_DATE);
while (isdigit((unsigned char) *cp)) while (isdigit((unsigned char) *cp))
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
/* /*
* insist that the delimiters match to get a * insist that the delimiters match to get a
...@@ -788,16 +805,16 @@ ParseDateTime(const char *timestr, char *lowstr, ...@@ -788,16 +805,16 @@ ParseDateTime(const char *timestr, char *lowstr,
if (*cp == delim) if (*cp == delim)
{ {
ftype[nf] = DTK_DATE; ftype[nf] = DTK_DATE;
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
while (isdigit((unsigned char) *cp) || *cp == delim) while (isdigit((unsigned char) *cp) || *cp == delim)
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
} }
} }
else else
{ {
ftype[nf] = DTK_DATE; ftype[nf] = DTK_DATE;
while (isalnum((unsigned char) *cp) || *cp == delim) while (isalnum((unsigned char) *cp) || *cp == delim)
*lp++ = pg_tolower((unsigned char) *cp++); APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
} }
} }
...@@ -811,9 +828,9 @@ ParseDateTime(const char *timestr, char *lowstr, ...@@ -811,9 +828,9 @@ ParseDateTime(const char *timestr, char *lowstr,
/* Leading decimal point? Then fractional seconds... */ /* Leading decimal point? Then fractional seconds... */
else if (*cp == '.') else if (*cp == '.')
{ {
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
while (isdigit((unsigned char) *cp)) while (isdigit((unsigned char) *cp))
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
ftype[nf] = DTK_NUMBER; ftype[nf] = DTK_NUMBER;
} }
...@@ -825,9 +842,9 @@ ParseDateTime(const char *timestr, char *lowstr, ...@@ -825,9 +842,9 @@ ParseDateTime(const char *timestr, char *lowstr,
else if (isalpha((unsigned char) *cp)) else if (isalpha((unsigned char) *cp))
{ {
ftype[nf] = DTK_STRING; ftype[nf] = DTK_STRING;
*lp++ = pg_tolower((unsigned char) *cp++); APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
while (isalpha((unsigned char) *cp)) while (isalpha((unsigned char) *cp))
*lp++ = pg_tolower((unsigned char) *cp++); APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
/* /*
* Full date string with leading text month? Could also be a * Full date string with leading text month? Could also be a
...@@ -838,15 +855,15 @@ ParseDateTime(const char *timestr, char *lowstr, ...@@ -838,15 +855,15 @@ ParseDateTime(const char *timestr, char *lowstr,
char delim = *cp; char delim = *cp;
ftype[nf] = DTK_DATE; ftype[nf] = DTK_DATE;
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
while (isdigit((unsigned char) *cp) || *cp == delim) while (isdigit((unsigned char) *cp) || *cp == delim)
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
} }
} }
/* sign? then special or numeric timezone */ /* sign? then special or numeric timezone */
else if (*cp == '+' || *cp == '-') else if (*cp == '+' || *cp == '-')
{ {
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
/* soak up leading whitespace */ /* soak up leading whitespace */
while (isspace((unsigned char) *cp)) while (isspace((unsigned char) *cp))
cp++; cp++;
...@@ -854,18 +871,18 @@ ParseDateTime(const char *timestr, char *lowstr, ...@@ -854,18 +871,18 @@ ParseDateTime(const char *timestr, char *lowstr,
if (isdigit((unsigned char) *cp)) if (isdigit((unsigned char) *cp))
{ {
ftype[nf] = DTK_TZ; ftype[nf] = DTK_TZ;
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
while (isdigit((unsigned char) *cp) || while (isdigit((unsigned char) *cp) ||
*cp == ':' || *cp == '.') *cp == ':' || *cp == '.')
*lp++ = *cp++; APPEND_CHAR(bufp, bufend, *cp++);
} }
/* special? */ /* special? */
else if (isalpha((unsigned char) *cp)) else if (isalpha((unsigned char) *cp))
{ {
ftype[nf] = DTK_SPECIAL; ftype[nf] = DTK_SPECIAL;
*lp++ = pg_tolower((unsigned char) *cp++); APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
while (isalpha((unsigned char) *cp)) while (isalpha((unsigned char) *cp))
*lp++ = pg_tolower((unsigned char) *cp++); APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
} }
/* otherwise something wrong... */ /* otherwise something wrong... */
else else
...@@ -882,7 +899,7 @@ ParseDateTime(const char *timestr, char *lowstr, ...@@ -882,7 +899,7 @@ ParseDateTime(const char *timestr, char *lowstr,
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
/* force in a delimiter after each field */ /* force in a delimiter after each field */
*lp++ = '\0'; *bufp++ = '\0';
nf++; nf++;
} }
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.131 2005/05/24 02:09:45 momjian Exp $ * $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.132 2005/05/26 02:04:13 neilc Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -306,15 +306,13 @@ abstimein(PG_FUNCTION_ARGS) ...@@ -306,15 +306,13 @@ abstimein(PG_FUNCTION_ARGS)
*tm = &date; *tm = &date;
int dterr; int dterr;
char *field[MAXDATEFIELDS]; char *field[MAXDATEFIELDS];
char lowstr[MAXDATELEN + 1]; char workbuf[MAXDATELEN + 1];
int dtype; int dtype;
int nf, int nf,
ftype[MAXDATEFIELDS]; ftype[MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr)) dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
dterr = DTERR_BAD_FORMAT; field, ftype, MAXDATEFIELDS, &nf);
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0) if (dterr == 0)
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz); dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0) if (dterr != 0)
...@@ -711,12 +709,10 @@ reltimein(PG_FUNCTION_ARGS) ...@@ -711,12 +709,10 @@ reltimein(PG_FUNCTION_ARGS)
char *field[MAXDATEFIELDS]; char *field[MAXDATEFIELDS];
int nf, int nf,
ftype[MAXDATEFIELDS]; ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + 1]; char workbuf[MAXDATELEN + 1];
if (strlen(str) >= sizeof(lowstr)) dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
dterr = DTERR_BAD_FORMAT; field, ftype, MAXDATEFIELDS, &nf);
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0) if (dterr == 0)
dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec); dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
if (dterr != 0) if (dterr != 0)
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.123 2005/05/24 02:09:45 momjian Exp $ * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.124 2005/05/26 02:04:13 neilc Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -77,12 +77,10 @@ timestamp_in(PG_FUNCTION_ARGS) ...@@ -77,12 +77,10 @@ timestamp_in(PG_FUNCTION_ARGS)
int dterr; int dterr;
char *field[MAXDATEFIELDS]; char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + MAXDATEFIELDS]; char workbuf[MAXDATELEN + MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr)) dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
dterr = DTERR_BAD_FORMAT; field, ftype, MAXDATEFIELDS, &nf);
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0) if (dterr == 0)
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz); dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0) if (dterr != 0)
...@@ -317,12 +315,10 @@ timestamptz_in(PG_FUNCTION_ARGS) ...@@ -317,12 +315,10 @@ timestamptz_in(PG_FUNCTION_ARGS)
int dterr; int dterr;
char *field[MAXDATEFIELDS]; char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + MAXDATEFIELDS]; char workbuf[MAXDATELEN + MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr)) dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
dterr = DTERR_BAD_FORMAT; field, ftype, MAXDATEFIELDS, &nf);
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0) if (dterr == 0)
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz); dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0) if (dterr != 0)
...@@ -493,7 +489,7 @@ interval_in(PG_FUNCTION_ARGS) ...@@ -493,7 +489,7 @@ interval_in(PG_FUNCTION_ARGS)
int dterr; int dterr;
char *field[MAXDATEFIELDS]; char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + MAXDATEFIELDS]; char workbuf[256];
tm->tm_year = 0; tm->tm_year = 0;
tm->tm_mon = 0; tm->tm_mon = 0;
...@@ -503,10 +499,8 @@ interval_in(PG_FUNCTION_ARGS) ...@@ -503,10 +499,8 @@ interval_in(PG_FUNCTION_ARGS)
tm->tm_sec = 0; tm->tm_sec = 0;
fsec = 0; fsec = 0;
if (strlen(str) >= sizeof(lowstr)) dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field,
dterr = DTERR_BAD_FORMAT; ftype, MAXDATEFIELDS, &nf);
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0) if (dterr == 0)
dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec); dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
if (dterr != 0) if (dterr != 0)
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.53 2005/05/24 04:03:01 momjian Exp $ * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.54 2005/05/26 02:04:14 neilc Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -276,7 +276,7 @@ extern void GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp); ...@@ -276,7 +276,7 @@ extern void GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp);
extern void j2date(int jd, int *year, int *month, int *day); extern void j2date(int jd, int *year, int *month, int *day);
extern int date2j(int year, int month, int day); extern int date2j(int year, int month, int day);
extern int ParseDateTime(const char *timestr, char *lowstr, extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
char **field, int *ftype, char **field, int *ftype,
int maxfields, int *numfields); int maxfields, int *numfields);
extern int DecodeDateTime(char **field, int *ftype, extern int DecodeDateTime(char **field, int *ftype,
......
...@@ -221,3 +221,10 @@ select avg(f1) from interval_tbl; ...@@ -221,3 +221,10 @@ select avg(f1) from interval_tbl;
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs @ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
(1 row) (1 row)
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
interval
--------------------------------------------
@ 4541 years 4 mons 4 days 17 mins 31 secs
(1 row)
...@@ -66,3 +66,6 @@ SELECT '' AS ten, * FROM INTERVAL_TBL; ...@@ -66,3 +66,6 @@ SELECT '' AS ten, * FROM INTERVAL_TBL;
-- updating pg_aggregate.agginitval -- updating pg_aggregate.agginitval
select avg(f1) from interval_tbl; select avg(f1) from interval_tbl;
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册