This patch addresses a personal need: nearly every time I use
generate_series for timestamps, I end up casting the result into date or
the ISO string thereof. Like such:

SELECT d.dt::date as dt
FROM generate_series('2015-01-01'::date,
                     '2016-01-04'::date,
                     interval '1 day') AS d(dt);


That's less than elegant.

With this patch, we can do this:


SELECT d.date_val FROM
generate_series('1991-09-24'::date,'1991-10-01'::date) as d(date_val);
  date_val
------------
 1991-09-24
 1991-09-25
 1991-09-26
 1991-09-27
 1991-09-28
 1991-09-29
 1991-09-30
 1991-10-01
(8 rows)

SELECT d.date_val FROM
generate_series('1991-09-24'::date,'1991-10-01'::date,7) as d(date_val);
  date_val
------------
 1991-09-24
 1991-10-01
(2 rows)

SELECT d.date_val FROM
generate_series('1999-12-31'::date,'1999-12-29'::date,-1) as d(date_val);
  date_val
------------
 1999-12-31
 1999-12-30
 1999-12-29
(3 rows)


One thing I discovered in doing this patch is that if you do a timestamp
generate_series involving infinity....it tries to do it. I didn't wait to
see if it finished.

For the date series, I put in checks to return an empty set:

SELECT d.date_val FROM
generate_series('-infinity'::date,'1999-12-29'::date) as d(date_val);
 date_val
----------
(0 rows)

SELECT d.date_val FROM generate_series('1991-09-24'::date,'infinity'::date)
as d(date_val);
 date_val
----------
(0 rows)



Notes:
- I borrowed the int4 implementation's check for step-size of 0 for POLA
reasons. However, it occurred to me that the function might be leakproof if
the behavior where changed to instead return an empty set. I'm not sure
that leakproof is a goal in and of itself.

First attempt at this patch attached. The examples above are copied from
the new test cases.
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9c143b2..15ebe47 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14657,6 +14657,26 @@ AND
       </entry>
      </row>
 
