diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index efe45974c35..86e47661aa0 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.166 2008/09/01 20:42:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.167 2008/09/10 18:29:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -179,6 +179,7 @@ coerce_type(ParseState *pstate, Node *node, Const *newcon = makeNode(Const); Oid baseTypeId; int32 baseTypeMod; + int32 inputTypeMod; Type targetType; ParseCallbackState pcbstate; @@ -190,13 +191,27 @@ coerce_type(ParseState *pstate, Node *node, * what we want here. The needed check will be applied properly * inside coerce_to_domain(). */ - baseTypeMod = -1; + baseTypeMod = targetTypeMod; baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod); + /* + * For most types we pass typmod -1 to the input routine, because + * existing input routines follow implicit-coercion semantics for + * length checks, which is not always what we want here. Any length + * constraint will be applied later by our caller. An exception + * however is the INTERVAL type, for which we *must* pass the typmod + * or it won't be able to obey the bizarre SQL-spec input rules. + * (Ugly as sin, but so is this part of the spec...) + */ + if (baseTypeId == INTERVALOID) + inputTypeMod = baseTypeMod; + else + inputTypeMod = -1; + targetType = typeidType(baseTypeId); newcon->consttype = baseTypeId; - newcon->consttypmod = -1; + newcon->consttypmod = inputTypeMod; newcon->constlen = typeLen(targetType); newcon->constbyval = typeByVal(targetType); newcon->constisnull = con->constisnull; @@ -215,20 +230,17 @@ coerce_type(ParseState *pstate, Node *node, setup_parser_errposition_callback(&pcbstate, pstate, con->location); /* - * We pass typmod -1 to the input routine, primarily because existing - * input routines follow implicit-coercion semantics for length - * checks, which is not always what we want here. Any length - * constraint will be applied later by our caller. - * * We assume here that UNKNOWN's internal representation is the same * as CSTRING. */ if (!con->constisnull) newcon->constvalue = stringTypeDatum(targetType, DatumGetCString(con->constvalue), - -1); + inputTypeMod); else - newcon->constvalue = stringTypeDatum(targetType, NULL, -1); + newcon->constvalue = stringTypeDatum(targetType, + NULL, + inputTypeMod); cancel_parser_errposition_callback(&pcbstate); diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index a92583ca19e..6712d6d8d44 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.190 2008/06/09 19:34:02 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.191 2008/09/10 18:29:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,8 +35,8 @@ 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 DecodeTime(char *str, int fmask, int *tmask, - struct pg_tm * tm, fsec_t *fsec); +static int DecodeTime(char *str, int fmask, int range, + int *tmask, struct pg_tm * tm, fsec_t *fsec); static int DecodeTimezone(char *str, int *tzp); static const datetkn *datebsearch(const char *key, const datetkn *base, int nel); static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits, @@ -832,7 +832,8 @@ DecodeDateTime(char **field, int *ftype, int nf, break; case DTK_TIME: - dterr = DecodeTime(field[i], fmask, &tmask, tm, fsec); + dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE, + &tmask, tm, fsec); if (dterr) return dterr; @@ -1563,6 +1564,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf, case DTK_TIME: dterr = DecodeTime(field[i], (fmask | DTK_DATE_M), + INTERVAL_FULL_RANGE, &tmask, tm, fsec); if (dterr) return dterr; @@ -2224,7 +2226,8 @@ ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm) * used to represent time spans. */ static int -DecodeTime(char *str, int fmask, int *tmask, struct pg_tm * tm, fsec_t *fsec) +DecodeTime(char *str, int fmask, int range, + int *tmask, struct pg_tm * tm, fsec_t *fsec) { char *cp; @@ -2245,6 +2248,13 @@ DecodeTime(char *str, int fmask, int *tmask, struct pg_tm * tm, fsec_t *fsec) { tm->tm_sec = 0; *fsec = 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; + } } else if (*cp != ':') return DTERR_BAD_FORMAT; @@ -2705,7 +2715,8 @@ DecodeSpecial(int field, char *lowtoken, int *val) * preceding an hh:mm:ss field. - thomas 1998-04-30 */ int -DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm, fsec_t *fsec) +DecodeInterval(char **field, int *ftype, int nf, int range, + int *dtype, struct pg_tm * tm, fsec_t *fsec) { bool is_before = FALSE; char *cp; @@ -2734,7 +2745,8 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm, switch (ftype[i]) { case DTK_TIME: - dterr = DecodeTime(field[i], fmask, &tmask, tm, fsec); + dterr = DecodeTime(field[i], fmask, range, + &tmask, tm, fsec); if (dterr) return dterr; type = DTK_DAY; @@ -2757,7 +2769,8 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm, while (*cp != '\0' && *cp != ':' && *cp != '.') cp++; if (*cp == ':' && - DecodeTime(field[i] + 1, fmask, &tmask, tm, fsec) == 0) + DecodeTime(field[i] + 1, fmask, INTERVAL_FULL_RANGE, + &tmask, tm, fsec) == 0) { if (*field[i] == '-') { @@ -2796,19 +2809,66 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm, type = DTK_HOUR; } } - /* DROP THROUGH */ + /* FALL THROUGH */ case DTK_DATE: case DTK_NUMBER: + if (type == IGNORE_DTF) + { + /* use typmod to decide what rightmost integer field is */ + switch (range) + { + case INTERVAL_MASK(YEAR): + type = DTK_YEAR; + break; + case INTERVAL_MASK(MONTH): + case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH): + type = DTK_MONTH; + break; + case INTERVAL_MASK(DAY): + type = DTK_DAY; + break; + case INTERVAL_MASK(HOUR): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + type = DTK_HOUR; + break; + case INTERVAL_MASK(MINUTE): + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + type = DTK_MINUTE; + break; + case INTERVAL_MASK(SECOND): + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + type = DTK_SECOND; + break; + default: + type = DTK_SECOND; + break; + } + } + errno = 0; val = strtoi(field[i], &cp, 10); if (errno == ERANGE) return DTERR_FIELD_OVERFLOW; - if (type == IGNORE_DTF) - type = DTK_SECOND; + if (*cp == '-') + { + /* SQL "years-months" syntax */ + int val2; - if (*cp == '.') + val2 = strtoi(cp + 1, &cp, 10); + if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR) + return DTERR_FIELD_OVERFLOW; + if (*cp != '\0') + return DTERR_BAD_FORMAT; + type = DTK_MONTH; + val = val * MONTHS_PER_YEAR + val2; + fval = 0; + } + else if (*cp == '.') { fval = strtod(cp, &cp); if (*cp != '\0') @@ -2896,6 +2956,7 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm, #endif } tmask = DTK_M(HOUR); + type = DTK_DAY; break; case DTK_DAY: diff --git a/src/backend/utils/adt/nabstime.c b/src/backend/utils/adt/nabstime.c index a40ca5edd05..4a505c341e9 100644 --- a/src/backend/utils/adt/nabstime.c +++ b/src/backend/utils/adt/nabstime.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.155 2008/03/25 22:42:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.156 2008/09/10 18:29:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -632,7 +632,8 @@ reltimein(PG_FUNCTION_ARGS) dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) - dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec); + dterr = DecodeInterval(field, ftype, nf, INTERVAL_FULL_RANGE, + &dtype, tm, &fsec); if (dterr != 0) { if (dterr == DTERR_FIELD_OVERFLOW) diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index d6a5dee0842..9060b989f9c 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.190 2008/07/07 18:09:46 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.191 2008/09/10 18:29:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -604,6 +604,7 @@ interval_in(PG_FUNCTION_ARGS) *tm = &tt; int dtype; int nf; + int range; int dterr; char *field[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS]; @@ -617,10 +618,15 @@ interval_in(PG_FUNCTION_ARGS) tm->tm_sec = 0; fsec = 0; + if (typmod >= 0) + range = INTERVAL_RANGE(typmod); + else + range = INTERVAL_FULL_RANGE; + dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) - dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec); + dterr = DecodeInterval(field, ftype, nf, range, &dtype, tm, &fsec); if (dterr != 0) { if (dterr == DTERR_FIELD_OVERFLOW) @@ -945,7 +951,7 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod) * Unspecified range and precision? Then not necessary to adjust. Setting * typmod to -1 is the convention for all types. */ - if (typmod != -1) + if (typmod >= 0) { int range = INTERVAL_RANGE(typmod); int precision = INTERVAL_PRECISION(typmod); diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index ffb4c45240f..439e9779d20 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.69 2008/01/01 19:45:59 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.70 2008/09/10 18:29:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -290,7 +290,7 @@ 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 *dtype, + int nf, int range, int *dtype, struct pg_tm * tm, fsec_t *fsec); extern void DateTimeParseError(int dterr, const char *str, const char *datatype); diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 049850af8eb..ede3c58708c 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -361,3 +361,160 @@ SELECT '1:20:05 5 microseconds'::interval; -- error ERROR: invalid input syntax for type interval: "1:20:05 5 microseconds" LINE 1: SELECT '1:20:05 5 microseconds'::interval; ^ +SELECT interval '1-2'; -- SQL year-month literal + interval +--------------- + 1 year 2 mons +(1 row) + +-- test SQL-spec syntaxes for restricted field sets +SELECT interval '1' year; + interval +---------- + 1 year +(1 row) + +SELECT interval '2' month; + interval +---------- + 2 mons +(1 row) + +SELECT interval '3' day; + interval +---------- + 3 days +(1 row) + +SELECT interval '4' hour; + interval +---------- + 04:00:00 +(1 row) + +SELECT interval '5' minute; + interval +---------- + 00:05:00 +(1 row) + +SELECT interval '6' second; + interval +---------- + 00:00:06 +(1 row) + +SELECT interval '1' year to month; + interval +---------- + 1 mon +(1 row) + +SELECT interval '1-2' year to month; + interval +--------------- + 1 year 2 mons +(1 row) + +SELECT interval '1 2' day to hour; + interval +---------------- + 1 day 02:00:00 +(1 row) + +SELECT interval '1 2:03' day to hour; + interval +---------------- + 1 day 02:00:00 +(1 row) + +SELECT interval '1 2:03:04' day to hour; + interval +---------------- + 1 day 02:00:00 +(1 row) + +SELECT interval '1 2' day to minute; + interval +---------------- + 1 day 02:00:00 +(1 row) + +SELECT interval '1 2:03' day to minute; + interval +---------------- + 1 day 02:03:00 +(1 row) + +SELECT interval '1 2:03:04' day to minute; + interval +---------------- + 1 day 02:03:00 +(1 row) + +SELECT interval '1 2' day to second; + interval +---------------- + 1 day 02:00:00 +(1 row) + +SELECT interval '1 2:03' day to second; + interval +---------------- + 1 day 02:03:00 +(1 row) + +SELECT interval '1 2:03:04' day to second; + interval +---------------- + 1 day 02:03:04 +(1 row) + +SELECT interval '1 2' hour to minute; +ERROR: invalid input syntax for type interval: "1 2" +LINE 1: SELECT interval '1 2' hour to minute; + ^ +SELECT interval '1 2:03' hour to minute; + interval +---------- + 02:03:00 +(1 row) + +SELECT interval '1 2:03:04' hour to minute; + interval +---------- + 02:03:00 +(1 row) + +SELECT interval '1 2' hour to second; +ERROR: invalid input syntax for type interval: "1 2" +LINE 1: SELECT interval '1 2' hour to second; + ^ +SELECT interval '1 2:03' hour to second; + interval +---------- + 02:03:00 +(1 row) + +SELECT interval '1 2:03:04' hour to second; + interval +---------- + 02:03:04 +(1 row) + +SELECT interval '1 2' minute to second; +ERROR: invalid input syntax for type interval: "1 2" +LINE 1: SELECT interval '1 2' minute to second; + ^ +SELECT interval '1 2:03' minute to second; + interval +---------- + 00:02:03 +(1 row) + +SELECT interval '1 2:03:04' minute to second; + interval +---------- + 00:03:04 +(1 row) + diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index d081bf1ffed..2c6aecaa519 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -126,4 +126,33 @@ SELECT '3 days 5 milliseconds'::interval; SELECT '1 second 2 seconds'::interval; -- error SELECT '10 milliseconds 20 milliseconds'::interval; -- error SELECT '5.5 seconds 3 milliseconds'::interval; -- error -SELECT '1:20:05 5 microseconds'::interval; -- error \ No newline at end of file +SELECT '1:20:05 5 microseconds'::interval; -- error +SELECT interval '1-2'; -- SQL year-month literal + +-- test SQL-spec syntaxes for restricted field sets +SELECT interval '1' year; +SELECT interval '2' month; +SELECT interval '3' day; +SELECT interval '4' hour; +SELECT interval '5' minute; +SELECT interval '6' second; +SELECT interval '1' year to month; +SELECT interval '1-2' year to month; +SELECT interval '1 2' day to hour; +SELECT interval '1 2:03' day to hour; +SELECT interval '1 2:03:04' day to hour; +SELECT interval '1 2' day to minute; +SELECT interval '1 2:03' day to minute; +SELECT interval '1 2:03:04' day to minute; +SELECT interval '1 2' day to second; +SELECT interval '1 2:03' day to second; +SELECT interval '1 2:03:04' day to second; +SELECT interval '1 2' hour to minute; +SELECT interval '1 2:03' hour to minute; +SELECT interval '1 2:03:04' hour to minute; +SELECT interval '1 2' hour to second; +SELECT interval '1 2:03' hour to second; +SELECT interval '1 2:03:04' hour to second; +SELECT interval '1 2' minute to second; +SELECT interval '1 2:03' minute to second; +SELECT interval '1 2:03:04' minute to second;