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

Commit 70530c8

Browse files
committed
Adjust the parser to accept the typename syntax INTERVAL ... SECOND(n)
and the literal syntax INTERVAL 'string' ... SECOND(n), as required by the SQL standard. Our old syntax put (n) directly after INTERVAL, which was a mistake, but will still be accepted for backward compatibility as well as symmetry with the TIMESTAMP cases. Change intervaltypmodout to show it in the spec's way, too. (This could potentially affect clients, if there are any that analyze the typmod of an INTERVAL in any detail.) Also fix interval input to handle 'min:sec.frac' properly; I had overlooked this case in my previous patch. Document the use of the interval fields qualifier, which up to now we had never mentioned in the docs. (I think the omission was intentional because it didn't work per spec; but it does now, or at least close enough to be credible.)
1 parent d53a566 commit 70530c8

File tree

6 files changed

+294
-56
lines changed

6 files changed

+294
-56
lines changed

doc/src/sgml/datatype.sgml

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.227 2008/05/16 16:31:01 tgl Exp $ -->
1+
<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.228 2008/09/11 15:27:30 tgl Exp $ -->
22

33
<chapter id="datatype">
44
<title id="datatype-title">Data Types</title>
@@ -131,7 +131,7 @@
131131
</row>
132132

133133
<row>
134-
<entry><type>interval [ (<replaceable>p</replaceable>) ]</type></entry>
134+
<entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry>
135135
<entry></entry>
136136
<entry>time span</entry>
137137
</row>
@@ -1420,7 +1420,7 @@ SELECT b, char_length(b) FROM test2;
14201420
<entry>1 microsecond / 14 digits</entry>
14211421
</row>
14221422
<row>
1423-
<entry><type>interval [ (<replaceable>p</replaceable>) ]</type></entry>
1423+
<entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry>
14241424
<entry>12 bytes</entry>
14251425
<entry>time intervals</entry>
14261426
<entry>-178000000 years</entry>
@@ -1505,6 +1505,30 @@ SELECT b, char_length(b) FROM test2;
15051505
storage is used, or from 0 to 10 when floating-point storage is used.
15061506
</para>
15071507

1508+
<para>
1509+
The <type>interval</type> type has an additional option, which is
1510+
to restrict the set of stored fields by writing one of these phrases:
1511+
<programlisting>
1512+
YEAR
1513+
MONTH
1514+
DAY
1515+
HOUR
1516+
MINUTE
1517+
SECOND
1518+
YEAR TO MONTH
1519+
DAY TO HOUR
1520+
DAY TO MINUTE
1521+
DAY TO SECOND
1522+
HOUR TO MINUTE
1523+
MINUTE TO SECOND
1524+
</programlisting>
1525+
Input falling outside the specified set of fields is silently discarded.
1526+
Note that if both <replaceable>fields</replaceable> and
1527+
<replaceable>precision</replaceable> are specified, the
1528+
<replaceable>fields</replaceable> must include <literal>SECOND</>,
1529+
since the precision applies only to the seconds.
1530+
</para>
1531+
15081532
<para>
15091533
The type <type>time with time zone</type> is defined by the SQL
15101534
standard, but the definition exhibits properties which lead to
@@ -1928,18 +1952,26 @@ January 8 04:05:06 1999 PST
19281952
<replaceable>direction</> can be <literal>ago</literal> or
19291953
empty. The at sign (<literal>@</>) is optional noise. The amounts
19301954
of different units are implicitly added up with appropriate
1931-
sign accounting.
1955+
sign accounting. <literal>ago</literal> negates all the fields.
19321956
</para>
19331957

19341958
<para>
19351959
Quantities of days, hours, minutes, and seconds can be specified without
19361960
explicit unit markings. For example, <literal>'1 12:59:10'</> is read
1937-
the same as <literal>'1 day 12 hours 59 min 10 sec'</>.
1961+
the same as <literal>'1 day 12 hours 59 min 10 sec'</>. Also,
1962+
a combination of years and months can be specified with a dash;
1963+
for example <literal>'200-10'</> is read the same as <literal>'200 years
1964+
10 months'</>. (These shorter forms are in fact the only ones allowed
1965+
by the SQL standard.)
19381966
</para>
19391967

