Re: guc GetConfigOptionByNum and tablefunc API - minor changes - Mailing list pgsql-patches
From | Bruce Momjian |
---|---|
Subject | Re: guc GetConfigOptionByNum and tablefunc API - minor changes |
Date | |
Msg-id | 200207232217.g6NMHSH07812@candle.pha.pa.us Whole thread Raw |
In response to | Re: guc GetConfigOptionByNum and tablefunc API - minor changes (Joe Conway <mail@joeconway.com>) |
List | pgsql-patches |
Your patch has been added to the PostgreSQL unapplied patches list at: http://candle.pha.pa.us/cgi-bin/pgpatches I will try to apply it within the next 48 hours. --------------------------------------------------------------------------- Joe Conway wrote: > Tom Lane wrote: > > Doesn't this duplicate get_func_rettype()? (Which is more clearly > > named anyway; a function's type is a second-order concept IMHO...) > > Here's a second try at all three patches. I removed the foidGetTypeId() > function from funcapi.c, and replaced references to it in > contrib/tablefunc with get_func_rettype(). > > The only change to funcapi now is a minor addition to the comments in > funcapi.h and the funcapi doc for the previously mentioned undocumented > function. > > As always, thanks for the review. > > Joe > > > Index: contrib/tablefunc/Makefile > =================================================================== > RCS file: contrib/tablefunc/Makefile > diff -N contrib/tablefunc/Makefile > *** /dev/null 1 Jan 1970 00:00:00 -0000 > --- contrib/tablefunc/Makefile 9 Jul 2002 03:34:24 -0000 > *************** > *** 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 > Index: contrib/tablefunc/README.tablefunc > =================================================================== > RCS file: contrib/tablefunc/README.tablefunc > diff -N contrib/tablefunc/README.tablefunc > *** /dev/null 1 Jan 1970 00:00:00 -0000 > --- contrib/tablefunc/README.tablefunc 21 Jul 2002 02:50:53 -0000 > *************** > *** 0 **** > --- 1,272 ---- > + /* > + * tablefunc > + * > + * Sample to demonstrate C functions which return setof scalar > + * and setof composite. > + * Joe Conway <mail@joeconway.com> > + * > + * 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 > + > Index: contrib/tablefunc/tablefunc-test.sql > =================================================================== > RCS file: contrib/tablefunc/tablefunc-test.sql > diff -N contrib/tablefunc/tablefunc-test.sql > *** /dev/null 1 Jan 1970 00:00:00 -0000 > --- contrib/tablefunc/tablefunc-test.sql 21 Jul 2002 03:01:13 -0000 > *************** > *** 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;'); > + > + > Index: contrib/tablefunc/tablefunc.c > =================================================================== > RCS file: contrib/tablefunc/tablefunc.c > diff -N contrib/tablefunc/tablefunc.c > *** /dev/null 1 Jan 1970 00:00:00 -0000 > --- contrib/tablefunc/tablefunc.c 21 Jul 2002 05:36:00 -0000 > *************** > *** 0 **** > --- 1,665 ---- > + /* > + * tablefunc > + * > + * Sample to demonstrate C functions which return setof scalar > + * and setof composite. > + * Joe Conway <mail@joeconway.com> > + * > + * 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 <stdlib.h> > + #include <math.h> > + > + #include "postgres.h" > + > + #include "fmgr.h" > + #include "funcapi.h" > + #include "executor/spi.h" > + #include "utils/builtins.h" > + #include "utils/guc.h" > + #include "utils/lsyscache.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 funcid = 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 = get_func_rettype(funcid); > + > + /* 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 funcid = 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 = get_func_rettype(funcid); > + > + /* 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; > + } > Index: contrib/tablefunc/tablefunc.h > =================================================================== > RCS file: contrib/tablefunc/tablefunc.h > diff -N contrib/tablefunc/tablefunc.h > *** /dev/null 1 Jan 1970 00:00:00 -0000 > --- contrib/tablefunc/tablefunc.h 19 Jul 2002 04:46:28 -0000 > *************** > *** 0 **** > --- 1,39 ---- > + /* > + * tablefunc > + * > + * Sample to demonstrate C functions which return setof scalar > + * and setof composite. > + * Joe Conway <mail@joeconway.com> > + * > + * 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 */ > Index: contrib/tablefunc/tablefunc.sql.in > =================================================================== > RCS file: contrib/tablefunc/tablefunc.sql.in > diff -N contrib/tablefunc/tablefunc.sql.in > *** /dev/null 1 Jan 1970 00:00:00 -0000 > --- contrib/tablefunc/tablefunc.sql.in 21 Jul 2002 01:19:53 -0000 > *************** > *** 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; > + > Index: src/backend/utils/misc/guc.c > =================================================================== > RCS file: /opt/src/cvs/pgsql/src/backend/utils/misc/guc.c,v > retrieving revision 1.75 > diff -c -r1.75 guc.c > *** src/backend/utils/misc/guc.c 20 Jul 2002 15:12:55 -0000 1.75 > --- src/backend/utils/misc/guc.c 21 Jul 2002 01:36:12 -0000 > *************** > *** 2347,2358 **** > * form of name. Return value is palloc'd. > */ > char * > ! GetConfigOptionByNum(int varnum, const char **varname) > { > ! struct config_generic *conf = guc_variables[varnum]; > > if (varname) > *varname = conf->name; > > return _ShowOption(conf); > } > --- 2347,2366 ---- > * form of name. Return value is palloc'd. > */ > char * > ! GetConfigOptionByNum(int varnum, const char **varname, bool *noshow) > { > ! struct config_generic *conf; > ! > ! /* check requested variable number valid */ > ! Assert((varnum >= 0) && (varnum < num_guc_variables)); > ! > ! conf = guc_variables[varnum]; > > if (varname) > *varname = conf->name; > + > + if (noshow) > + *noshow = (conf->flags & GUC_NO_SHOW_ALL) ? true : false; > > return _ShowOption(conf); > } > Index: src/include/funcapi.h > =================================================================== > RCS file: /opt/src/cvs/pgsql/src/include/funcapi.h,v > retrieving revision 1.3 > diff -c -r1.3 funcapi.h > *** src/include/funcapi.h 18 Jul 2002 04:40:30 -0000 1.3 > --- src/include/funcapi.h 21 Jul 2002 05:28:36 -0000 > *************** > *** 139,144 **** > --- 139,146 ---- > * HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values) - > * build a HeapTuple given user data in C string form. values is an array > * of C strings, one for each attribute of the return tuple. > + * void get_type_metadata(Oid typeid, Oid *attinfuncid, Oid *attelem) - Get > + * an attribute "in" function and typelem value given the typeid. > * > * Macro declarations: > * TupleGetDatum(TupleTableSlot *slot, HeapTuple tuple) - get a Datum > Index: src/include/utils/guc.h > =================================================================== > RCS file: /opt/src/cvs/pgsql/src/include/utils/guc.h,v > retrieving revision 1.19 > diff -c -r1.19 guc.h > *** src/include/utils/guc.h 20 Jul 2002 15:12:56 -0000 1.19 > --- src/include/utils/guc.h 20 Jul 2002 23:44:52 -0000 > *************** > *** 87,93 **** > extern void ShowGUCConfigOption(const char *name); > extern void ShowAllGUCConfig(void); > extern char *GetConfigOptionByName(const char *name, const char **varname); > ! extern char *GetConfigOptionByNum(int varnum, const char **varname); > extern int GetNumConfigOptions(void); > > extern void SetPGVariable(const char *name, List *args, bool is_local); > --- 87,93 ---- > extern void ShowGUCConfigOption(const char *name); > extern void ShowAllGUCConfig(void); > extern char *GetConfigOptionByName(const char *name, const char **varname); > ! extern char *GetConfigOptionByNum(int varnum, const char **varname, bool *noshow); > extern int GetNumConfigOptions(void); > > extern void SetPGVariable(const char *name, List *args, bool is_local); > Index: doc/src/sgml/xfunc.sgml > =================================================================== > RCS file: /opt/src/cvs/pgsql/doc/src/sgml/xfunc.sgml,v > retrieving revision 1.53 > diff -c -r1.53 xfunc.sgml > *** doc/src/sgml/xfunc.sgml 18 Jul 2002 04:47:17 -0000 1.53 > --- doc/src/sgml/xfunc.sgml 21 Jul 2002 05:29:09 -0000 > *************** > *** 1557,1562 **** > --- 1557,1570 ---- > </para> > > <para> > + In order to get an attribute "in" function and typelem value given the > + typeid, use > + <programlisting> > + void get_type_metadata(Oid typeid, Oid *attinfuncid, Oid *attelem) > + </programlisting> > + </para> > + > + <para> > Finally, in order to return a tuple using the SRF portion of the API > (described below), the tuple must be converted into a Datum. Use > <programlisting> > > ---------------------------(end of broadcast)--------------------------- > TIP 3: if posting/reading through Usenet, please send an appropriate > subscribe-nomail command to majordomo@postgresql.org so that your > message can get through to the mailing list cleanly -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 853-3000 + If your life is a hard drive, | 830 Blythe Avenue + Christ can be your backup. | Drexel Hill, Pennsylvania 19026
pgsql-patches by date: