Re: Infinite loop for generate_series with timestamp arguments - Mailing list pgsql-general

From Tom Lane
Subject Re: Infinite loop for generate_series with timestamp arguments
Date
Msg-id 4081027.1741037421@sss.pgh.pa.us
Whole thread Raw
In response to Re: Infinite loop for generate_series with timestamp arguments  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-general
I wrote:
> We could perhaps do better by doing the initial addition of the
> interval and seeing if that produces a value greater than, less
> than, or equal to the start timestamp.  But I'm afraid that
> doesn't move the goalposts very far, because as this example
> shows, we might get different results in different months.

> Another idea is to check, after doing each addition, to make
> sure that the timestamp actually advanced in the expected
> direction.  But should we error out if not, or just stop?

Here's a very POC-y patch using both of these ideas (and
choosing to error out if the interval changes sign).
If we go this way, generate_series_timestamptz would need
similar changes, and some regression test cases would be
appropriate.  I'm not sure if the documentation needs
adjustment; it doesn't talk about the difficulty of
identifying the sign of an interval.

            regards, tom lane

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 9682f9dbdca..202bbd1edcd 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -6622,6 +6622,7 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
     FuncCallContext *funcctx;
     generate_series_timestamp_fctx *fctx;
     Timestamp    result;
+    Timestamp    nextval = 0;

     /* stuff done only on the first call of the function */
     if (SRF_IS_FIRSTCALL())
@@ -6651,18 +6652,25 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
         fctx->finish = finish;
         fctx->step = *step;

-        /* Determine sign of the interval */
-        fctx->step_sign = interval_sign(&fctx->step);
-
-        if (fctx->step_sign == 0)
+        if (INTERVAL_NOT_FINITE((&fctx->step)))
             ereport(ERROR,
                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                     errmsg("step size cannot equal zero")));
+                     errmsg("step size cannot be infinite")));

-        if (INTERVAL_NOT_FINITE((&fctx->step)))
+        /*
+         * Compute the next series value so that we can identify the step
+         * direction.  This seems more reliable than trusting interval_sign().
+         */
+        nextval =
+            DatumGetTimestamp(DirectFunctionCall2(timestamp_pl_interval,
+                                                  TimestampGetDatum(start),
+                                                  PointerGetDatum(&fctx->step)));
+        fctx->step_sign = timestamp_cmp_internal(nextval, start);
+
+        if (fctx->step_sign == 0)
             ereport(ERROR,
                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                     errmsg("step size cannot be infinite")));
+                     errmsg("step size cannot equal zero")));

         funcctx->user_fctx = fctx;
         MemoryContextSwitchTo(oldcontext);
@@ -6682,9 +6690,18 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
         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)));
+        if (nextval)
+            fctx->current = nextval;    /* already calculated it */
+        else
+            fctx->current =
+                DatumGetTimestamp(DirectFunctionCall2(timestamp_pl_interval,
+                                                      TimestampGetDatum(result),
+                                                      PointerGetDatum(&fctx->step)));
+        /* check for directional instability */
+        if (fctx->step_sign != timestamp_cmp_internal(fctx->current, result))
+            ereport(ERROR,
+                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                     errmsg("step size changed sign")));

         /* do when there is more left to send */
         SRF_RETURN_NEXT(funcctx, TimestampGetDatum(result));

pgsql-general by date:

Previous
From: Tom Lane
Date:
Subject: Re: Infinite loop for generate_series with timestamp arguments
Next
From: chandan Kumar
Date:
Subject: Review my steps for rollback to restore point