Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit f867339

Browse files
committed
Make our parsing of INTERVAL literals spec-compliant (or at least a heck of
a lot closer than it was before). To do this, tweak coerce_type() to pass through the typmod information when invoking interval_in() on an UNKNOWN constant; then fix DecodeInterval to pay attention to the typmod when deciding how to interpret a units-less integer value. I changed one or two other details as well. I believe the code now reacts as expected by spec for all the literal syntaxes that are specifically enumerated in the spec. There are corner cases involving strings that don't exactly match the set of fields called out by the typmod, for which we might want to tweak the behavior some more; but I think this is an area of user friendliness rather than spec compliance. There remain some non-compliant details about the SQL syntax (as opposed to what's inside the literal string); but at least we'll throw error rather than silently doing the wrong thing in those cases.
1 parent 3b9ec46 commit f867339

File tree

7 files changed

+296
-30
lines changed

7 files changed

+296
-30
lines changed

src/backend/parser/parse_coerce.c

+22-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.166 2008/09/01 20:42:44 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.167 2008/09/10 18:29:40 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -179,6 +179,7 @@ coerce_type(ParseState *pstate, Node *node,
179179
Const *newcon = makeNode(Const);
180180
Oid baseTypeId;
181181
int32 baseTypeMod;
182+
int32 inputTypeMod;
182183
Type targetType;
183184
ParseCallbackState pcbstate;
184185

@@ -190,13 +191,27 @@ coerce_type(ParseState *pstate, Node *node,
190191
* what we want here. The needed check will be applied properly
191192
* inside coerce_to_domain().
192193
*/
193-
baseTypeMod = -1;
194+
baseTypeMod = targetTypeMod;
194195
baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod);
195196

197+
/*
198+
* For most types we pass typmod -1 to the input routine, because
199+
* existing input routines follow implicit-coercion semantics for
200+
* length checks, which is not always what we want here. Any length
201+
* constraint will be applied later by our caller. An exception
202+
* however is the INTERVAL type, for which we *must* pass the typmod
203+
* or it won't be able to obey the bizarre SQL-spec input rules.
204+
* (Ugly as sin, but so is this part of the spec...)
205+
*/
206+
if (baseTypeId == INTERVALOID)
207+
inputTypeMod = baseTypeMod;
208+
else
209+
inputTypeMod = -1;
210+
196211
targetType = typeidType(baseTypeId);
197212

198213
newcon->consttype = baseTypeId;
199-
newcon->consttypmod = -1;
214+
newcon->consttypmod = inputTypeMod;
200215
newcon->constlen = typeLen(targetType);
201216
newcon->constbyval = typeByVal(targetType);
202217
newcon->constisnull = con->constisnull;
@@ -215,20 +230,17 @@ coerce_type(ParseState *pstate, Node *node,
215230
setup_parser_errposition_callback(&pcbstate, pstate, con->location);
216231

217232
/*
218-
* We pass typmod -1 to the input routine, primarily because existing
219-
* input routines follow implicit-coercion semantics for length
220-
* checks, which is not always what we want here. Any length
221-
* constraint will be applied later by our caller.
222-
*
223233
* We assume here that UNKNOWN's internal representation is the same
224234
* as CSTRING.
225235
*/
226236
if (!con->constisnull)
227237
newcon->constvalue = stringTypeDatum(targetType,
228238
DatumGetCString(con->constvalue),
229-
-1);
239+
inputTypeMod);
230240
else
231-
newcon->constvalue = stringTypeDatum(targetType, NULL, -1);
241+
newcon->constvalue = stringTypeDatum(targetType,
242+
NULL,
243+
inputTypeMod);
232244

233245
cancel_parser_errposition_callback(&pcbstate);
234246

src/backend/utils/adt/datetime.c

+73-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.190 2008/06/09 19:34:02 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.191 2008/09/10 18:29:41 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -35,8 +35,8 @@ static int DecodeNumber(int flen, char *field, bool haveTextMonth,
3535
static int DecodeNumberField(int len, char *str,
3636
int fmask, int *tmask,
3737
struct pg_tm * tm, fsec_t *fsec, bool *is2digits);
38-
static int DecodeTime(char *str, int fmask, int *tmask,
39-
struct pg_tm * tm, fsec_t *fsec);
38+
static int DecodeTime(char *str, int fmask, int range,
39+
int *tmask, struct pg_tm * tm, fsec_t *fsec);
4040
static int DecodeTimezone(char *str, int *tzp);
4141
static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
4242
static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
@@ -832,7 +832,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
832832
break;
833833

834834
case DTK_TIME:
835-
dterr = DecodeTime(field[i], fmask, &tmask, tm, fsec);
835+
dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE,
836+
&tmask, tm, fsec);
836837
if (dterr)
837838
return dterr;
838839

@@ -1563,6 +1564,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
15631564

15641565
case DTK_TIME:
15651566
dterr = DecodeTime(field[i], (fmask | DTK_DATE_M),
1567+
INTERVAL_FULL_RANGE,
15661568
&tmask, tm, fsec);
15671569
if (dterr)
15681570
return dterr;
@@ -2224,7 +2226,8 @@ ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm)
22242226
* used to represent time spans.
22252227
*/
22262228
static int
2227-
DecodeTime(char *str, int fmask, int *tmask, struct pg_tm * tm, fsec_t *fsec)
2229+
DecodeTime(char *str, int fmask, int range,
2230+
int *tmask, struct pg_tm * tm, fsec_t *fsec)
22282231
{
22292232
char *cp;
22302233

@@ -2245,6 +2248,13 @@ DecodeTime(char *str, int fmask, int *tmask, struct pg_tm * tm, fsec_t *fsec)
22452248
{
22462249
tm->tm_sec = 0;
22472250
*fsec = 0;
2251+
/* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
2252+
if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
2253+
{
2254+
tm->tm_sec = tm->tm_min;
2255+
tm->tm_min = tm->tm_hour;
2256+
tm->tm_hour = 0;
2257+
}
22482258
}
22492259
else if (*cp != ':')
22502260
return DTERR_BAD_FORMAT;
@@ -2705,7 +2715,8 @@ DecodeSpecial(int field, char *lowtoken, int *val)
27052715
* preceding an hh:mm:ss field. - thomas 1998-04-30
27062716
*/
27072717
int
2708-
DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm, fsec_t *fsec)
2718+
DecodeInterval(char **field, int *ftype, int nf, int range,
2719+
int *dtype, struct pg_tm * tm, fsec_t *fsec)
27092720
{
27102721
bool is_before = FALSE;
27112722
char *cp;
@@ -2734,7 +2745,8 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm,
27342745
switch (ftype[i])
27352746
{
27362747
case DTK_TIME:
2737-
dterr = DecodeTime(field[i], fmask, &tmask, tm, fsec);
2748+
dterr = DecodeTime(field[i], fmask, range,
2749+
&tmask, tm, fsec);
27382750
if (dterr)
27392751
return dterr;
27402752
type = DTK_DAY;
@@ -2757,7 +2769,8 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm,
27572769
while (*cp != '\0' && *cp != ':' && *cp != '.')
27582770
cp++;
27592771
if (*cp == ':' &&
2760-
DecodeTime(field[i] + 1, fmask, &tmask, tm, fsec) == 0)
2772+
DecodeTime(field[i] + 1, fmask, INTERVAL_FULL_RANGE,
2773+
&tmask, tm, fsec) == 0)
27612774
{
27622775
if (*field[i] == '-')
27632776
{
@@ -2796,19 +2809,66 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm,
27962809
type = DTK_HOUR;
27972810
}
27982811
}
2799-
/* DROP THROUGH */
2812+
/* FALL THROUGH */
28002813

28012814
case DTK_DATE:
28022815
case DTK_NUMBER:
2816+
if (type == IGNORE_DTF)
2817+
{
2818+
/* use typmod to decide what rightmost integer field is */
2819+
switch (range)
2820+
{
2821+
case INTERVAL_MASK(YEAR):
2822+
type = DTK_YEAR;
2823+
break;
2824+
case INTERVAL_MASK(MONTH):
2825+
case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
2826+
type = DTK_MONTH;
2827+
break;
2828+
case INTERVAL_MASK(DAY):
2829+
type = DTK_DAY;
2830+
break;
2831+
case INTERVAL_MASK(HOUR):
2832+
case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
2833+
case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
2834+
case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
2835+
type = DTK_HOUR;
2836+
break;
2837+
case INTERVAL_MASK(MINUTE):
2838+
case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
2839+
type = DTK_MINUTE;
2840+
break;
2841+
case INTERVAL_MASK(SECOND):
2842+
case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
2843+
case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
2844+
type = DTK_SECOND;
2845+
break;
2846+
default:
2847+
type = DTK_SECOND;
2848+
break;
2849+
}
2850+
}
2851+
28032852
errno = 0;
28042853
val = strtoi(field[i], &cp, 10);
28052854
if (errno == ERANGE)
28062855
return DTERR_FIELD_OVERFLOW;
28072856

2808-
if (type == IGNORE_DTF)
2809-
type = DTK_SECOND;
2857+
if (*cp == '-')
2858+
{
2859+
/* SQL "years-months" syntax */
2860+
int val2;
28102861

2811-
if (*cp == '.')
2862+
val2 = strtoi(cp + 1, &cp, 10);
2863+
if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR)
2864+
return DTERR_FIELD_OVERFLOW;
2865+
if (*cp != '\0')
2866+
return DTERR_BAD_FORMAT;
2867+
type = DTK_MONTH;
2868+
val = val * MONTHS_PER_YEAR + val2;
2869+
fval = 0;
2870+
}
2871+
else if (*cp == '.')
28122872
{
28132873
fval = strtod(cp, &cp);
28142874
if (*cp != '\0')
@@ -2896,6 +2956,7 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm,
28962956
#endif
28972957
}
28982958
tmask = DTK_M(HOUR);
2959+
type = DTK_DAY;
28992960
break;
29002961

29012962
case DTK_DAY:

src/backend/utils/adt/nabstime.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*
1111
*
1212
* IDENTIFICATION
13-
* $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.155 2008/03/25 22:42:44 tgl Exp $
13+
* $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.156 2008/09/10 18:29:41 tgl Exp $
1414
*
1515
*-------------------------------------------------------------------------
1616
*/
@@ -632,7 +632,8 @@ reltimein(PG_FUNCTION_ARGS)
632632
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
633633
field, ftype, MAXDATEFIELDS, &nf);
634634
if (dterr == 0)
635-
dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
635+
dterr = DecodeInterval(field, ftype, nf, INTERVAL_FULL_RANGE,
636+
&dtype, tm, &fsec);
636637
if (dterr != 0)
637638
{
638639
if (dterr == DTERR_FIELD_OVERFLOW)

src/backend/utils/adt/timestamp.c

+9-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.190 2008/07/07 18:09:46 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.191 2008/09/10 18:29:41 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -604,6 +604,7 @@ interval_in(PG_FUNCTION_ARGS)
604604
*tm = &tt;
605605
int dtype;
606606
int nf;
607+
int range;
607608
int dterr;
608609
char *field[MAXDATEFIELDS];
609610
int ftype[MAXDATEFIELDS];
@@ -617,10 +618,15 @@ interval_in(PG_FUNCTION_ARGS)
617618
tm->tm_sec = 0;
618619
fsec = 0;
619620

621+
if (typmod >= 0)
622+
range = INTERVAL_RANGE(typmod);
623+
else
624+
range = INTERVAL_FULL_RANGE;
625+
620626
dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field,
621627
ftype, MAXDATEFIELDS, &nf);
622628
if (dterr == 0)
623-
dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
629+
dterr = DecodeInterval(field, ftype, nf, range, &dtype, tm, &fsec);
624630
if (dterr != 0)
625631
{
626632
if (dterr == DTERR_FIELD_OVERFLOW)
@@ -945,7 +951,7 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod)
945951
* Unspecified range and precision? Then not necessary to adjust. Setting
946952
* typmod to -1 is the convention for all types.
947953
*/
948-
if (typmod != -1)
954+
if (typmod >= 0)
949955
{
950956
int range = INTERVAL_RANGE(typmod);
951957
int precision = INTERVAL_PRECISION(typmod);

src/include/utils/datetime.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
1010
* Portions Copyright (c) 1994, Regents of the University of California
1111
*
12-
* $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.69 2008/01/01 19:45:59 momjian Exp $
12+
* $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.70 2008/09/10 18:29:41 tgl Exp $
1313
*
1414
*-------------------------------------------------------------------------
1515
*/
@@ -290,7 +290,7 @@ extern int DecodeTimeOnly(char **field, int *ftype,
290290
int nf, int *dtype,
291291
struct pg_tm * tm, fsec_t *fsec, int *tzp);
292292
extern int DecodeInterval(char **field, int *ftype,
293-
int nf, int *dtype,
293+
int nf, int range, int *dtype,
294294
struct pg_tm * tm, fsec_t *fsec);
295295
extern void DateTimeParseError(int dterr, const char *str,
296296
const char *datatype);

0 commit comments

Comments
 (0)