As mentioned above, here is my contrib/tablefunc patch. It includes
authorBruce Momjian
Tue, 30 Jul 2002 16:31:11 +0000 (16:31 +0000)
committerBruce Momjian
Tue, 30 Jul 2002 16:31:11 +0000 (16:31 +0000)
three functions which exercise the tablefunc API.

show_all_settings()
   - returns the same information as SHOW ALL, but as a query result

normal_rand(int numvals, float8 mean, float8 stddev, int seed)
   - returns a set of normally distributed float8 values
   - This routine implements Algorithm P (Polar method for normal
     deviates) from Knuth's _The_Art_of_Computer_Programming_, Volume 2,
     3rd ed., pages 122-126. Knuth cites his source as "The polar
     method", G. E. P. Box, M. E. Muller, and G. Marsaglia,
     _Annals_Math,_Stat._ 29 (1958), 610-611.

crosstabN(text sql)
   - returns a set of row_name plus N category value columns
   - crosstab2(), crosstab3(), and crosstab4() are defined for you,
     but you can create additional crosstab functions per directions
     in the README.

Joe Conway

contrib/README
contrib/tablefunc/Makefile [new file with mode: 0644]
contrib/tablefunc/README.tablefunc [new file with mode: 0644]
contrib/tablefunc/tablefunc-test.sql [new file with mode: 0644]
contrib/tablefunc/tablefunc.c [new file with mode: 0644]
contrib/tablefunc/tablefunc.h [new file with mode: 0644]
contrib/tablefunc/tablefunc.sql.in [new file with mode: 0644]

index 6ae9b2851ca08894e419f570c45861cd94d7e8cc..3bb288367795918f5a6975de2dd7728116817c9c 100644 (file)
@@ -186,6 +186,10 @@ string -
    C-like input/output conversion routines for strings
    by Massimo Dal Zotto 
 
+tablefunc -
+   Examples of tables returning functions
+   by Joe Conway 
+
 tips/apache_logging -
    Getting Apache to log to PostgreSQL
    by Terry Mackintosh 