+     <row>
+      <entry><literal><function>generate_series(<parameter>start</parameter>, 
<parameter>stop</parameter>)</function></literal></entry>
+      <entry><type>date</type></entry>
+      <entry><type>setof date</type></entry>
+      <entry>
+       Generate a series of values, from <parameter>start</parameter> to 
<parameter>stop</parameter>
+       with a step size of one day
+      </entry>
+     </row>
+
+     <row>
+      <entry><literal><function>generate_series(<parameter>start</parameter>, 
<parameter>stop</parameter>, <parameter>step 
integer</parameter>)</function></literal></entry>
+      <entry><type>date</type></entry>
+      <entry><type>setof date</type></entry>
+      <entry>
+       Generate a series of values, from <parameter>start</parameter> to 
<parameter>stop</parameter>
+       with a step size of <parameter>step</parameter>
+      </entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
@@ -14721,6 +14741,26 @@ SELECT * FROM generate_series('2008-03-01 
00:00'::timestamp,
  2008-03-03 22:00:00
  2008-03-04 08:00:00
 (9 rows)
+
+SELECT d.date_val FROM generate_series('1991-09-24'::date,'1991-10-01'::date) 
as d(date_val);
+  date_val  
+------------
+ 1991-09-24
+ 1991-09-25
+ 1991-09-26
+ 1991-09-27
+ 1991-09-28
+ 1991-09-29
+ 1991-09-30
+ 1991-10-01
+(8 rows)
+
+SELECT d.date_val FROM 
generate_series('1991-09-24'::date,'1991-10-01'::date,7) as d(date_val);
+  date_val  
+------------
+ 1991-09-24
+ 1991-10-01
+(2 rows)
 </programlisting>
   </para>
 
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 332db7e..7404a2f 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -30,6 +30,7 @@
 #include "utils/datetime.h"
 #include "utils/nabstime.h"
 #include "utils/sortsupport.h"
+#include "funcapi.h"
 
 /*
  * gcc's -ffast-math switch breaks routines that expect exact results from
@@ -2811,3 +2812,97 @@ timetz_izone(PG_FUNCTION_ARGS)
 
        PG_RETURN_TIMETZADT_P(result);
 }
+
+/* Corey BEGIN */
+typedef struct
+{
+       DateADT         current;
+       DateADT         finish;
+       int32           step;
+} generate_series_date_fctx;
+
+
+/* generate_series_date()
+ * Generate the set of dates from start to finish by step
+ */
+Datum
+generate_series_date(PG_FUNCTION_ARGS)
+{
+       return generate_series_step_date(fcinfo);
+}
+
+Datum
+generate_series_step_date(PG_FUNCTION_ARGS)
+{
+       FuncCallContext *funcctx;
+       generate_series_date_fctx *fctx;
+       DateADT result;
+
+       /* stuff done only on the first call of the function */
+       if (SRF_IS_FIRSTCALL())
+       {
+               DateADT         start = PG_GETARG_DATEADT(0);
+               DateADT         finish = PG_GETARG_DATEADT(1);
+               int32           step = 1;
+
+               /* see if we were given an explicit step size */
+               if (PG_NARGS() == 3)
+                       step = PG_GETARG_INT32(2);
+               if (step == 0)
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("step size cannot equal 
zero")));
+
+               MemoryContext oldcontext;
+
+               /* create a function context for cross-call persistence */
+               funcctx = SRF_FIRSTCALL_INIT();
+
+               /*
+                * switch to memory context appropriate for multiple function 
calls
+                */
+               oldcontext = 
MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+               /* allocate memory for user context */
+               fctx = (generate_series_date_fctx *)
+                       palloc(sizeof(generate_series_date_fctx));
+
+               /*
+                * Use fctx to keep state from call to call. Seed current with 
the
+                * original start value
+                */
+               fctx->current = start;
+               fctx->finish = finish;
+               /* cannot iterate infinity */
+               if (DATE_NOT_FINITE(start) || DATE_NOT_FINITE(finish))
+                       fctx->step = 0;
+               else
+                       fctx->step = step;
+
+               funcctx->user_fctx = fctx;
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+       /* stuff done on every call of the function */
+       funcctx = SRF_PERCALL_SETUP();
+
+       /*
+        * get the saved state and use current as the result for this iteration
+        */
+       fctx = funcctx->user_fctx;
+       result = fctx->current;
+
+       if ((fctx->step > 0 && fctx->current <= fctx->finish) ||
+               (fctx->step < 0 && fctx->current >= fctx->finish))
+       {
+               /* increment current in preparation for next iteration */
+               fctx->current += fctx->step;
+
+               /* do when there is more left to send */
+               SRF_RETURN_NEXT(funcctx, DateADTGetDatum(result));
+       }
+       else
+               /* do when there is no more left */
+               SRF_RETURN_DONE(funcctx);
+}
+
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 79e92ff..65293eb 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4013,6 +4013,11 @@ DATA(insert OID = 938  (  generate_series PGNSP PGUID 12 
1 1000 0 0 f f f f t t
 DESCR("non-persistent series generator");
 DATA(insert OID = 939  (  generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t 
t s s 3 0 1184 "1184 1184 1186" _null_ _null_ _null_ _null_ _null_ 
generate_series_timestamptz _null_ _null_ _null_ ));
 DESCR("non-persistent series generator");
+DATA(insert OID = 2739 (  generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t 
t s s 3 0 1082 "1082 1082 23" _null_ _null_ "{start,finish,step}" _null_ _null_ 
generate_series_step_date _null_ _null_ _null_ ));
+DESCR("non-persistent series generator");
+DATA(insert OID = 2740 (  generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t 
t s s 2 0 1082 "1082 1082" _null_ _null_ "{start,finish}" _null_ _null_ 
generate_series_date _null_ _null_ _null_ ));
+DESCR("non-persistent series generator");
+
 
 /* boolean aggregates */
 DATA(insert OID = 2515 ( booland_statefunc                        PGNSP PGUID 
12 1 0 0 0 f f f f t f i s 2 0 16 "16 16" _null_ _null_ _null_ _null_ _null_ 
booland_statefunc _null_ _null_ _null_ ));
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 1b962af..f427cde 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -205,4 +205,7 @@ extern Datum timetz_izone(PG_FUNCTION_ARGS);
 extern Datum timetz_pl_interval(PG_FUNCTION_ARGS);
 extern Datum timetz_mi_interval(PG_FUNCTION_ARGS);
 
+extern Datum generate_series_date(PG_FUNCTION_ARGS);
+extern Datum generate_series_step_date(PG_FUNCTION_ARGS);
+
 #endif   /* DATE_H */
diff --git a/src/test/regress/expected/date.out 
b/src/test/regress/expected/date.out
index 56c5520..427f3df 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1452,3 +1452,42 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+SET datestyle TO iso;
+SELECT d.date_val FROM generate_series('1991-09-24'::date,'1991-10-01'::date) 
as d(date_val);
+  date_val  
+------------
+ 1991-09-24
+ 1991-09-25
+ 1991-09-26
+ 1991-09-27
+ 1991-09-28
+ 1991-09-29
+ 1991-09-30
+ 1991-10-01
+(8 rows)
+
+SELECT d.date_val FROM 
generate_series('1991-09-24'::date,'1991-10-01'::date,7) as d(date_val);
+  date_val  
+------------
+ 1991-09-24
+ 1991-10-01
+(2 rows)
+
+SELECT d.date_val FROM 
generate_series('1999-12-31'::date,'1999-12-29'::date,-1) as d(date_val);
+  date_val  
+------------
+ 1999-12-31
+ 1999-12-30
+ 1999-12-29
+(3 rows)
+
+SELECT d.date_val FROM generate_series('-infinity'::date,'1999-12-29'::date) 
as d(date_val);
+ date_val 
+----------
+(0 rows)
+
+SELECT d.date_val FROM generate_series('1991-09-24'::date,'infinity'::date) as 
d(date_val);
+ date_val 
+----------
+(0 rows)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index e40b4c4..b790e32 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -340,3 +340,10 @@ select make_date(2013, 11, -1);
 select make_date(-44, 3, 15);  -- perhaps we should allow this sometime?
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+SET datestyle TO iso;
+SELECT d.date_val FROM generate_series('1991-09-24'::date,'1991-10-01'::date) 
as d(date_val);
+SELECT d.date_val FROM 
generate_series('1991-09-24'::date,'1991-10-01'::date,7) as d(date_val);
+SELECT d.date_val FROM 
generate_series('1999-12-31'::date,'1999-12-29'::date,-1) as d(date_val);
+SELECT d.date_val FROM generate_series('-infinity'::date,'1999-12-29'::date) 
as d(date_val);
+SELECT d.date_val FROM generate_series('1991-09-24'::date,'infinity'::date) as 
d(date_val);
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to