]> granicus.if.org Git - postgresql/commitdiff
Adjust the parser to accept the typename syntax INTERVAL ... SECOND(n)
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 11 Sep 2008 15:27:30 +0000 (15:27 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 11 Sep 2008 15:27:30 +0000 (15:27 +0000)
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.)

doc/src/sgml/datatype.sgml
src/backend/parser/gram.y
src/backend/utils/adt/datetime.c
src/backend/utils/adt/timestamp.c
src/test/regress/expected/interval.out
src/test/regress/sql/interval.sql

index 48dfe0a9c4772350937e210eac9507bd3e9f69cc..0b969eaa22ec2d9fe821a3a84200ecafbf47b8e5 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.227 2008/05/16 16:31:01 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.228 2008/09/11 15:27:30 tgl Exp $ -->
 
  <chapter id="datatype">
   <title id="datatype-title">Data Types</title>
       </row>
 
       <row>
-       <entry><type>interval [ (<replaceable>p</replaceable>) ]</type></entry>
+       <entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry>
        <entry></entry>
        <entry>time span</entry>
       </row>
@@ -1420,7 +1420,7 @@ SELECT b, char_length(b) FROM test2;
         <entry>1 microsecond / 14 digits</entry>
        </row>
        <row>
-        <entry><type>interval [ (<replaceable>p</replaceable>) ]</type></entry>
+        <entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry>
         <entry>12 bytes</entry>
         <entry>time intervals</entry>
         <entry>-178000000 years</entry>
@@ -1505,6 +1505,30 @@ SELECT b, char_length(b) FROM test2;
     storage is used, or from 0 to 10 when floating-point storage is used.
    </para>
 
+   <para>
+    The <type>interval</type> type has an additional option, which is
+    to restrict the set of stored fields by writing one of these phrases:
+<programlisting>
+    YEAR
+    MONTH
+    DAY
+    HOUR
+    MINUTE
+    SECOND
+    YEAR TO MONTH
+    DAY TO HOUR
+    DAY TO MINUTE
+    DAY TO SECOND
+    HOUR TO MINUTE
+    MINUTE TO SECOND
+</programlisting>
+    Input falling outside the specified set of fields is silently discarded.
+    Note that if both <replaceable>fields</replaceable> and
+    <replaceable>precision</replaceable> are specified, the
+    <replaceable>fields</replaceable> must include <literal>SECOND</>,
+    since the precision applies only to the seconds.
+   </para>
+
    <para>
     The type <type>time with time zone</type> is defined by the SQL
     standard, but the definition exhibits properties which lead to
@@ -1928,18 +1952,26 @@ January 8 04:05:06 1999 PST
       <replaceable>direction</> can be <literal>ago</literal> or
       empty.  The at sign (<literal>@</>) is optional noise.  The amounts
       of different units are implicitly added up with appropriate
-      sign accounting.
+      sign accounting.  <literal>ago</literal> negates all the fields.
      </para>
 
      <para>
       Quantities of days, hours, minutes, and seconds can be specified without
       explicit unit markings.  For example, <literal>'1 12:59:10'</> is read
-      the same as <literal>'1 day 12 hours 59 min 10 sec'</>.
+      the same as <literal>'1 day 12 hours 59 min 10 sec'</>.  Also,
+      a combination of years and months can be specified with a dash;
+      for example <literal>'200-10'</> is read the same as <literal>'200 years
+      10 months'</>.  (These shorter forms are in fact the only ones allowed
+      by the SQL standard.)
      </para>
 
      <para>
-      The optional subsecond precision <replaceable>p</replaceable> should 
-      be between 0 and 6, and defaults to the precision of the input literal.
+      When writing an interval constant with a <replaceable>fields</>
+      specification, or when assigning to an interval column that was defined
+      with a <replaceable>fields</> specification, the interpretation of
+      unmarked quantities depends on the <replaceable>fields</>.  For
+      example <literal>INTERVAL '1' YEAR</> is read as 1 year, whereas
+      <literal>INTERVAL '1'</> means 1 second.
      </para>
 
      <para>
index 5c716dd8f22cc6bd5237938eb66c1cf3bb6b5573..ecbd55d13020b4095a6cc4481d53b706b3d86f27 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.622 2008/09/02 20:37:54 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.623 2008/09/11 15:27:30 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -292,7 +292,7 @@ static TypeName *TableFuncTypeName(List *columns);
 
 %type <list>   extract_list overlay_list position_list
 %type <list>   substr_list trim_list
-%type <ival>   opt_interval
+%type <list>   opt_interval interval_second
 %type <node>   overlay_placing substr_from substr_for
 
 %type <boolean> opt_instead opt_analyze
@@ -1222,28 +1222,39 @@ zone_value:
                        | ConstInterval Sconst opt_interval
                                {
                                        TypeName *t = $1;
-                                       if ($3 != INTERVAL_FULL_RANGE)
+                                       if ($3 != NIL)
                                        {
-                                               if (($3 & ~(INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE))) != 0)
+                                               A_Const *n = (A_Const *) linitial($3);
+                                               if ((n->val.val.ival & ~(INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE))) != 0)
                                                        ereport(ERROR,
                                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                                         errmsg("time zone interval must be HOUR or HOUR TO MINUTE"),
                                                                         scanner_errposition(@3)));
-                                               t->typmods = list_make1(makeIntConst($3, @3));
                                        }
+                                       t->typmods = $3;
                                        $$ = makeStringConstCast($2, @2, t);
                                }
                        | ConstInterval '(' Iconst ')' Sconst opt_interval
                                {
                                        TypeName *t = $1;
-                                       if (($6 != INTERVAL_FULL_RANGE)
-                                               && (($6 & ~(INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE))) != 0))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("time zone interval must be HOUR or HOUR TO MINUTE"),
-                                                                scanner_errposition(@6)));
-                                       t->typmods = list_make2(makeIntConst($6, @6),
-                                                                                       makeIntConst($3, @3));
+                                       if ($6 != NIL)
+                                       {
+                                               A_Const *n = (A_Const *) linitial($6);
+                                               if ((n->val.val.ival & ~(INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE))) != 0)
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                        errmsg("time zone interval must be HOUR or HOUR TO MINUTE"),
+                                                                        scanner_errposition(@6)));
+                                               if (list_length($6) != 1)
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                        errmsg("interval precision specified twice"),
+                                                                        scanner_errposition(@1)));
+                                               t->typmods = lappend($6, makeIntConst($3, @3));
+                                       }
+                                       else
+                                               t->typmods = list_make2(makeIntConst(INTERVAL_FULL_RANGE, -1),
+                                                                                               makeIntConst($3, @3));
                                        $$ = makeStringConstCast($5, @5, t);
                                }
                        | NumericOnly                                                   { $$ = makeAConst($1, @1); }
@@ -6983,14 +6994,23 @@ SimpleTypename:
                        | ConstInterval opt_interval
                                {
                                        $$ = $1;
-                                       if ($2 != INTERVAL_FULL_RANGE)
-                                               $$->typmods = list_make1(makeIntConst($2, @2));
+                                       $$->typmods = $2;
                                }
                        | ConstInterval '(' Iconst ')' opt_interval
                                {
                                        $$ = $1;
-                                       $$->typmods = list_make2(makeIntConst($5, @5),
-                                                                                        makeIntConst($3, @3));
+                                       if ($5 != NIL)
+                                       {
+                                               if (list_length($5) != 1)
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                        errmsg("interval precision specified twice"),
+                                                                        scanner_errposition(@1)));
+                                               $$->typmods = lappend($5, makeIntConst($3, @3));
+                                       }
+                                       else
+                                               $$->typmods = list_make2(makeIntConst(INTERVAL_FULL_RANGE, -1),
+                                                                                                makeIntConst($3, @3));
                                }
                ;
 
@@ -7337,30 +7357,74 @@ opt_timezone:
                ;
 
 opt_interval:
-                       YEAR_P                                                                  { $$ = INTERVAL_MASK(YEAR); }
-                       | MONTH_P                                                               { $$ = INTERVAL_MASK(MONTH); }
-                       | DAY_P                                                                 { $$ = INTERVAL_MASK(DAY); }
-                       | HOUR_P                                                                { $$ = INTERVAL_MASK(HOUR); }
-                       | MINUTE_P                                                              { $$ = INTERVAL_MASK(MINUTE); }
-                       | SECOND_P                                                              { $$ = INTERVAL_MASK(SECOND); }
+                       YEAR_P
+                               { $$ = list_make1(makeIntConst(INTERVAL_MASK(YEAR), @1)); }
+                       | MONTH_P
+                               { $$ = list_make1(makeIntConst(INTERVAL_MASK(MONTH), @1)); }
+                       | DAY_P
+                               { $$ = list_make1(makeIntConst(INTERVAL_MASK(DAY), @1)); }
+                       | HOUR_P
+                               { $$ = list_make1(makeIntConst(INTERVAL_MASK(HOUR), @1)); }
+                       | MINUTE_P
+                               { $$ = list_make1(makeIntConst(INTERVAL_MASK(MINUTE), @1)); }
+                       | interval_second
+                               { $$ = $1; }
                        | YEAR_P TO MONTH_P
-                                       { $$ = INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH); }
+                               {
+                                       $$ = list_make1(makeIntConst(INTERVAL_MASK(YEAR) |
+                                                                                                INTERVAL_MASK(MONTH), @1));
+                               }
                        | DAY_P TO HOUR_P
-                                       { $$ = INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR); }
+                               {
+                                       $$ = list_make1(makeIntConst(INTERVAL_MASK(DAY) |
+                                                                                                INTERVAL_MASK(HOUR), @1));
+                               }
                        | DAY_P TO MINUTE_P
-                                       { $$ = INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR)
-                                               | INTERVAL_MASK(MINUTE); }
-                       | DAY_P TO SECOND_P
-                                       { $$ = INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR)
-                                               | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND); }
+                               {
+                                       $$ = list_make1(makeIntConst(INTERVAL_MASK(DAY) |
+                                                                                                INTERVAL_MASK(HOUR) |
+                                                                                                INTERVAL_MASK(MINUTE), @1));
+                               }
+                       | DAY_P TO interval_second
+                               {
+                                       $$ = $3;
+                                       linitial($$) = makeIntConst(INTERVAL_MASK(DAY) |
+                                                                                               INTERVAL_MASK(HOUR) |
+                                                                                               INTERVAL_MASK(MINUTE) |
+                                                                                               INTERVAL_MASK(SECOND), @1);
+                               }
                        | HOUR_P TO MINUTE_P
-                                       { $$ = INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE); }
-                       | HOUR_P TO SECOND_P
-                                       { $$ = INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE)
-                                               | INTERVAL_MASK(SECOND); }
-                       | MINUTE_P TO SECOND_P
-                                       { $$ = INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND); }
-                       | /*EMPTY*/                                                             { $$ = INTERVAL_FULL_RANGE; }
+                               {
+                                       $$ = list_make1(makeIntConst(INTERVAL_MASK(HOUR) |
+                                                                                                INTERVAL_MASK(MINUTE), @1));
+                               }
+                       | HOUR_P TO interval_second
+                               {
+                                       $$ = $3;
+                                       linitial($$) = makeIntConst(INTERVAL_MASK(HOUR) |
+                                                                                               INTERVAL_MASK(MINUTE) |
+                                                                                               INTERVAL_MASK(SECOND), @1);
+                               }
+                       | MINUTE_P TO interval_second
+                               {
+                                       $$ = $3;
+                                       linitial($$) = makeIntConst(INTERVAL_MASK(MINUTE) |
+                                                                                               INTERVAL_MASK(SECOND), @1);
+                               }
+                       | /*EMPTY*/
+                               { $$ = NIL; }
+               ;
+
+interval_second:
+                       SECOND_P
+                               {
+                                       $$ = list_make1(makeIntConst(INTERVAL_MASK(SECOND), @1));
+                               }
+                       | SECOND_P '(' Iconst ')'
+                               {
+                                       $$ = list_make2(makeIntConst(INTERVAL_MASK(SECOND), @1),
+                                                                       makeIntConst($3, @3));
+                               }
                ;
 
 
@@ -9014,16 +9078,24 @@ AexprConst: Iconst
                        | ConstInterval Sconst opt_interval
                                {
                                        TypeName *t = $1;
-                                       /* precision is not specified, but fields may be... */
-                                       if ($3 != INTERVAL_FULL_RANGE)
-                                               t->typmods = list_make1(makeIntConst($3, @3));
+                                       t->typmods = $3;
                                        $$ = makeStringConstCast($2, @2, t);
                                }
                        | ConstInterval '(' Iconst ')' Sconst opt_interval
                                {
                                        TypeName *t = $1;
-                                       t->typmods = list_make2(makeIntConst($6, @6),
-                                                                                   makeIntConst($3, @3));
+                                       if ($6 != NIL)
+                                       {
+                                               if (list_length($6) != 1)
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                        errmsg("interval precision specified twice"),
+                                                                        scanner_errposition(@1)));
+                                               t->typmods = lappend($6, makeIntConst($3, @3));
+                                       }
+                                       else
+                                               t->typmods = list_make2(makeIntConst(INTERVAL_FULL_RANGE, -1),
+                                                                                               makeIntConst($3, @3));
                                        $$ = makeStringConstCast($5, @5, t);
                                }
                        | TRUE_P
index 6712d6d8d44397e1929db9e5b0660c957a2dd911..a1cf2b4ed54c71047fa12769bfa50fdc9fe3a8ce 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.191 2008/09/10 18:29:41 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.192 2008/09/11 15:27:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2256,9 +2256,25 @@ DecodeTime(char *str, int fmask, int range,
                        tm->tm_hour = 0;
                }
        }
-       else if (*cp != ':')
-               return DTERR_BAD_FORMAT;
-       else
+       else if (*cp == '.')
+       {
+               /* always assume mm:ss.sss is MINUTE TO SECOND */
+               double          frac;
+
+               str = cp;
+               frac = strtod(str, &cp);
+               if (*cp != '\0')
+                       return DTERR_BAD_FORMAT;
+#ifdef HAVE_INT64_TIMESTAMP
+               *fsec = rint(frac * 1000000);
+#else
+               *fsec = frac;
+#endif
+               tm->tm_sec = tm->tm_min;
+               tm->tm_min = tm->tm_hour;
+               tm->tm_hour = 0;
+       }
+       else if (*cp == ':')
        {
                str = cp + 1;
                errno = 0;
@@ -2284,6 +2300,8 @@ DecodeTime(char *str, int fmask, int range,
                else
                        return DTERR_BAD_FORMAT;
        }
+       else
+               return DTERR_BAD_FORMAT;
 
        /* do a sanity check */
 #ifdef HAVE_INT64_TIMESTAMP
index 9060b989f9cd01e57d77e2d2c4368b02395b64cf..acfc89ba047110a9545c911cabbf7f6c6215c4f7 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.191 2008/09/10 18:29:41 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.192 2008/09/11 15:27:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -744,7 +744,7 @@ intervaltypmodin(PG_FUNCTION_ARGS)
        tl = ArrayGetIntegerTypmods(ta, &n);
 
        /*
-        * tl[0] - opt_interval tl[1] - Iconst (optional)
+        * tl[0] - interval range (fields bitmask)  tl[1] - precision (optional)
         *
         * Note we must validate tl[0] even though it's normally guaranteed
         * correct by the grammar --- consider SELECT 'foo'::"interval"(1000).
@@ -881,7 +881,7 @@ intervaltypmodout(PG_FUNCTION_ARGS)
        }
 
        if (precision != INTERVAL_FULL_PRECISION)
-               snprintf(res, 64, "(%d)%s", precision, fieldstr);
+               snprintf(res, 64, "%s(%d)", fieldstr, precision);
        else
                snprintf(res, 64, "%s", fieldstr);
 
index ede3c58708c1c321ddffd80d72be6dd380982c16..a9b5766439baaeae7d768ab8728c4df9816d1068 100644 (file)
@@ -518,3 +518,100 @@ SELECT interval '1 2:03:04' minute to second;
  00:03:04
 (1 row)
 
+-- test syntaxes for restricted precision
+SELECT interval(0) '1 day 01:23:45.6789';
+    interval    
+----------------
+ 1 day 01:23:46
+(1 row)
+
+SELECT interval(2) '1 day 01:23:45.6789';
+     interval      
+-------------------
+ 1 day 01:23:45.68
+(1 row)
+
+SELECT interval '12:34.5678' minute to second(2);  -- per SQL spec
+  interval   
+-------------
+ 00:12:34.57
+(1 row)
+
+SELECT interval(2) '12:34.5678' minute to second;  -- historical PG
+  interval   
+-------------
+ 00:12:34.57
+(1 row)
+
+SELECT interval(2) '12:34.5678' minute to second(2);  -- syntax error
+ERROR:  interval precision specified twice
+LINE 1: SELECT interval(2) '12:34.5678' minute to second(2);
+               ^
+SELECT interval '1.234' second;
+   interval   
+--------------
+ 00:00:01.234
+(1 row)
+
+SELECT interval '1.234' second(2);
+  interval   
+-------------
+ 00:00:01.23
+(1 row)
+
+SELECT interval '1 2.345' day to second(2);
+    interval    
+----------------
+ 1 day 02:20:42
+(1 row)
+
+SELECT interval '1 2:03' day to second(2);
+    interval    
+----------------
+ 1 day 02:03:00
+(1 row)
+
+SELECT interval '1 2:03.4567' day to second(2);
+     interval      
+-------------------
+ 1 day 00:02:03.46
+(1 row)
+
+SELECT interval '1 2:03:04.5678' day to second(2);
+     interval      
+-------------------
+ 1 day 02:03:04.57
+(1 row)
+
+SELECT interval '1 2.345' hour to second(2);
+ERROR:  invalid input syntax for type interval: "1 2.345"
+LINE 1: SELECT interval '1 2.345' hour to second(2);
+                        ^
+SELECT interval '1 2:03.45678' hour to second(2);
+  interval   
+-------------
+ 00:02:03.46
+(1 row)
+
+SELECT interval '1 2:03:04.5678' hour to second(2);
+  interval   
+-------------
+ 02:03:04.57
+(1 row)
+
+SELECT interval '1 2.3456' minute to second(2);
+ERROR:  invalid input syntax for type interval: "1 2.3456"
+LINE 1: SELECT interval '1 2.3456' minute to second(2);
+                        ^
+SELECT interval '1 2:03.5678' minute to second(2);
+  interval   
+-------------
+ 00:02:03.57
+(1 row)
+
+SELECT interval '1 2:03:04.5678' minute to second(2);
+  interval   
+-------------
+ 00:03:04.57
+(1 row)
+
index 2c6aecaa5197d65afb1dda84414f669ab4096309..06e470585915c9d5a309b8c8a43de1ac7b035f37 100644 (file)
@@ -156,3 +156,22 @@ 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;
+
+-- test syntaxes for restricted precision
+SELECT interval(0) '1 day 01:23:45.6789';
+SELECT interval(2) '1 day 01:23:45.6789';
+SELECT interval '12:34.5678' minute to second(2);  -- per SQL spec
+SELECT interval(2) '12:34.5678' minute to second;  -- historical PG
+SELECT interval(2) '12:34.5678' minute to second(2);  -- syntax error
+SELECT interval '1.234' second;
+SELECT interval '1.234' second(2);
+SELECT interval '1 2.345' day to second(2);
+SELECT interval '1 2:03' day to second(2);
+SELECT interval '1 2:03.4567' day to second(2);
+SELECT interval '1 2:03:04.5678' day to second(2);
+SELECT interval '1 2.345' hour to second(2);
+SELECT interval '1 2:03.45678' hour to second(2);
+SELECT interval '1 2:03:04.5678' hour to second(2);
+SELECT interval '1 2.3456' minute to second(2);
+SELECT interval '1 2:03.5678' minute to second(2);
+SELECT interval '1 2:03:04.5678' minute to second(2);