19401968
<para>
1941-
The optional subsecond precision <replaceable>p</replaceable> should
1942-
be between 0 and 6, and defaults to the precision of the input literal.
1969+
When writing an interval constant with a <replaceable>fields</>
1970+
specification, or when assigning to an interval column that was defined
1971+
with a <replaceable>fields</> specification, the interpretation of
1972+
unmarked quantities depends on the <replaceable>fields</>. For
1973+
example <literal>INTERVAL '1' YEAR</> is read as 1 year, whereas
1974+
<literal>INTERVAL '1'</> means 1 second.
19431975
</para>
19441976

19451977
<para>

src/backend/parser/gram.y

Lines changed: 114 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*
1212
*
1313
* IDENTIFICATION
14-
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.622 2008/09/02 20:37:54 tgl Exp $
14+
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.623 2008/09/11 15:27:30 tgl Exp $
1515
*
1616
* HISTORY
1717
* AUTHOR DATE MAJOR EVENT
@@ -292,7 +292,7 @@ static TypeName *TableFuncTypeName(List *columns);
292292

293293
%type <list> extract_list overlay_list position_list
294294
%type <list> substr_list trim_list
295-
%type <ival> opt_interval
295+
%type <list> opt_interval interval_second
296296
%type <node> overlay_placing substr_from substr_for
297297

298298
%type <boolean> opt_instead opt_analyze
@@ -1222,28 +1222,39 @@ zone_value:
12221222
| ConstInterval Sconst opt_interval
12231223
{
12241224
TypeName *t = $1;
1225-
if ($3 != INTERVAL_FULL_RANGE)
1225+
if ($3 != NIL)
12261226
{
1227-
if (($3 & ~(INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE))) != 0)
1227+
A_Const *n = (A_Const *) linitial($3);
1228+
if ((n->val.val.ival & ~(INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE))) != 0)
12281229
ereport(ERROR,
12291230
(errcode(ERRCODE_SYNTAX_ERROR),
12301231
errmsg("time zone interval must be HOUR or HOUR TO MINUTE"),
12311232
scanner_errposition(@3)));
1232-
t->typmods = list_make1(makeIntConst($3, @3));
12331233
}
1234+
t->typmods = $3;
12341235
$$ = makeStringConstCast($2, @2, t);
12351236
}
12361237
| ConstInterval '(' Iconst ')' Sconst opt_interval
12371238
{
12381239
TypeName *t = $1;
1239-
if (($6 != INTERVAL_FULL_RANGE)
1240-
&& (($6 & ~(INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE))) != 0))
1241-
ereport(ERROR,
1242-
(errcode(ERRCODE_SYNTAX_ERROR),
1243-
errmsg("time zone interval must be HOUR or HOUR TO MINUTE"),
1244-
scanner_errposition(@6)));
1245-
t->typmods = list_make2(makeIntConst($6, @6),
1246-
makeIntConst($3, @3));
1240+
if ($6 != NIL)
1241+
{
1242+
A_Const *n = (A_Const *) linitial($6);
1243+
if ((n->val.val.ival & ~(INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE))) != 0)
1244+
ereport(ERROR,
1245+
(errcode(ERRCODE_SYNTAX_ERROR),
1246+
errmsg("time zone interval must be HOUR or HOUR TO MINUTE"),
1247+
scanner_errposition(@6)));
1248+
if (list_length($6) != 1)
1249+
ereport(ERROR,
1250+
(errcode(ERRCODE_SYNTAX_ERROR),
1251+
errmsg("interval precision specified twice"),
1252+
scanner_errposition(@1)));
1253+
t->typmods = lappend($6, makeIntConst($3, @3));
1254+
}
1255+
else
1256+
t->typmods = list_make2(makeIntConst(INTERVAL_FULL_RANGE, -1),
1257+
makeIntConst($3, @3));
12471258
$$ = makeStringConstCast($5, @5, t);
12481259
}
12491260
| NumericOnly { $$ = makeAConst($1, @1); }
@@ -6983,14 +6994,23 @@ SimpleTypename:
69836994
| ConstInterval opt_interval
69846995
{
69856996
$$ = $1;
6986-
if ($2 != INTERVAL_FULL_RANGE)
6987-
$$->typmods = list_make1(makeIntConst($2, @2));
6997+
$$->typmods = $2;
69886998
}
69896999
| ConstInterval '(' Iconst ')' opt_interval
69907000
{
69917001
$$ = $1;
6992-
$$->typmods = list_make2(makeIntConst($5, @5),
6993-
makeIntConst($3, @3));
7002+
if ($5 != NIL)
7003+
{
7004+
if (list_length($5) != 1)
7005+
ereport(ERROR,
7006+
(errcode(ERRCODE_SYNTAX_ERROR),
7007+
errmsg("interval precision specified twice"),
7008+
scanner_errposition(@1)));
7009+
$$->typmods = lappend($5, makeIntConst($3, @3));
7010+
}
7011+
else
7012+
$$->typmods = list_make2(makeIntConst(INTERVAL_FULL_RANGE, -1),
7013+
makeIntConst($3, @3));
69947014
}
69957015
;
69967016

