From b6d15590f7269536c4da5b4cd835bc1e4f2cdf43 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 4 May 2008 23:19:24 +0000
Subject: [PATCH] Add timestamp and timestamptz versions of generate_series().

Hitoshi Harada
---
 doc/src/sgml/func.sgml            |  48 ++++++--
 src/backend/utils/adt/timestamp.c | 181 +++++++++++++++++++++++++++++-
 src/include/catalog/catversion.h  |   4 +-
 src/include/catalog/pg_proc.h     |   6 +-
 src/include/utils/timestamp.h     |   5 +-
 5 files changed, 229 insertions(+), 15 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 47727a1f412..d168891d4e7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.435 2008/05/04 21:13:35 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.436 2008/05/04 23:19:23 tgl Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
@@ -10650,6 +10650,16 @@ AND
       </entry>
      </row>
 
+     <row>
+      <entry><literal><function>generate_series</function>(<parameter>start</parameter>, <parameter>stop</parameter>, <parameter>step</parameter> <type>interval</>)</literal></entry>
+      <entry><type>timestamp</type> or <type>timestamp with time zone</type></entry>
+      <entry><type>setof timestamp</type> or <type>setof timestamp with time zone</type> (same as argument 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>
@@ -10683,6 +10693,7 @@ select * from generate_series(4,3);
 -----------------
 (0 rows)
 
+-- this example relies on the date-plus-integer operator
 select current_date + s.a as dates from generate_series(0,14,7) as s(a);
    dates
 ------------
@@ -10690,16 +10701,26 @@ select current_date + s.a as dates from generate_series(0,14,7) as s(a);
  2004-02-12
  2004-02-19
 (3 rows)
+
+select * from generate_series('2008-03-01 00:00'::timestamp,
+                              '2008-03-04 12:00', '10 hours');
+   generate_series   
+---------------------
+ 2008-03-01 00:00:00
+ 2008-03-01 10:00:00
+ 2008-03-01 20:00:00
+ 2008-03-02 06:00:00
+ 2008-03-02 16:00:00
+ 2008-03-03 02:00:00
+ 2008-03-03 12:00:00
+ 2008-03-03 22:00:00
+ 2008-03-04 08:00:00
+(9 rows)
 </programlisting>
   </para>
 
   <table id="functions-srf-subscripts">
-
-  <indexterm>
-   <primary>generate_subscripts</primary>
-  </indexterm>
-
-   <title>Subscripts Generating Functions</title>
+   <title>Subscript Generating Functions</title>
    <tgroup cols="3">
     <thead>
      <row>
@@ -10711,7 +10732,7 @@ select current_date + s.a as dates from generate_series(0,14,7) as s(a);
 
     <tbody>
      <row>
-      <entry><literal><function>generate_subscripts</function>(<parameter>array annyarray</parameter>, <parameter>dim int</parameter>)</literal></entry>
+      <entry><literal><function>generate_subscripts</function>(<parameter>array anyarray</parameter>, <parameter>dim int</parameter>)</literal></entry>
       <entry><type>setof int</type></entry>
       <entry>
        Generate a series comprising the given array's subscripts.
@@ -10719,7 +10740,7 @@ select current_date + s.a as dates from generate_series(0,14,7) as s(a);
      </row>
 
      <row>
-      <entry><literal><function>generate_subscripts</function>(<parameter>array annyarray</parameter>, <parameter>dim int</parameter>, <parameter>reverse boolean</parameter>)</literal></entry>
+      <entry><literal><function>generate_subscripts</function>(<parameter>array anyarray</parameter>, <parameter>dim int</parameter>, <parameter>reverse boolean</parameter>)</literal></entry>
       <entry><type>setof int</type></entry>
       <entry>
        Generate a series comprising the given array's subscripts. When
@@ -10732,10 +10753,17 @@ select current_date + s.a as dates from generate_series(0,14,7) as s(a);
    </tgroup>
   </table>
 
+  <indexterm>
+   <primary>generate_subscripts</primary>
+  </indexterm>
+
   <para>
+   <function>generate_subscripts</> is a convenience function that generates
+   the set of valid subscripts for the specified dimension of the given
+   array.
    Zero rows are returned for arrays that do not have the requested dimension,
    or for NULL arrays (but valid subscripts are returned for NULL array
-   elements.) Some examples follow:
+   elements).  Some examples follow:
 <programlisting>
 -- basic usage
 select generate_subscripts('{NULL,1,NULL,2}'::int[], 1) as s;
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 25d95b60efb..c266ed0f38c 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.188 2008/05/04 21:13:35 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.189 2008/05/04 23:19:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,6 +24,7 @@
 #include "access/hash.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "funcapi.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
@@ -45,6 +46,22 @@ TimestampTz PgStartTime;
 /* Set at configuration reload */
 TimestampTz PgReloadTime;
 
+typedef struct
+{
+	Timestamp	current;
+	Timestamp	finish;
+	Interval	step;
+	int			step_sign;
+} generate_series_timestamp_fctx;
+
+typedef struct
+{
+	TimestampTz	current;
+	TimestampTz	finish;
+	Interval	step;
+	int			step_sign;
+} generate_series_timestamptz_fctx;
+
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static int	EncodeSpecialTimestamp(Timestamp dt, char *str);
@@ -4651,3 +4668,165 @@ timestamptz_izone(PG_FUNCTION_ARGS)
 
 	PG_RETURN_TIMESTAMP(result);
 }
+
+/* generate_series_timestamp()
+ * Generate the set of timestamps from start to finish by step
+ */
+Datum
+generate_series_timestamp(PG_FUNCTION_ARGS)
+{
+	FuncCallContext *funcctx;
+	generate_series_timestamp_fctx *fctx;
+	Timestamp result;
+
+	/* stuff done only on the first call of the function */
+	if (SRF_IS_FIRSTCALL())
+	{
+		Timestamp start = PG_GETARG_TIMESTAMP(0);
+		Timestamp finish = PG_GETARG_TIMESTAMP(1);
+		Interval *step = PG_GETARG_INTERVAL_P(2);
+		MemoryContext oldcontext;
+		Interval interval_zero;
+
+		/* 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_timestamp_fctx *)
+			palloc(sizeof(generate_series_timestamp_fctx));
+
+		/*
+		 * Use fctx to keep state from call to call. Seed current with the
+		 * original start value
+		 */
+		fctx->current = start;
+		fctx->finish = finish;
+		fctx->step = *step;
+
+		/* Determine sign of the interval */
+		MemSet(&interval_zero, 0, sizeof(Interval));
+		fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+
+		if (fctx->step_sign == 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("step size cannot equal zero")));
+
+		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_sign > 0 ?
+		timestamp_cmp_internal(result, fctx->finish) <= 0 :
+		timestamp_cmp_internal(result, fctx->finish) >= 0)
+	{
+		/* increment current in preparation for next iteration */
+		fctx->current = DatumGetTimestamp(
+			DirectFunctionCall2(timestamp_pl_interval,
+								TimestampGetDatum(fctx->current),
+								PointerGetDatum(&fctx->step)));
+
+		/* do when there is more left to send */
+		SRF_RETURN_NEXT(funcctx, TimestampGetDatum(result));
+	}
+	else
+	{
+		/* do when there is no more left */
+		SRF_RETURN_DONE(funcctx);
+	}
+}
+
+/* generate_series_timestamptz()
+ * Generate the set of timestamps from start to finish by step
+ */
+Datum
+generate_series_timestamptz(PG_FUNCTION_ARGS)
+{
+	FuncCallContext *funcctx;
+	generate_series_timestamptz_fctx *fctx;
+	TimestampTz result;
+
+	/* stuff done only on the first call of the function */
+	if (SRF_IS_FIRSTCALL())
+	{
+		TimestampTz start = PG_GETARG_TIMESTAMPTZ(0);
+		TimestampTz finish = PG_GETARG_TIMESTAMPTZ(1);
+		Interval *step = PG_GETARG_INTERVAL_P(2);
+		MemoryContext oldcontext;
+		Interval interval_zero;
+
+		/* 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_timestamptz_fctx *)
+			palloc(sizeof(generate_series_timestamptz_fctx));
+
+		/*
+		 * Use fctx to keep state from call to call. Seed current with the
+		 * original start value
+		 */
+		fctx->current = start;
+		fctx->finish = finish;
+		fctx->step = *step;
+
+		/* Determine sign of the interval */
+		MemSet(&interval_zero, 0, sizeof(Interval));
+		fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+
+		if (fctx->step_sign == 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("step size cannot equal zero")));
+
+		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_sign > 0 ?
+		timestamp_cmp_internal(result, fctx->finish) <= 0 :
+		timestamp_cmp_internal(result, fctx->finish) >= 0)
+	{
+		/* increment current in preparation for next iteration */
+		fctx->current = DatumGetTimestampTz(
+			DirectFunctionCall2(timestamptz_pl_interval,
+								TimestampTzGetDatum(fctx->current),
+								PointerGetDatum(&fctx->step)));
+
+		/* do when there is more left to send */
+		SRF_RETURN_NEXT(funcctx, TimestampTzGetDatum(result));
+	}
+	else
+	{
+		/* do when there is no more left */
+		SRF_RETURN_DONE(funcctx);
+	}
+}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 751cb8e8f72..cea905dbd28 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.454 2008/05/04 21:13:35 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.455 2008/05/04 23:19:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200805041
+#define CATALOG_VERSION_NO	200805042
 
 #endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 8f9954692b3..75a0b5900d7 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.495 2008/05/04 21:13:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.496 2008/05/04 23:19:23 tgl Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -3872,6 +3872,10 @@ DATA(insert OID = 1068 (  generate_series PGNSP PGUID 12 1 1000 f f t t i 3 20 "
 DESCR("non-persistent series generator");
 DATA(insert OID = 1069 (  generate_series PGNSP PGUID 12 1 1000 f f t t i 2 20 "20 20" _null_ _null_ _null_ generate_series_int8 - _null_ _null_ ));
 DESCR("non-persistent series generator");
+DATA(insert OID = 938  (  generate_series PGNSP PGUID 12 1 1000 f f t t i 3 1114 "1114 1114 1186" _null_ _null_ _null_ generate_series_timestamp - _null_ _null_ ));
+DESCR("non-persistent series generator");
+DATA(insert OID = 939  (  generate_series PGNSP PGUID 12 1 1000 f f t t s 3 1184 "1184 1184 1186" _null_ _null_ _null_ generate_series_timestamptz - _null_ _null_ ));
+DESCR("non-persistent series generator");
 
 /* boolean aggregates */
 DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 f f t f i 2 16 "16 16" _null_ _null_ _null_ booland_statefunc - _null_ _null_ ));
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 31ad788ebf1..6b2efaf8d78 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -6,7 +6,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/timestamp.h,v 1.77 2008/05/04 21:13:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.78 2008/05/04 23:19:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -308,6 +308,9 @@ extern Datum clock_timestamp(PG_FUNCTION_ARGS);
 extern Datum pg_postmaster_start_time(PG_FUNCTION_ARGS);
 extern Datum pg_conf_load_time(PG_FUNCTION_ARGS);
 
+extern Datum generate_series_timestamp(PG_FUNCTION_ARGS);
+extern Datum generate_series_timestamptz(PG_FUNCTION_ARGS);
+
 /* Internal routines (not fmgr-callable) */
 
 extern TimestampTz GetCurrentTimestamp(void);