Re: pg_generate_sequence and info_schema patch (Was: SELECT - Mailing list pgsql-patches

From Joe Conway
Subject Re: pg_generate_sequence and info_schema patch (Was: SELECT
Date
Msg-id 401D677A.5050902@joeconway.com
Whole thread Raw
In response to Re: pg_generate_sequence and info_schema patch (Was: SELECT Question)  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: pg_generate_sequence and info_schema patch (Was: SELECT Question)  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-patches
Tom Lane wrote:
> Joe Conway <mail@joeconway.com> writes:
>>regression=# select * from pg_generate_sequence(8, 4);
>>ERROR:  finish is less than start
>
> Hm, would it be better just to return an empty set?  Certainly I'd
> expect pg_generate_sequence(1,0) to return an empty set with no error.

OK -- for this and other concerns below, I bit the bullet and decided to
support descending series and step sizes other than one. Now it does this:

regression=# select * from generate_series(8, 4);
  generate_series
-----------------
                8
                7
                6
                5
                4
(5 rows)

regression=# select * from generate_series(8, 4, 2);
  generate_series
-----------------
                8
                6
                4
(3 rows)

regression=# select * from generate_series(8000000000, 8000000004, 2);
  generate_series
-----------------
       8000000000
       8000000002
       8000000004
(3 rows)

regression=# select * from generate_series(8000000004, 8000000000, 3);
  generate_series
-----------------
       8000000004
       8000000001
(2 rows)

regression=# select * from generate_series(8000000004, 8000000000, -3);
ERROR:  step value must be greater than 0
HINT:  Use start greater than finish to create a descending series.

>>regression=# select * from pg_generate_sequence(3,8000000000);
>>ERROR:  range of start to finish is too large
>>HINT:  start to finish range must be less than 4294967295
>
> Is there a good reason for that restriction?  (I've never thought it was
> good design for the SRF API to assume that the number of iterations
> could be determined in advance, anyway.)

See above -- fixed. But I'm not going to try to return > 4 billion
values to illustrate ;-)

> Actually I think you could leave off the pg_ prefix
> and just make it generate_series or generate_set.

OK -- made it generate_series().

> Maybe the best documentation answer is to create a new subsection in the
> Functions chapter.  This may be our first standard set-returning
> function but I bet it will not be the last, so the shortness of the
> subsection doesn't bother me.

Agreed. I'll start this post-superbowl :-)

I'll apply in 24-48 hours if there are no further comments.

Thanks,

Joe

p.s. I did a `make distclean` prior to creating the attached diff. Do
the lines at the top, e.g.:
   ? src/bin/pg_id/.deps
   ? src/bin/pg_id/pg_id
   ...
indicate stuff not being cleaned up when it ought to be?


? src/bin/pg_id/.deps
? src/bin/pg_id/pg_id
? src/interfaces/ecpg/compatlib/libecpg_compat.so.1.0
? src/interfaces/ecpg/ecpglib/libecpg.so.4.0
? src/interfaces/ecpg/pgtypeslib/libpgtypes.so.1.0
? src/interfaces/libpgtcl/libpgtcl.so.2.4
? src/interfaces/libpq/libpq.so.3.1
Index: src/backend/catalog/information_schema.sql
===================================================================
RCS file: /cvsroot/pgsql-server/src/backend/catalog/information_schema.sql,v
retrieving revision 1.21
diff -c -r1.21 information_schema.sql
*** src/backend/catalog/information_schema.sql    17 Dec 2003 22:11:30 -0000    1.21
--- src/backend/catalog/information_schema.sql    1 Feb 2004 20:45:13 -0000
***************
*** 399,415 ****
  CREATE FUNCTION _pg_keypositions() RETURNS SETOF integer
      LANGUAGE sql
      IMMUTABLE
!     AS 'select 1 union all select 2 union all select 3 union all
!         select 4 union all select 5 union all select 6 union all
!         select 7 union all select 8 union all select 9 union all
!         select 10 union all select 11 union all select 12 union all
!         select 13 union all select 14 union all select 15 union all
!         select 16 union all select 17 union all select 18 union all
!         select 19 union all select 20 union all select 21 union all
!         select 22 union all select 23 union all select 24 union all
!         select 25 union all select 26 union all select 27 union all
!         select 28 union all select 29 union all select 30 union all
!         select 31 union all select 32';

  CREATE VIEW constraint_column_usage AS
      SELECT CAST(current_database() AS sql_identifier) AS table_catalog,
--- 399,407 ----
  CREATE FUNCTION _pg_keypositions() RETURNS SETOF integer
      LANGUAGE sql
      IMMUTABLE
!     AS 'select g.s
!         from generate_series(1,current_setting(''max_index_keys'')::int,1)
!         as g(s)';

  CREATE VIEW constraint_column_usage AS
      SELECT CAST(current_database() AS sql_identifier) AS table_catalog,
Index: src/backend/utils/adt/int.c
===================================================================
RCS file: /cvsroot/pgsql-server/src/backend/utils/adt/int.c,v
retrieving revision 1.59
diff -c -r1.59 int.c
*** src/backend/utils/adt/int.c    1 Dec 2003 21:52:37 -0000    1.59
--- src/backend/utils/adt/int.c    1 Feb 2004 20:45:13 -0000
***************
*** 34,39 ****
--- 34,40 ----
  #include <ctype.h>
  #include <limits.h>

+ #include "funcapi.h"
  #include "libpq/pqformat.h"
  #include "utils/builtins.h"

***************
*** 44,49 ****
--- 45,57 ----
  #define SHRT_MIN (-0x8000)
  #endif

+ typedef struct
+ {
+     int32        current;
+     int32        finish;
+     int32        step;
+ }    generate_series_fctx;
+
  /*****************************************************************************
   *     USER I/O ROUTINES                                                         *
   *****************************************************************************/
***************
*** 1021,1023 ****
--- 1029,1121 ----

      PG_RETURN_INT16(arg1 >> arg2);
  }