@@ -7337,30 +7357,74 @@ opt_timezone:
73377357
;
73387358

73397359
opt_interval:
7340-
YEAR_P { $$ = INTERVAL_MASK(YEAR); }
7341-
| MONTH_P { $$ = INTERVAL_MASK(MONTH); }
7342-
| DAY_P { $$ = INTERVAL_MASK(DAY); }
7343-
| HOUR_P { $$ = INTERVAL_MASK(HOUR); }
7344-
| MINUTE_P { $$ = INTERVAL_MASK(MINUTE); }
7345-
| SECOND_P { $$ = INTERVAL_MASK(SECOND); }
7360+
YEAR_P
7361+
{ $$ = list_make1(makeIntConst(INTERVAL_MASK(YEAR), @1)); }
7362+
| MONTH_P
7363+
{ $$ = list_make1(makeIntConst(INTERVAL_MASK(MONTH), @1)); }
7364+
| DAY_P
7365+
{ $$ = list_make1(makeIntConst(INTERVAL_MASK(DAY), @1)); }
7366+
| HOUR_P
7367+
{ $$ = list_make1(makeIntConst(INTERVAL_MASK(HOUR), @1)); }
7368+
| MINUTE_P
7369+
{ $$ = list_make1(makeIntConst(INTERVAL_MASK(MINUTE), @1)); }
7370+
| interval_second
7371+
{ $$ = $1; }
73467372
| YEAR_P TO MONTH_P
7347-
{ $$ = INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH); }
7373+
{
7374+
$$ = list_make1(makeIntConst(INTERVAL_MASK(YEAR) |
7375+
INTERVAL_MASK(MONTH), @1));
7376+
}
73487377
| DAY_P TO HOUR_P
7349-
{ $$ = INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR); }
7378+
{
7379+
$$ = list_make1(makeIntConst(INTERVAL_MASK(DAY) |
7380+
INTERVAL_MASK(HOUR), @1));
7381+
}
73507382
| DAY_P TO MINUTE_P
7351-
{ $$ = INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR)
7352-
| INTERVAL_MASK(MINUTE); }
7353-
| DAY_P TO SECOND_P
7354-
{ $$ = INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR)
7355-
| INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND); }
7383+
{
7384+
$$ = list_make1(makeIntConst(INTERVAL_MASK(DAY) |
7385+
INTERVAL_MASK(HOUR) |
7386+
INTERVAL_MASK(MINUTE), @1));
7387+
}
7388+
| DAY_P TO interval_second
7389+
{
7390+
$$ = $3;
7391+
linitial($$) = makeIntConst(INTERVAL_MASK(DAY) |
7392+
INTERVAL_MASK(HOUR) |
7393+
INTERVAL_MASK(MINUTE) |
7394+
INTERVAL_MASK(SECOND), @1);
7395+
}
73567396
| HOUR_P TO MINUTE_P
7357-
{ $$ = INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE); }
7358-
| HOUR_P TO SECOND_P
7359-
{ $$ = INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE)
7360-
| INTERVAL_MASK(SECOND); }
7361-
| MINUTE_P TO SECOND_P
7362-
{ $$ = INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND); }
7363-
| /*EMPTY*/ { $$ = INTERVAL_FULL_RANGE; }
7397+
{
7398+
$$ = list_make1(makeIntConst(INTERVAL_MASK(HOUR) |
7399+
INTERVAL_MASK(MINUTE), @1));
7400+
}
7401+
| HOUR_P TO interval_second
7402+
{
7403+
$$ = $3;
7404+
linitial($$) = makeIntConst(INTERVAL_MASK(HOUR) |
7405+
INTERVAL_MASK(MINUTE) |
7406+
INTERVAL_MASK(SECOND), @1);
7407+
}
7408+
| MINUTE_P TO interval_second
7409+
{
7410+
$$ = $3;
7411+
linitial($$) = makeIntConst(INTERVAL_MASK(MINUTE) |
7412+
INTERVAL_MASK(SECOND), @1);
7413+
}
7414+
| /*EMPTY*/
7415+
{ $$ = NIL; }
7416+
;
7417+
7418+
interval_second:
7419+
SECOND_P
7420+
{
7421+
$$ = list_make1(makeIntConst(INTERVAL_MASK(SECOND), @1));
7422+
}
7423+
| SECOND_P '(' Iconst ')'
7424+
{
7425+
$$ = list_make2(makeIntConst(INTERVAL_MASK(SECOND), @1),
7426+
makeIntConst($3, @3));
7427+
}
73647428
;
73657429