diff --git a/contrib/tablefunc/Makefile b/contrib/tablefunc/Makefile
new file mode 100644 (file)
index 0000000..0f9c540
--- /dev/null
@@ -0,0 +1,9 @@
+subdir = contrib/tablefunc
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+
+MODULES = tablefunc
+DATA_built = tablefunc.sql
+DOCS = README.tablefunc
+
+include $(top_srcdir)/contrib/contrib-global.mk
diff --git a/contrib/tablefunc/README.tablefunc b/contrib/tablefunc/README.tablefunc
new file mode 100644 (file)
index 0000000..b3b663a
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * tablefunc
+ *
+ * Sample to demonstrate C functions which return setof scalar
+ * and setof composite.
+ * Joe Conway 
+ *
+ * Copyright 2002 by PostgreSQL Global Development Group
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without a written agreement
+ * is hereby granted, provided that the above copyright notice and this
+ * paragraph and the following two paragraphs appear in all copies.
+ * 
+ * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+ * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+ * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ */
+Version 0.1 (20 July, 2002):
+  First release
+
+Release Notes:
+
+  Version 0.1
+    - initial release    
+
+Installation:
+  Place these files in a directory called 'tablefunc' under 'contrib' in the
+  PostgreSQL source tree. Then run:
+
+    make
+    make install
+
+  You can use tablefunc.sql to create the functions in your database of choice, e.g.
+
+    psql -U postgres template1 < tablefunc.sql
+
+  installs following functions into database template1:
+
+    show_all_settings()
+      - returns the same information as SHOW ALL, but as a query result
+
+    normal_rand(int numvals, float8 mean, float8 stddev, int seed)
+      - returns a set of normally distributed float8 values
+
+    crosstabN(text sql)
+      - returns a set of row_name plus N category value columns
+      - crosstab2(), crosstab3(), and crosstab4() are defined for you,
+        but you can create additional crosstab functions per the instructions
+        in the documentation below.
+
+Documentation
+==================================================================
+Name
+
+show_all_settings() - returns the same information as SHOW ALL,
+       but as a query result.
+
+Synopsis
+
+show_all_settings()
+
+Inputs
+
+  none
+
+Outputs
+
+  Returns setof tablefunc_config_settings which is defined by:
+    CREATE VIEW tablefunc_config_settings AS
+    SELECT
+      ''::TEXT AS name,
+      ''::TEXT AS setting;
+
+Example usage
+
+  test=# select * from show_all_settings();
+             name              |                setting
+-------------------------------+---------------------------------------
+ australian_timezones          | off
+ authentication_timeout        | 60
+ checkpoint_segments           | 3
+    .
+    .
+    .
+ wal_debug                     | 0
+ wal_files                     | 0
+ wal_sync_method               | fdatasync
+(94 rows)
+
+==================================================================
+Name
+
+normal_rand(int, float8, float8, int) - returns a set of normally
+       distributed float8 values
+
+Synopsis
+
+normal_rand(int numvals, float8 mean, float8 stddev, int seed)
+
+Inputs
+
+  numvals
+    the number of random values to be returned from the function
+
+  mean
+    the mean of the normal distribution of values
+
+  stddev
+    the standard deviation of the normal distribution of values
+
+  seed
+    a seed value for the pseudo-random number generator
+
+Outputs
+
+  Returns setof float8, where the returned set of random values are normally
+    distributed (Gaussian distribution)
+
+Example usage
+
+  test=# SELECT * FROM
+  test=# normal_rand(1000, 5, 3, EXTRACT(SECONDS FROM CURRENT_TIME(0))::int);
+     normal_rand
+----------------------
+     1.56556322244898
+     9.10040991424657
+     5.36957140345079
+   -0.369151492880995
+    0.283600703686639
+       .
+       .
+       .
+     4.82992125404908
+     9.71308014517282
+     2.49639286969028
+(1000 rows)
+
+  Returns 1000 values with a mean of 5 and a standard deviation of 3.
+
+==================================================================
+Name
+
+crosstabN(text) - returns a set of row_name plus N category value columns
+
+Synopsis
+
+crosstabN(text sql)
+
+Inputs
+
+  sql
+
+    A SQL statement which produces the source set of data. The SQL statement
+    must return one row_name column, one category column, and one value
+    column.
+
+    e.g. provided sql must produce a set something like:
+
+             row_name    cat    value
+            ----------+-------+-------
+              row1      cat1    val1
+              row1      cat2    val2
+              row1      cat3    val3
+              row1      cat4    val4
+              row2      cat1    val5
+              row2      cat2    val6
+              row2      cat3    val7
+              row2      cat4    val8
+
+Outputs
+
+  Returns setof tablefunc_crosstab_N, which is defined by:
+
+    CREATE VIEW tablefunc_crosstab_N AS
+      SELECT
+        ''::TEXT AS row_name,
+        ''::TEXT AS category_1,
+        ''::TEXT AS category_2,
+            .
+            .
+            .
+        ''::TEXT AS category_N;
+
+     for the default installed functions, where N is 2, 3, or 4.
+
+     e.g. the provided crosstab2 function produces a set something like:
+                      <== values  columns ==>
+           row_name   category_1   category_2
+           ---------+------------+------------
+             row1        val1         val2
+             row2        val5         val6
+
+Notes
+
+  1. The sql result must be ordered by 1,2.
+
+  2. The number of values columns depends on the tuple description
+     of the function's declared return type.
+
+  3. Missing values (i.e. not enough adjacent rows of same row_name to
+     fill the number of result values columns) are filled in with nulls.
+
+  4. Extra values (i.e. too many adjacent rows of same row_name to fill
+     the number of result values columns) are skipped.
+
+  5. Rows with all nulls in the values columns are skipped.
+
+  6. The installed defaults are for illustration purposes. You
+     can create your own return types and functions based on the
+     crosstab() function of the installed library.
+
+     The return type must have a first column that matches the data
+     type of the sql set used as its source. The subsequent category
+     columns must have the same data type as the value column of the
+     sql result set.
+
+     Create a VIEW to define your return type, similar to the VIEWS
+     in the provided installation script. Then define a unique function
+     name accepting one text parameter and returning setof your_view_name.
+     For example, if your source data produces row_names that are TEXT,
+     and values that are FLOAT8, and you want 5 category columns:
+
+      CREATE VIEW my_crosstab_float8_5_cols AS
+        SELECT
+          ''::TEXT AS row_name,
+          0::FLOAT8 AS category_1,
+          0::FLOAT8 AS category_2,
+          0::FLOAT8 AS category_3,
+          0::FLOAT8 AS category_4,
+          0::FLOAT8 AS category_5;
+
+      CREATE OR REPLACE FUNCTION crosstab_float8_5_cols(text)
+        RETURNS setof my_crosstab_float8_5_cols
+        AS '$libdir/tablefunc','crosstab' LANGUAGE 'c' STABLE STRICT;
+
+Example usage
+
+create table ct(id serial, rowclass text, rowid text, attribute text, value text);
+insert into ct(rowclass, rowid, attribute, value) values('group1','test1','att1','val1');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test1','att2','val2');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test1','att3','val3');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test1','att4','val4');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test2','att1','val5');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test2','att2','val6');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test2','att3','val7');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test2','att4','val8');
+
+select * from crosstab3(
+  'select rowid, attribute, value
+   from ct
+   where rowclass = ''group1''
+   and (attribute = ''att2'' or attribute = ''att3'') order by 1,2;');
+
+ row_name | category_1 | category_2 | category_3
+----------+------------+------------+------------
+ test1    | val2       | val3       |
+ test2    | val6       | val7       |
+(2 rows)
+
+==================================================================
+-- Joe Conway
+
diff --git a/contrib/tablefunc/tablefunc-test.sql b/contrib/tablefunc/tablefunc-test.sql
new file mode 100644 (file)
index 0000000..141894b
--- /dev/null
@@ -0,0 +1,47 @@
+--
+-- show_all_settings()
+--
+SELECT * FROM show_all_settings();
+
+--
+-- normal_rand()
+--
+SELECT * FROM normal_rand(100, 250, 5, EXTRACT(SECONDS FROM CURRENT_TIME(0))::int);
+
+--
+-- crosstab()
+--
+create table ct(id serial, rowclass text, rowid text, attribute text, value text);
+
+insert into ct(rowclass, rowid, attribute, value) values('group1','test1','att1','val1');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test1','att2','val2');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test1','att3','val3');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test1','att4','val4');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test2','att1','val5');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test2','att2','val6');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test2','att3','val7');
+insert into ct(rowclass, rowid, attribute, value) values('group1','test2','att4','val8');
+insert into ct(rowclass, rowid, attribute, value) values('group2','test3','att1','val1');
+insert into ct(rowclass, rowid, attribute, value) values('group2','test3','att2','val2');
+insert into ct(rowclass, rowid, attribute, value) values('group2','test3','att3','val3');
+insert into ct(rowclass, rowid, attribute, value) values('group2','test4','att1','val4');
+insert into ct(rowclass, rowid, attribute, value) values('group2','test4','att2','val5');
+insert into ct(rowclass, rowid, attribute, value) values('group2','test4','att3','val6');
+
+select * from crosstab2('select rowid, attribute, value from ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') order by 1,2;');
+select * from crosstab3('select rowid, attribute, value from ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') order by 1,2;');
+select * from crosstab4('select rowid, attribute, value from ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') order by 1,2;');
+
+select * from crosstab2('select rowid, attribute, value from ct where rowclass = ''group1'' order by 1,2;');
+select * from crosstab3('select rowid, attribute, value from ct where rowclass = ''group1'' order by 1,2;');
+select * from crosstab4('select rowid, attribute, value from ct where rowclass = ''group1'' order by 1,2;');
+
+select * from crosstab2('select rowid, attribute, value from ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') order by 1,2;');
+select * from crosstab3('select rowid, attribute, value from ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') order by 1,2;');
+select * from crosstab4('select rowid, attribute, value from ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') order by 1,2;');
+
+select * from crosstab2('select rowid, attribute, value from ct where rowclass = ''group2'' order by 1,2;');
+select * from crosstab3('select rowid, attribute, value from ct where rowclass = ''group2'' order by 1,2;');
+select * from crosstab4('select rowid, attribute, value from ct where rowclass = ''group2'' order by 1,2;');
+
+
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
new file mode 100644 (file)
index 0000000..236d833
--- /dev/null
@@ -0,0 +1,664 @@
+/*
+ * tablefunc
+ *
+ * Sample to demonstrate C functions which return setof scalar
+ * and setof composite.
+ * Joe Conway 
+ *
+ * Copyright 2002 by PostgreSQL Global Development Group
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without a written agreement
+ * is hereby granted, provided that the above copyright notice and this
+ * paragraph and the following two paragraphs appear in all copies.
+ * 
+ * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+ * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+ * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ */
+#include 
+#include 
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "funcapi.h"
+#include "executor/spi.h" 
+#include "utils/builtins.h"
+#include "utils/guc.h"
+
+#include "tablefunc.h"
+
+static bool compatTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
+static void get_normal_pair(float8 *x1, float8 *x2);
+
+typedef struct
+{
+   float8  mean;       /* mean of the distribution */
+   float8  stddev;     /* stddev of the distribution */
+   float8  carry_val;  /* hold second generated value */
+   bool    use_carry;  /* use second generated value */
+}  normal_rand_fctx;
+
+typedef struct
+{
+   SPITupleTable  *spi_tuptable;   /* sql results from user query */
+   char           *lastrowid;      /* rowid of the last tuple sent */
+}  crosstab_fctx;
+
+#define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(cstrp)))
+#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))
+#define xpfree(var_) \
+   do { \
+       if (var_ != NULL) \
+       { \
+           pfree(var_); \
+           var_ = NULL; \
+       } \
+   } while (0)
+
+/*
+ * show_all_settings - equiv to SHOW ALL command but implemented as
+ * a Table Function.
+ */
+PG_FUNCTION_INFO_V1(show_all_settings);
+Datum
+show_all_settings(PG_FUNCTION_ARGS)
+{
+   FuncCallContext    *funcctx;
+   TupleDesc           tupdesc;
+   int                 call_cntr;
+   int                 max_calls;
+   TupleTableSlot     *slot;
+   AttInMetadata      *attinmeta;
+
+   /* stuff done only on the first call of the function */
+   if(SRF_IS_FIRSTCALL())
+   {
+       Oid         foid = fcinfo->flinfo->fn_oid;
+       Oid         functypeid;
+
+       /* create a function context for cross-call persistence */
+       funcctx = SRF_FIRSTCALL_INIT();
+
+       /* get the typeid that represents our return type */
+       functypeid = foidGetTypeId(foid);
+
+       /* Build a tuple description for a funcrelid tuple */
+       tupdesc = TypeGetTupleDesc(functypeid, NIL);
+
+       /* allocate a slot for a tuple with this tupdesc */
+       slot = TupleDescGetSlot(tupdesc);
+
+       /* assign slot to function context */
+       funcctx->slot = slot;
+
+       /*
+        * Generate attribute metadata needed later to produce tuples from raw
+        * C strings
+        */
+       attinmeta = TupleDescGetAttInMetadata(tupdesc);
+       funcctx->attinmeta = attinmeta;
+
+       /* total number of tuples to be returned */
+       funcctx->max_calls = GetNumConfigOptions();
+    }
+
+   /* stuff done on every call of the function */
+   funcctx = SRF_PERCALL_SETUP();
+
+   call_cntr = funcctx->call_cntr;
+   max_calls = funcctx->max_calls;
+   slot = funcctx->slot;
+   attinmeta = funcctx->attinmeta;
+
+   if (call_cntr < max_calls)  /* do when there is more left to send */
+   {
+       char       **values;
+       char       *varname;
+       char       *varval;
+       bool        noshow;
+       HeapTuple   tuple;
+       Datum       result;
+
+       /*
+        * Get the next visible GUC variable name and value
+        */
+       do
+       {
+           varval = GetConfigOptionByNum(call_cntr, (const char **) &varname, &noshow);
+           if (noshow)
+           {
+               /* varval is a palloc'd copy, so free it */
+               xpfree(varval);
+
+               /* bump the counter and get the next config setting */
+               call_cntr = ++funcctx->call_cntr;
+
+               /* make sure we haven't gone too far now */
+               if (call_cntr >= max_calls)
+                   SRF_RETURN_DONE(funcctx);
+           }
+       } while (noshow);
+
+       /*
+        * Prepare a values array for storage in our slot.
+        * This should be an array of C strings which will
+        * be processed later by the appropriate "in" functions.
+        */
+       values = (char **) palloc(2 * sizeof(char *));
+       values[0] = pstrdup(varname);
+       values[1] = varval; /* varval is already a palloc'd copy */
+
+       /* build a tuple */
+       tuple = BuildTupleFromCStrings(attinmeta, values);
+
+       /* make the tuple into a datum */
+       result = TupleGetDatum(slot, tuple);
+
+       /* Clean up */
+       xpfree(values[0]);
+       xpfree(values[1]);
+       xpfree(values);
+
+       SRF_RETURN_NEXT(funcctx, result);
+   }
+   else    /* do when there is no more left */
+   {
+       SRF_RETURN_DONE(funcctx);
+   }
+}
+
+/*
+ * normal_rand - return requested number of random values
+ * with a Gaussian (Normal) distribution.
+ *
+ * inputs are int numvals, float8 lower_bound, and float8 upper_bound
+ * returns float8
+ */
+PG_FUNCTION_INFO_V1(normal_rand);
+Datum
+normal_rand(PG_FUNCTION_ARGS)
+{
+   FuncCallContext    *funcctx;
+   int                 call_cntr;
+   int                 max_calls;
+   normal_rand_fctx   *fctx;
+   float8              mean;
+   float8              stddev;
+   float8              carry_val;
+   bool                use_carry;
+
+   /* stuff done only on the first call of the function */
+   if(SRF_IS_FIRSTCALL())
+   {
+       /* create a function context for cross-call persistence */
+       funcctx = SRF_FIRSTCALL_INIT();
+
+       /* total number of tuples to be returned */
+       funcctx->max_calls = PG_GETARG_UINT32(0);
+
+       /* allocate memory for user context */
+       fctx = (normal_rand_fctx *) palloc(sizeof(normal_rand_fctx));
+
+       /*
+        * Use fctx to keep track of upper and lower bounds
+        * from call to call. It will also be used to carry over
+        * the spare value we get from the Box-Muller algorithm
+        * so that we only actually calculate a new value every
+        * other call.
+        */
+       fctx->mean = PG_GETARG_FLOAT8(1);
+       fctx->stddev = PG_GETARG_FLOAT8(2);
+       fctx->carry_val = 0;
+       fctx->use_carry = false;
+
+       funcctx->user_fctx = fctx;
+
+       /*
+        * we might actually get passed a negative number, but for this
+        * purpose it doesn't matter, just cast it as an unsigned value
+        */
+       srandom(PG_GETARG_UINT32(3));
+    }
+
+   /* stuff done on every call of the function */
+   funcctx = SRF_PERCALL_SETUP();
+
+   call_cntr = funcctx->call_cntr;
+   max_calls = funcctx->max_calls;
+   fctx = funcctx->user_fctx;
+   mean = fctx->mean;
+   stddev = fctx->stddev;
+   carry_val = fctx->carry_val;
+   use_carry = fctx->use_carry;
+   if (call_cntr < max_calls)  /* do when there is more left to send */
+   {
+       float8      result;
+
+       if(use_carry)
+       {
+           /*
+            * reset use_carry and use second value obtained on last pass
+            */
+           fctx->use_carry = false;
+           result = carry_val;
+       }
+       else
+       {
+           float8      normval_1;
+           float8      normval_2;
+
+           /* Get the next two normal values */
+           get_normal_pair(&normval_1, &normval_2);
+
+           /* use the first */
+           result = mean + (stddev * normval_1);
+
+           /* and save the second */
+           fctx->carry_val = mean + (stddev * normval_2);
+           fctx->use_carry = true;
+       }
+
+       /* send the result */
+       SRF_RETURN_NEXT(funcctx, Float8GetDatum(result));
+   }
+   else    /* do when there is no more left */
+   {
+       SRF_RETURN_DONE(funcctx);
+   }
+}
+
+/*
+ * get_normal_pair()
+ * Assigns normally distributed (Gaussian) values to a pair of provided
+ * parameters, with mean 0, standard deviation 1.
+ *
+ * This routine implements Algorithm P (Polar method for normal deviates)
+ * from Knuth's _The_Art_of_Computer_Programming_, Volume 2, 3rd ed., pages
+ * 122-126. Knuth cites his source as "The polar method", G. E. P. Box, M. E.
+ * Muller, and G. Marsaglia, _Annals_Math,_Stat._ 29 (1958), 610-611.
+ *
+ */
+static void
+get_normal_pair(float8 *x1, float8 *x2)
+{
+   float8  u1, u2, v1, v2, s;
+
+   for(;;)
+   {
+       u1 = (float8) random() / (float8) RAND_MAX;
+       u2 = (float8) random() / (float8) RAND_MAX;
+
+       v1 = (2.0 * u1) - 1.0;
+       v2 = (2.0 * u2) - 1.0;
+
+       s = pow(v1, 2) + pow(v2, 2);
+
+       if (s >= 1.0)
+           continue;
+
+       if (s == 0)
+       {
+           *x1 = 0;
+           *x2 = 0;
+       }
+       else
+       {
+           *x1 = v1 * sqrt((-2.0 * log(s)) / s);
+           *x2 = v2 * sqrt((-2.0 * log(s)) / s);
+       }
+
+       return;
+   }
+}
+
+/*
+ * crosstab - create a crosstab of rowids and values columns from a
+ * SQL statement returning one rowid column, one category column,
+ * and one value column.
+ *
+ * e.g. given sql which produces:
+ *
+ *         rowid   cat     value
+ *         ------+-------+-------
+ *             row1    cat1    val1
+ *             row1    cat2    val2
+ *             row1    cat3    val3
+ *             row1    cat4    val4
+ *             row2    cat1    val5
+ *             row2    cat2    val6
+ *             row2    cat3    val7
+ *             row2    cat4    val8
+ *
+ * crosstab returns:
+ *                 <===== values columns =====>
+ *         rowid   cat1    cat2    cat3    cat4
+ *         ------+-------+-------+-------+-------
+ *             row1    val1    val2    val3    val4
+ *             row2    val5    val6    val7    val8
+ *
+ * NOTES:
+ * 1. SQL result must be ordered by 1,2.
+ * 2. The number of values columns depends on the tuple description
+ *    of the function's declared return type.
+ * 2. Missing values (i.e. not enough adjacent rows of same rowid to
+ *    fill the number of result values columns) are filled in with nulls.
+ * 3. Extra values (i.e. too many adjacent rows of same rowid to fill
+ *    the number of result values columns) are skipped.
+ * 4. Rows with all nulls in the values columns are skipped.
+ */
+PG_FUNCTION_INFO_V1(crosstab);
+Datum
+crosstab(PG_FUNCTION_ARGS)
+{
+   FuncCallContext    *funcctx;
+   TupleDesc           ret_tupdesc;
+   int                 call_cntr;
+   int                 max_calls;
+   TupleTableSlot     *slot;
+   AttInMetadata      *attinmeta;
+   SPITupleTable      *spi_tuptable;
+   TupleDesc           spi_tupdesc;
+   char               *lastrowid;
+   crosstab_fctx      *fctx;
+   int                 i;
+   int                 num_categories;
+
+   /* stuff done only on the first call of the function */
+   if(SRF_IS_FIRSTCALL())
+   {
+       char       *sql = GET_STR(PG_GETARG_TEXT_P(0));
+       Oid         foid = fcinfo->flinfo->fn_oid;
+       Oid         functypeid;
+       TupleDesc   tupdesc;
+       int         ret;
+       int         proc;
+
+       /* create a function context for cross-call persistence */
+       funcctx = SRF_FIRSTCALL_INIT();
+
+       /* get the typeid that represents our return type */
+       functypeid = foidGetTypeId(foid);
+
+       /* Build a tuple description for a funcrelid tuple */
+       tupdesc = TypeGetTupleDesc(functypeid, NIL);
+
+       /* allocate a slot for a tuple with this tupdesc */
+       slot = TupleDescGetSlot(tupdesc);
+
+       /* assign slot to function context */
+       funcctx->slot = slot;
+
+       /*
+        * Generate attribute metadata needed later to produce tuples from raw
+        * C strings
+        */
+       attinmeta = TupleDescGetAttInMetadata(tupdesc);
+       funcctx->attinmeta = attinmeta;
+
+       /* Connect to SPI manager */
+       if ((ret = SPI_connect()) < 0)
+           elog(ERROR, "crosstab: SPI_connect returned %d", ret);
+
+       /* Retrieve the desired rows */
+       ret = SPI_exec(sql, 0);
+       proc = SPI_processed;
+
+       /* Check for qualifying tuples */
+       if ((ret == SPI_OK_SELECT) && (proc > 0))
+       {
+           spi_tuptable = SPI_tuptable;
+           spi_tupdesc = spi_tuptable->tupdesc;
+
+           /*
+            * The provided SQL query must always return three columns.
+            *
+            * 1. rowid     the label or identifier for each row in the final
+            *              result
+            * 2. category  the label or identifier for each column in the
+            *              final result
+            * 3. values    the value for each column in the final result
+            */
+           if (spi_tupdesc->natts != 3)
+               elog(ERROR, "crosstab: provided SQL must return 3 columns;"
+                               " a rowid, a category, and a values column");
+
+           /*
+            * Check that return tupdesc is compatible with the one we got
+            * from ret_relname, at least based on number and type of
+            * attributes
+            */
+           if (!compatTupleDescs(tupdesc, spi_tupdesc))
+               elog(ERROR, "crosstab: return and sql tuple descriptions are"
+                                       " incompatible");
+
+           /* allocate memory for user context */
+           fctx = (crosstab_fctx *) palloc(sizeof(crosstab_fctx));
+
+           /*
+            * OK, we have data, and it seems to be valid, so save it
+            * for use across calls
+            */
+           fctx->spi_tuptable = spi_tuptable;
+           fctx->lastrowid = NULL;
+           funcctx->user_fctx = fctx;
+
+           /* total number of tuples to be returned */
+           funcctx->max_calls = proc;
+       }
+       else
+       {
+           /* no qualifying tuples */
+           funcctx->max_calls = 0;
+       }
+    }
+
+   /* stuff done on every call of the function */
+   funcctx = SRF_PERCALL_SETUP();
+
+   /*
+    * initialize per-call variables
+    */
+   call_cntr = funcctx->call_cntr;
+   max_calls = funcctx->max_calls;
+
+   /* return slot for our tuple */
+   slot = funcctx->slot;
+
+   /* user context info */
+   fctx = (crosstab_fctx *) funcctx->user_fctx;
+   lastrowid = fctx->lastrowid;
+   spi_tuptable = fctx->spi_tuptable;
+
+   /* the sql tuple */
+   spi_tupdesc = spi_tuptable->tupdesc;
+
+   /* attribute return type and return tuple description */
+   attinmeta = funcctx->attinmeta;
+   ret_tupdesc = attinmeta->tupdesc;
+
+   /* the return tuple always must have 1 rowid + num_categories columns */
+   num_categories = ret_tupdesc->natts - 1;
+   if (call_cntr < max_calls)  /* do when there is more left to send */
+   {
+       HeapTuple   tuple;
+       Datum       result;
+       char      **values;
+       bool        allnulls = true;
+
+       while (true)
+       {
+           /* allocate space */
+           values = (char **) palloc((1 + num_categories) * sizeof(char *));
+
+           /* and make sure it's clear */
+           memset(values, '\0', (1 + num_categories) * sizeof(char *));
+
+           /*
+            * now loop through the sql results and assign each value
+            * in sequence to the next category
+            */
+           for (i = 0; i < num_categories; i++)
+           {
+               HeapTuple   spi_tuple;
+               char       *rowid;
+
+               /* see if we've gone too far already */
+               if (call_cntr >= max_calls)
+                   break;
+
+               /* get the next sql result tuple */
+               spi_tuple = spi_tuptable->vals[call_cntr];
+
+               /* get the rowid from the current sql result tuple */
+               rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
+
+               /* 
+                * If this is the first pass through the values for this rowid
+                * set it, otherwise make sure it hasn't changed on us. Also
+                * check to see if the rowid is the same as that of the last
+                * tuple sent -- if so, skip this tuple entirely
+                */
+               if (i == 0)
+                   values[0] = pstrdup(rowid);
+
+               if ((rowid != NULL) && (strcmp(rowid, values[0]) == 0))
+               {
+                   if ((lastrowid != NULL) && (strcmp(rowid, lastrowid) == 0))
+                       break;
+                   else if (allnulls == true)
+                       allnulls = false;
+
+                   /*
+                    * Get the next category item value, which is alway attribute
+                    * number three.
+                    *
+                    * Be careful to sssign the value to the array index based
+                    * on which category we are presently processing.
+                    */
+                   values[1 + i] = SPI_getvalue(spi_tuple, spi_tupdesc, 3);
+
+                   /*
+                    * increment the counter since we consume a row
+                    * for each category, but not for last pass
+                    * because the API will do that for us
+                    */
+                   if (i < (num_categories - 1))
+                       call_cntr = ++funcctx->call_cntr;
+               }
+               else
+               {
+                   /*
+                    * We'll fill in NULLs for the missing values,
+                    * but we need to decrement the counter since
+                    * this sql result row doesn't belong to the current
+                    * output tuple.
+                    */
+                   call_cntr = --funcctx->call_cntr;
+                   break;
+               }
+
+               if (rowid != NULL)
+                   xpfree(rowid);
+           }
+
+           xpfree(fctx->lastrowid);
+
+           if (values[0] != NULL)
+               lastrowid = fctx->lastrowid = pstrdup(values[0]);
+
+           if (!allnulls)
+           {
+               /* build the tuple */
+               tuple = BuildTupleFromCStrings(attinmeta, values);
+
+               /* make the tuple into a datum */
+               result = TupleGetDatum(slot, tuple);
+
+               /* Clean up */
+               for (i = 0; i < num_categories + 1; i++)
+                   if (values[i] != NULL)
+                       xpfree(values[i]);
+               xpfree(values);
+
+               SRF_RETURN_NEXT(funcctx, result);
+           }
+           else
+           {
+               /*
+                * Skipping this tuple entirely, but we need to advance
+                * the counter like the API would if we had returned
+                * one.
+                */
+               call_cntr = ++funcctx->call_cntr;
+
+               /* we'll start over at the top */
+               xpfree(values);
+
+               /* see if we've gone too far already */
+               if (call_cntr >= max_calls)
+               {
+                   /* release SPI related resources */
+                   SPI_finish();
+                   SRF_RETURN_DONE(funcctx);
+               }
+           }
+       }
+   }
+   else    /* do when there is no more left */
+   {
+       /* release SPI related resources */
+       SPI_finish();
+       SRF_RETURN_DONE(funcctx);
+   }
+}
+
+/*
+ * Check if two tupdescs match in type of attributes
+ */
+static bool
+compatTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
+{
+   int         i;
+   Form_pg_attribute   ret_attr;
+   Oid                 ret_atttypid;
+   Form_pg_attribute   sql_attr;
+   Oid                 sql_atttypid;
+
+   /* check the rowid types match */
+   ret_atttypid = ret_tupdesc->attrs[0]->atttypid;
+   sql_atttypid = sql_tupdesc->attrs[0]->atttypid;
+   if (ret_atttypid != sql_atttypid)
+       elog(ERROR, "compatTupleDescs: SQL rowid datatype does not match"
+                       " return rowid datatype");
+
+   /*
+    *  - attribute [1] of the sql tuple is the category;
+    *      no need to check it
+    *  - attribute [2] of the sql tuple should match
+    *      attributes [1] to [natts] of the return tuple
+    */
+   sql_attr = sql_tupdesc->attrs[2];
+   for (i = 1; i < ret_tupdesc->natts; i++)
+   {
+       ret_attr = ret_tupdesc->attrs[i];
+
+       if (ret_attr->atttypid != sql_attr->atttypid)
+           return false;
+   }
+
+   /* OK, the two tupdescs are compatible for our purposes */
+   return true;
+}
diff --git a/contrib/tablefunc/tablefunc.h b/contrib/tablefunc/tablefunc.h
new file mode 100644 (file)
index 0000000..309894a
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * tablefunc
+ *
+ * Sample to demonstrate C functions which return setof scalar
+ * and setof composite.
+ * Joe Conway 
+ *
+ * Copyright 2002 by PostgreSQL Global Development Group
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without a written agreement
+ * is hereby granted, provided that the above copyright notice and this
+ * paragraph and the following two paragraphs appear in all copies.
+ * 
+ * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+ * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+ * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ */
+
+#ifndef TABLEFUNC_H
+#define TABLEFUNC_H
+
+/*
+ * External declarations
+ */
+extern Datum show_all_settings(PG_FUNCTION_ARGS);
+extern Datum normal_rand(PG_FUNCTION_ARGS);
+extern Datum crosstab(PG_FUNCTION_ARGS);
+
+#endif   /* TABLEFUNC_H */
diff --git a/contrib/tablefunc/tablefunc.sql.in b/contrib/tablefunc/tablefunc.sql.in
new file mode 100644 (file)
index 0000000..746e8f9
--- /dev/null
@@ -0,0 +1,46 @@
+CREATE VIEW tablefunc_config_settings AS
+  SELECT
+    ''::TEXT AS name,
+    ''::TEXT AS setting;
+
+CREATE OR REPLACE FUNCTION show_all_settings()
+  RETURNS setof tablefunc_config_settings
+  AS 'MODULE_PATHNAME','show_all_settings' LANGUAGE 'c' STABLE STRICT;
+
+CREATE OR REPLACE FUNCTION normal_rand(int4, float8, float8, int4)
+  RETURNS setof float8
+  AS 'MODULE_PATHNAME','normal_rand' LANGUAGE 'c' VOLATILE STRICT;
+
+CREATE VIEW tablefunc_crosstab_2 AS
+  SELECT
+    ''::TEXT AS row_name,
+    ''::TEXT AS category_1,
+    ''::TEXT AS category_2;
+
+CREATE VIEW tablefunc_crosstab_3 AS
+  SELECT
+    ''::TEXT AS row_name,
+    ''::TEXT AS category_1,
+    ''::TEXT AS category_2,
+    ''::TEXT AS category_3;
+
+CREATE VIEW tablefunc_crosstab_4 AS
+  SELECT
+    ''::TEXT AS row_name,
+    ''::TEXT AS category_1,
+    ''::TEXT AS category_2,
+    ''::TEXT AS category_3,
+    ''::TEXT AS category_4;
+
+CREATE OR REPLACE FUNCTION crosstab2(text)
+  RETURNS setof tablefunc_crosstab_2
+  AS 'MODULE_PATHNAME','crosstab' LANGUAGE 'c' STABLE STRICT;
+
+CREATE OR REPLACE FUNCTION crosstab3(text)
+  RETURNS setof tablefunc_crosstab_3
+  AS 'MODULE_PATHNAME','crosstab' LANGUAGE 'c' STABLE STRICT;
+
+CREATE OR REPLACE FUNCTION crosstab4(text)
+  RETURNS setof tablefunc_crosstab_4
+  AS 'MODULE_PATHNAME','crosstab' LANGUAGE 'c' STABLE STRICT;
+