+
+ /*
+  * non-persistent numeric series generator
+  */
+ Datum
+ generate_series_int4(PG_FUNCTION_ARGS)
+ {
+     return generate_series_step_int4(fcinfo);
+ }
+
+ Datum
+ generate_series_step_int4(PG_FUNCTION_ARGS)
+ {
+     FuncCallContext           *funcctx;
+     generate_series_fctx   *fctx;
+     int32                    result;
+     MemoryContext            oldcontext;
+
+     /* stuff done only on the first call of the function */
+     if (SRF_IS_FIRSTCALL())
+     {
+         int32            start = PG_GETARG_INT32(0);
+         int32            finish = PG_GETARG_INT32(1);
+         int32            step = 1;
+
+         /* see if we were given an explicit step size */
+         if (PG_NARGS() == 3)
+         {
+             step = PG_GETARG_INT32(2);
+             if (step < 1)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                          errmsg("step value must be greater than 0"),
+                          errhint("Use start greater than finish to create"
+                                  " a descending series.")));
+         }
+
+         /* 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_fctx *) palloc(sizeof(generate_series_fctx));
+
+         /*
+          * Use fctx to keep state from call to call.
+          * Seed current with the original start value
+          */
+         fctx->current = start;
+         fctx->finish = finish;
+
+         /* use negative step if descending */
+         if (finish < start)
+             fctx->step = -step;
+         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, Int32GetDatum(result));
+     }
+     else
+         /* do when there is no more left */
+         SRF_RETURN_DONE(funcctx);
+ }
+
Index: src/backend/utils/adt/int8.c
===================================================================
RCS file: /cvsroot/pgsql-server/src/backend/utils/adt/int8.c,v
retrieving revision 1.50
diff -c -r1.50 int8.c
*** src/backend/utils/adt/int8.c    1 Dec 2003 21:52:37 -0000    1.50
--- src/backend/utils/adt/int8.c    1 Feb 2004 20:45:13 -0000
***************
*** 14,27 ****
--- 14,35 ----
  #include "postgres.h"

  #include <ctype.h>
+ #include <limits.h>
  #include <math.h>

+ #include "funcapi.h"
  #include "libpq/pqformat.h"
  #include "utils/int8.h"


  #define MAXINT8LEN        25

+ typedef struct
+ {
+     int64        current;
+     int64        finish;
+     int64        step;
+ }    generate_series_fctx;

  /***********************************************************************
   **
***************
*** 936,938 ****
--- 944,1036 ----

      PG_RETURN_TEXT_P(result);
  }
+
+ /*
+  * non-persistent numeric series generator
+  */
+ Datum
+ generate_series_int8(PG_FUNCTION_ARGS)
+ {
+     return generate_series_step_int8(fcinfo);
+ }
+
+ Datum
+ generate_series_step_int8(PG_FUNCTION_ARGS)
+ {
+     FuncCallContext           *funcctx;
+     generate_series_fctx   *fctx;
+     int64                    result;
+     MemoryContext            oldcontext;
+
+     /* stuff done only on the first call of the function */
+     if (SRF_IS_FIRSTCALL())
+     {
+         int64            start = PG_GETARG_INT64(0);
+         int64            finish = PG_GETARG_INT64(1);
+         int64            step = 1;
+
+         /* see if we were given an explicit step size */
+         if (PG_NARGS() == 3)
+         {
+             step = PG_GETARG_INT64(2);
+             if (step < 1)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                          errmsg("step value must be greater than 0"),
+                          errhint("Use start greater than finish to create"
+                                  " a descending series.")));
+         }
+
+         /* 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_fctx *) palloc(sizeof(generate_series_fctx));
+
+         /*
+          * Use fctx to keep state from call to call.
+          * Seed current with the original start value
+          */
+         fctx->current = start;
+         fctx->finish = finish;
+
+         /* use negative step if descending */
+         if (finish < start)
+             fctx->step = -step;
+         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, Int64GetDatum(result));
+     }
+     else
+         /* do when there is no more left */
+         SRF_RETURN_DONE(funcctx);
+ }
+
Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /cvsroot/pgsql-server/src/include/catalog/pg_proc.h,v
retrieving revision 1.318
diff -c -r1.318 pg_proc.h
*** src/include/catalog/pg_proc.h    6 Jan 2004 23:55:19 -0000    1.318
--- src/include/catalog/pg_proc.h    1 Feb 2004 20:45:14 -0000
***************
*** 3424,3429 ****
--- 3424,3440 ----
  DATA(insert OID = 2509 (  pg_get_expr           PGNSP PGUID 12 f f t f s 3 25 "25 26 16" _null_ pg_get_expr_ext -
_null_)); 
  DESCR("deparse an encoded expression with pretty-print option");

+ /* non-persistent series generator */
+ DATA(insert OID = 1066 (  generate_series PGNSP PGUID 12 f f t t v 3 23 "23 23 23" _null_ generate_series_step_int4 -
_null_)); 
+ DESCR("non-persistent series generator");
+ DATA(insert OID = 1067 (  generate_series PGNSP PGUID 12 f f t t v 2 23 "23 23" _null_ generate_series_int4 - _null_
));
+ DESCR("non-persistent series generator");
+
+ DATA(insert OID = 1068 (  generate_series PGNSP PGUID 12 f f t t v 3 20 "20 20 20" _null_ generate_series_step_int8 -
_null_)); 
+ DESCR("non-persistent series generator");
+ DATA(insert OID = 1069 (  generate_series PGNSP PGUID 12 f f t t v 2 20 "20 20" _null_ generate_series_int8 - _null_
));
+ DESCR("non-persistent series generator");
+

  /*
   * Symbolic values for provolatile column: these indicate whether the result
Index: src/include/utils/builtins.h
===================================================================
RCS file: /cvsroot/pgsql-server/src/include/utils/builtins.h,v
retrieving revision 1.233
diff -c -r1.233 builtins.h
*** src/include/utils/builtins.h    19 Jan 2004 19:04:40 -0000    1.233
--- src/include/utils/builtins.h    1 Feb 2004 20:45:14 -0000
***************
*** 175,180 ****
--- 175,182 ----
  extern Datum int2not(PG_FUNCTION_ARGS);
  extern Datum int2shl(PG_FUNCTION_ARGS);
  extern Datum int2shr(PG_FUNCTION_ARGS);
+ extern Datum generate_series_int4(PG_FUNCTION_ARGS);
+ extern Datum generate_series_step_int4(PG_FUNCTION_ARGS);

  /* name.c */
  extern Datum namein(PG_FUNCTION_ARGS);
Index: src/include/utils/int8.h
===================================================================
RCS file: /cvsroot/pgsql-server/src/include/utils/int8.h,v
retrieving revision 1.40
diff -c -r1.40 int8.h
*** src/include/utils/int8.h    1 Dec 2003 21:52:38 -0000    1.40
--- src/include/utils/int8.h    1 Feb 2004 20:45:14 -0000
***************
*** 112,115 ****
--- 112,118 ----
  extern Datum int8_text(PG_FUNCTION_ARGS);
  extern Datum text_int8(PG_FUNCTION_ARGS);

+ extern Datum generate_series_int8(PG_FUNCTION_ARGS);
+ extern Datum generate_series_step_int8(PG_FUNCTION_ARGS);
+
  #endif   /* INT8_H */

pgsql-patches by date:

Previous
From: Markus Bertheau
Date:
Subject: Re: Patch for psql startup clarity
Next
From: Tom Lane
Date:
Subject: Re: Patch for psql startup clarity