73667430

@@ -9014,16 +9078,24 @@ AexprConst: Iconst
90149078
| ConstInterval Sconst opt_interval
90159079
{
90169080
TypeName *t = $1;
9017-
/* precision is not specified, but fields may be... */
9018-
if ($3 != INTERVAL_FULL_RANGE)
9019-
t->typmods = list_make1(makeIntConst($3, @3));
9081+
t->typmods = $3;
90209082
$$ = makeStringConstCast($2, @2, t);
90219083
}
90229084
| ConstInterval '(' Iconst ')' Sconst opt_interval
90239085
{
90249086
TypeName *t = $1;
9025-
t->typmods = list_make2(makeIntConst($6, @6),
9026-
makeIntConst($3, @3));
9087+
if ($6 != NIL)
9088+
{
9089+
if (list_length($6) != 1)
9090+
ereport(ERROR,
9091+
(errcode(ERRCODE_SYNTAX_ERROR),
9092+
errmsg("interval precision specified twice"),
9093+
scanner_errposition(@1)));
9094+
t->typmods = lappend($6, makeIntConst($3, @3));
9095+
}
9096+
else
9097+
t->typmods = list_make2(makeIntConst(INTERVAL_FULL_RANGE, -1),
9098+
makeIntConst($3, @3));
90279099
$$ = makeStringConstCast($5, @5, t);
90289100
}
90299101
| TRUE_P

src/backend/utils/adt/datetime.c

Lines changed: 22 additions & 4 deletions
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.191 2008/09/10 18:29:41 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.192 2008/09/11 15:27:30 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -2256,9 +2256,25 @@ DecodeTime(char *str, int fmask, int range,
22562256
tm->tm_hour = 0;
22572257
}
22582258
}
2259-
else if (*cp != ':')
2260-
return DTERR_BAD_FORMAT;
2261-
else
2259+
else if (*cp == '.')
2260+
{
2261+
/* always assume mm:ss.sss is MINUTE TO SECOND */
2262+
double frac;
2263+
2264+
str = cp;
2265+
frac = strtod(str, &cp);
2266+
if (*cp != '\0')
2267+
return DTERR_BAD_FORMAT;
2268+
#ifdef HAVE_INT64_TIMESTAMP
2269+
*fsec = rint(frac * 1000000);
2270+
#else
2271+
*fsec = frac;
2272+
#endif
2273+
tm->tm_sec = tm->tm_min;
2274+
tm->tm_min = tm->tm_hour;
2275+
tm->tm_hour = 0;
2276+
}
2277+
else if (*cp == ':')
22622278
{
22632279
str = cp + 1;
22642280
errno = 0;
@@ -2284,6 +2300,8 @@ DecodeTime(char *str, int fmask, int range,
22842300
else
22852301
return DTERR_BAD_FORMAT;
22862302
}
2303+
else
2304+
return DTERR_BAD_FORMAT;
22872305

22882306
/* do a sanity check */
22892307
#ifdef HAVE_INT64_TIMESTAMP

src/backend/utils/adt/timestamp.c

Lines changed: 3 additions & 3 deletions
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.191 2008/09/10 18:29:41 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.192 2008/09/11 15:27:30 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -744,7 +744,7 @@ intervaltypmodin(PG_FUNCTION_ARGS)
744744
tl = ArrayGetIntegerTypmods(ta, &n);
745745

746746
/*
747-
* tl[0] - opt_interval tl[1] - Iconst (optional)
747+
* tl[0] - interval range (fields bitmask) tl[1] - precision (optional)
748748
*
749749
* Note we must validate tl[0] even though it's normally guaranteed
750750
* correct by the grammar --- consider SELECT 'foo'::"interval"(1000).
@@ -881,7 +881,7 @@ intervaltypmodout(PG_FUNCTION_ARGS)
881881
}
882882

883883
if (precision != INTERVAL_FULL_PRECISION)
884-
snprintf(res, 64, "(%d)%s", precision, fieldstr);
884+
snprintf(res, 64, "%s(%d)", fieldstr, precision);
885885
else
886886
snprintf(res, 64, "%s", fieldstr);
887887

0 commit comments

Comments
 (0)