Thread: revised sample SRF C function; proposed SRF API
Here is a revised patch for a sample C function returning setof composite. (Same comments as last time -- It is a clone of SHOW ALL as an SRF. For the moment, the function is implemented as contrib/showguc, although a few minor changes to guc.c and guc.h were required to support it.) This version includes pieces that may be appropriate for fmgr.c and fmgr.h, to hide some of the ugliness and facilitate writing C functions which return setof composite. The API is something like this: An SRF written in C must define a variable of type (FuncCallContext *). The structure of FuncCallContext looks like: typedef struct { /* Number of times we've been called before */ uint call_cntr; /* Maximum number of calls */ uint max_calls; /* pointer to result slot */ TupleTableSlot *slot; /* pointer to misc context info */ void *fctx; /* pointer to array of attribute "type"in finfo */ FmgrInfo *att_in_funcinfo; /* pointer to array of attribute type typelem */ Oid *elements; /* memory context used to initialize structure */ MemoryContext fmctx; } FuncCallContext; The first line after the function declarations should be: FUNC_MULTIPLE_RESULT(funcctx, relname, max_calls, misc_ctx); where funcctx is the pointer to FuncCallContext. This is required. relname is the relation name for the composite type the function returns. This is required. max_calls is the maximum number of times the function is expected to return results. You don't have to provide or use this. misc_ctx is a pointer available for the user to store anything needed to retain context from call to call (i.e. this is what you previously might have assigned to fcinfo->flinfo->fn_extra). You don't have to provide or use this. Next, use funcctx->call_cntr and funcctx->max_calls (or whatever method you want) to determine whether or not the function is done returning results. If not, prepare an array of C strings representing the attribute values of your return tuple, and call: FUNC_BUILD_SLOT(values, funcctx); This applies the attribute "in" functions to your values, and stores the results in a TupleTableSlot. Next, clean up as appropriate, and call: FUNC_RETURN_NEXT(funcctx); This increments funcctx->call_cntr in preparation for the next call, sets rsi->isDone = ExprMultipleResult to let the caller know we're not done yet, and returns the slot. Finally, when funcctx->call_cntr = funcctx->max_calls, call: FUNC_RETURN_DONE(funcctx); This does some cleanup, sets rsi->isDone = ExprEndResult, and returns a NULL slot. I made some changes to pull as much into the first call initialization as possible to save redundant work on subsequent calls. For the 96 rows returned by this function, EXPLAIN ANALYZE time went from ~1.6 msec using the first patch, to ~0.9 msec using this one. Comments? Should this (maybe with a few tweaks) go into fmgr.c and fmgr.h as the SRF API? Thanks, Joe Index: contrib/showguc/Makefile =================================================================== RCS file: contrib/showguc/Makefile diff -N contrib/showguc/Makefile *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/Makefile 27 May 2002 00:24:44 -0000 *************** *** 0 **** --- 1,9 ---- + subdir = contrib/showguc + top_builddir = ../.. + include $(top_builddir)/src/Makefile.global + + MODULES = showguc + DATA_built = showguc.sql + DOCS = README.showguc + + include $(top_srcdir)/contrib/contrib-global.mk Index: contrib/showguc/README.showguc =================================================================== RCS file: contrib/showguc/README.showguc diff -N contrib/showguc/README.showguc *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/README.showguc 26 May 2002 05:46:57 -0000 *************** *** 0 **** --- 1,77 ---- + /* + * showguc + * + * Sample function to demonstrate a C function which returns setof composite. + * + * Copyright 2002 by PostgreSQL Global Development Group + * Cloned from src/backend/utils/misc/guc.c which was written by Peter Eisentraut + * <peter_e@gmx.net>, and modified to suit. + * + * 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 (25 May, 2002): + First release + + Release Notes: + + Version 0.1 + - initial release + + Installation: + Place these files in a directory called 'showguc' under 'contrib' in the PostgreSQL source tree. Then run: + + make + make install + + You can use showguc.sql to create the functions in your database of choice, e.g. + + psql -U postgres template1 < showguc.sql + + installs following functions into database template1: + + showvars() - returns all GUC variables + + Documentation + ================================================================== + Name + + showvars() - returns all GUC variables + + Synopsis + + showvars() + + Inputs + + none + + Outputs + + Returns setof __gucvar, where __gucvar is varname TEXT, varval TEXT. All + GUC variables displayed by SHOW ALL are returned as a set. + + Example usage + + select showvars(); + + ================================================================== + -- Joe Conway + Index: contrib/showguc/showguc.c =================================================================== RCS file: contrib/showguc/showguc.c diff -N contrib/showguc/showguc.c *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/showguc.c 27 May 2002 00:21:28 -0000 *************** *** 0 **** --- 1,726 ---- + /*-------------------------------------------------------------------- + * showguc.c + * + * Sample function to demonstrate a C function which returns setof composite. + * + * Copyright 2002 by PostgreSQL Global Development Group + * Cloned from src/backend/utils/misc/guc.c which was written by Peter Eisentraut + * <peter_e@gmx.net>, and modified to suit. + *-------------------------------------------------------------------- + */ + + #include "postgres.h" + + #include <errno.h> + #include <float.h> + #include <limits.h> + #include <unistd.h> + + #include "fmgr.h" + #include "showguc.h" + + #include "utils/guc.h" + #include "access/xlog.h" + #include "access/heapam.h" + #include "catalog/namespace.h" + #include "catalog/pg_type.h" + #include "commands/async.h" + #include "commands/variable.h" + #include "executor/executor.h" + #include "libpq/auth.h" + #include "libpq/pqcomm.h" + #include "miscadmin.h" + #include "optimizer/cost.h" + #include "optimizer/geqo.h" + #include "optimizer/paths.h" + #include "optimizer/planmain.h" + #include "parser/parse_expr.h" + #include "parser/parse_type.h" + #include "storage/fd.h" + #include "storage/freespace.h" + #include "storage/lock.h" + #include "storage/proc.h" + #include "tcop/tcopprot.h" + #include "utils/array.h" + #include "utils/builtins.h" + #include "utils/datetime.h" + #include "utils/elog.h" + #include "utils/pg_locale.h" + #include "utils/syscache.h" + #include "pgstat.h" + + + /* XXX these should be in other modules' header files */ + extern bool Log_connections; + extern int PreAuthDelay; + extern int AuthenticationTimeout; + extern int CheckPointTimeout; + extern int CommitDelay; + extern int CommitSiblings; + extern bool FixBTree; + + #ifdef HAVE_SYSLOG + extern char *Syslog_facility; + extern char *Syslog_ident; + + #endif + + /* + * Debugging options + */ + #ifdef USE_ASSERT_CHECKING + bool assert_enabled = true; + #endif + bool Debug_print_query = false; + bool Debug_print_plan = false; + bool Debug_print_parse = false; + bool Debug_print_rewritten = false; + bool Debug_pretty_print = false; + + bool Show_parser_stats = false; + bool Show_planner_stats = false; + bool Show_executor_stats = false; + bool Show_query_stats = false; /* this is sort of all three above + * together */ + bool Show_btree_build_stats = false; + + bool Explain_pretty_print = true; + + bool SQL_inheritance = true; + + bool Australian_timezones = false; + + bool Password_encryption = false; + + #ifndef PG_KRB_SRVTAB + #define PG_KRB_SRVTAB "" + #endif + + /* + * Declarations for GUC tables + * + * See src/backend/utils/misc/README for design notes. + */ + enum config_type + { + PGC_BOOL, + PGC_INT, + PGC_REAL, + PGC_STRING + }; + + /* Generic fields applicable to all types of variables */ + struct config_generic + { + /* constant fields, must be set correctly in initial value: */ + const char *name; /* name of variable - MUST BE FIRST */ + GucContext context; /* context required to set the variable */ + int flags; /* flag bits, see below */ + /* variable fields, initialized at runtime: */ + enum config_type vartype; /* type of variable (set only at startup) */ + int status; /* status bits, see below */ + GucSource reset_source; /* source of the reset_value */ + GucSource session_source; /* source of the session_value */ + GucSource tentative_source; /* source of the tentative_value */ + GucSource source; /* source of the current actual value */ + }; + + /* bit values in flags field */ + #define GUC_LIST_INPUT 0x0001 /* input can be list format */ + #define GUC_LIST_QUOTE 0x0002 /* double-quote list elements */ + #define GUC_NO_SHOW_ALL 0x0004 /* exclude from SHOW ALL */ + #define GUC_NO_RESET_ALL 0x0008 /* exclude from RESET ALL */ + + /* bit values in status field */ + #define GUC_HAVE_TENTATIVE 0x0001 /* tentative value is defined */ + #define GUC_HAVE_LOCAL 0x0002 /* a SET LOCAL has been executed */ + + + /* GUC records for specific variable types */ + + struct config_bool + { + struct config_generic gen; + /* these fields must be set correctly in initial value: */ + /* (all but reset_val are constants) */ + bool *variable; + bool reset_val; + bool (*assign_hook) (bool newval, bool doit, bool interactive); + const char *(*show_hook) (void); + /* variable fields, initialized at runtime: */ + bool session_val; + bool tentative_val; + }; + + struct config_int + { + struct config_generic gen; + /* these fields must be set correctly in initial value: */ + /* (all but reset_val are constants) */ + int *variable; + int reset_val; + int min; + int max; + bool (*assign_hook) (int newval, bool doit, bool interactive); + const char *(*show_hook) (void); + /* variable fields, initialized at runtime: */ + int session_val; + int tentative_val; + }; + + struct config_real + { + struct config_generic gen; + /* these fields must be set correctly in initial value: */ + /* (all but reset_val are constants) */ + double *variable; + double reset_val; + double min; + double max; + bool (*assign_hook) (double newval, bool doit, bool interactive); + const char *(*show_hook) (void); + /* variable fields, initialized at runtime: */ + double session_val; + double tentative_val; + }; + + struct config_string + { + struct config_generic gen; + /* these fields must be set correctly in initial value: */ + /* (all are constants) */ + char **variable; + const char *boot_val; + const char *(*assign_hook) (const char *newval, bool doit, bool interactive); + const char *(*show_hook) (void); + /* variable fields, initialized at runtime: */ + char *reset_val; + char *session_val; + char *tentative_val; + }; + + /* Macros for freeing malloc'd pointers only if appropriate to do so */ + /* Some of these tests are probably redundant, but be safe ... */ + #define SET_STRING_VARIABLE(rec, newval) \ + do { \ + if (*(rec)->variable && \ + *(rec)->variable != (rec)->reset_val && \ + *(rec)->variable != (rec)->session_val && \ + *(rec)->variable != (rec)->tentative_val) \ + free(*(rec)->variable); \ + *(rec)->variable = (newval); \ + } while (0) + #define SET_STRING_RESET_VAL(rec, newval) \ + do { \ + if ((rec)->reset_val && \ + (rec)->reset_val != *(rec)->variable && \ + (rec)->reset_val != (rec)->session_val && \ + (rec)->reset_val != (rec)->tentative_val) \ + free((rec)->reset_val); \ + (rec)->reset_val = (newval); \ + } while (0) + #define SET_STRING_SESSION_VAL(rec, newval) \ + do { \ + if ((rec)->session_val && \ + (rec)->session_val != *(rec)->variable && \ + (rec)->session_val != (rec)->reset_val && \ + (rec)->session_val != (rec)->tentative_val) \ + free((rec)->session_val); \ + (rec)->session_val = (newval); \ + } while (0) + #define SET_STRING_TENTATIVE_VAL(rec, newval) \ + do { \ + if ((rec)->tentative_val && \ + (rec)->tentative_val != *(rec)->variable && \ + (rec)->tentative_val != (rec)->reset_val && \ + (rec)->tentative_val != (rec)->session_val) \ + free((rec)->tentative_val); \ + (rec)->tentative_val = (newval); \ + } while (0) + + + /* + * This struct holds function context. + * Use fn_extra to hold a pointer to it across calls + */ + typedef struct + { + /* Number of times we've been called before */ + uint call_cntr; + + /* Maximum number of calls */ + uint max_calls; + + /* pointer to result slot */ + TupleTableSlot *slot; + + /* pointer to misc context info */ + void *fctx; + + /* pointer to array of attribute "type"in finfo */ + FmgrInfo *att_in_funcinfo; + + /* pointer to array of attribute type typelem */ + Oid *elements; + + /* memory context used to initialize structure */ + MemoryContext fmctx; + + } FuncCallContext; + + static char *GetNextGUCConfig(struct config_generic **guc_variables, + uint varnum, char **varname); + static char *_GetOption(struct config_generic *record); + static TupleTableSlot *first_pass_setup(char *relname, FuncCallContext *funcctx); + static TupleTableSlot *store_slot(TupleTableSlot *slot, char **values, FuncCallContext *funcctx); + static FuncCallContext *init_MultiFuncCall(PG_FUNCTION_ARGS); + static void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *fctx); + static Oid get_type_element(Oid type); + static Oid get_type_infunc(Type typ); + + /* + * The following macros are candidates to go into fmgr.h + */ + #define FUNC_MULTIPLE_RESULT(_funcctx, _relname, _max_calls, _fctx) \ + do { \ + _funcctx = init_MultiFuncCall(fcinfo); \ + if (_funcctx->call_cntr == 0) \ + { \ + _funcctx->max_calls = _max_calls; \ + _funcctx->slot = first_pass_setup(_relname, _funcctx); \ + _funcctx->fctx = _fctx; \ + } \ + else \ + ExecClearTuple(_funcctx->slot); \ + } while (0) + + #define FUNC_BUILD_SLOT(_slot, _values, _funcctx) \ + do { \ + _slot = store_slot(_slot, _values, _funcctx); \ + } while (0) + + #define FUNC_RETURN_NEXT(_funcctx, _slot) \ + do { \ + ReturnSetInfo *rsi; \ + _funcctx->call_cntr++; \ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ + rsi->isDone = ExprMultipleResult; \ + PG_RETURN_POINTER(_slot); \ + } while (0) + + + #define FUNC_RETURN_DONE(_funcctx, _slot) \ + do { \ + ReturnSetInfo *rsi; \ + end_MultiFuncCall(fcinfo, _funcctx); \ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ + rsi->isDone = ExprEndResult; \ + _slot = NULL; \ + PG_RETURN_POINTER(_slot); \ + } while (0) + + /* + * SHOW command + */ + PG_FUNCTION_INFO_V1(showguc); + + Datum + showguc(PG_FUNCTION_ARGS) + { + FuncCallContext *funcctx; + char *relname = "__gucvar"; + char *varname = NULL; + char *varval; + char **values; + TupleTableSlot *slot; + uint call_cntr; + uint max_calls; + struct config_generic **fctx; + + /* + * Setup the function context for multiple calls + */ + FUNC_MULTIPLE_RESULT(funcctx, relname, get_num_guc_variables(), get_guc_variables()); + + /* + * All set up now, so use what we've got + */ + call_cntr = funcctx->call_cntr; + max_calls = funcctx->max_calls; + slot = funcctx->slot; + fctx = (struct config_generic **) funcctx->fctx; + + /* + * Are there any more results to send? + */ + if (call_cntr < max_calls) + { + /* + * Get the next GUC variable name and value + */ + varval = GetNextGUCConfig(fctx, call_cntr, &varname); + + /* + * 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] = varname; + values[1] = varval; + + /* Store the values */ + FUNC_BUILD_SLOT(slot, values, funcctx); + + /* Clean up */ + pfree(varname); + pfree(values); + + FUNC_RETURN_NEXT(funcctx, slot); + } + else + { + /* All done! */ + FUNC_RETURN_DONE(funcctx, slot); + } + } + + /* internal functions */ + + /* + * SHOW ALL command + */ + static char * + GetNextGUCConfig(struct config_generic **guc_variables, uint varnum, char **varname) + { + struct config_generic *conf = guc_variables[varnum]; + + *varname = pstrdup(conf->name); + + if ((conf->flags & GUC_NO_SHOW_ALL) == 0) + return _GetOption(conf); + else + return NULL; + + } + + static char * + _GetOption(struct config_generic *record) + { + char buffer[256]; + const char *val; + char *retval; + + switch (record->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) record; + + if (conf->show_hook) + val = (*conf->show_hook) (); + else + val = *conf->variable ? "on" : "off"; + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) record; + + if (conf->show_hook) + val = (*conf->show_hook) (); + else + { + snprintf(buffer, sizeof(buffer), "%d", + *conf->variable); + val = buffer; + } + } + break; + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) record; + + if (conf->show_hook) + val = (*conf->show_hook) (); + else + { + snprintf(buffer, sizeof(buffer), "%g", + *conf->variable); + val = buffer; + } + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) record; + + if (conf->show_hook) + val = (*conf->show_hook) (); + else if (*conf->variable && **conf->variable) + val = *conf->variable; + else + val = "unset"; + } + break; + + default: + /* just to keep compiler quiet */ + val = "???"; + break; + } + retval = pstrdup(val); + + return retval; + } + + /* + * The following functions are candidates to go into fmgr.c + */ + static TupleTableSlot * + first_pass_setup(char *relname, FuncCallContext *funcctx) + { + TupleTableSlot *slot; + Oid relid; + Relation rel; + TupleDesc tupdesc; + int natts; + int i; + Type att_type; + Oid att_in_funcoid; + FmgrInfo *att_in_funcinfo; + Oid *elements; + + /* + * Make a standalone slot + */ + slot = MakeTupleTableSlot(); + + /* + * Open relation and get the tuple description + */ + relid = RelnameGetRelid(relname); + rel = relation_open(relid, AccessShareLock); + tupdesc = CreateTupleDescCopy(rel->rd_att); + relation_close(rel, AccessShareLock); + natts = tupdesc->natts; + + /* + * Bind the tuple description to the slot + */ + ExecSetSlotDescriptor(slot, tupdesc, true); + + /* + * Gather info needed later to call the "in" function for each attribute + */ + att_in_funcinfo = (FmgrInfo *) palloc(natts * sizeof(FmgrInfo)); + elements = (Oid *) palloc(natts * sizeof(Oid)); + + for (i = 0; i < natts; i++) + { + att_type = typeidType(tupdesc->attrs[i]->atttypid); + att_in_funcoid = get_type_infunc(att_type); + fmgr_info(att_in_funcoid, &att_in_funcinfo[i]); + elements[i] = get_type_element(tupdesc->attrs[i]->atttypid); + } + + funcctx->att_in_funcinfo = att_in_funcinfo; + funcctx->elements = elements; + + /* + * Return the slot! + */ + return slot; + } + + static TupleTableSlot * + store_slot(TupleTableSlot *slot, char **values, FuncCallContext *funcctx) + { + TupleDesc tupdesc; + int natts; + HeapTuple tuple; + char *nulls; + int i; + Datum *dvalues; + FmgrInfo att_in_funcinfo; + Oid element; + + /* + * Get the tuple description + */ + tupdesc = slot->ttc_tupleDescriptor; + natts = tupdesc->natts; + + dvalues = (Datum *) palloc(natts * sizeof(Datum)); + /* + * Call the "in" function for each attribute + */ + for (i = 0; i < natts; i++) + { + if (values[i] != NULL) + { + att_in_funcinfo = funcctx->att_in_funcinfo[i]; + element = funcctx->elements[i]; + + dvalues[i] = FunctionCall3(&att_in_funcinfo, CStringGetDatum(values[i]), + ObjectIdGetDatum(element), + Int32GetDatum(tupdesc->attrs[i]->atttypmod)); + } + else + dvalues[i] = PointerGetDatum(NULL); + } + + /* + * Form a tuple + */ + nulls = (char *) palloc(natts * sizeof(char)); + for (i = 0; i < natts; i++) + { + if (DatumGetPointer(dvalues[i]) != NULL) + nulls[i] = ' '; + else + nulls[i] = 'n'; + } + tuple = heap_formtuple(tupdesc, dvalues, nulls); + + /* + * Save the tuple in the tuple slot + */ + slot = ExecStoreTuple(tuple, /* tuple to store */ + slot, /* slot to store in */ + InvalidBuffer, /* buffer associated with + * this tuple */ + true); /* pfree this pointer */ + + /* + * Clean up + */ + pfree(nulls); + pfree(dvalues); + + /* + * Return the slot! + */ + return slot; + } + + + /* + * init_MultiFuncCall + * Create an empty FuncCallContext data structure + * and do some other basic Multi-function call setup + * and error checking + */ + FuncCallContext * + init_MultiFuncCall(PG_FUNCTION_ARGS) + { + FuncCallContext *retval; + + /* + * Bail if we're called in the wrong context + */ + if (fcinfo->resultinfo == NULL || !IsA(fcinfo->resultinfo, ReturnSetInfo)) + elog(ERROR, "function called in context that does not accept a set result"); + + if (fcinfo->flinfo->fn_extra == NULL) + { + /* + * First call + */ + MemoryContext oldcontext; + + /* switch to the appropriate memory context */ + oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); + + /* + * allocate space and zero it + */ + retval = (FuncCallContext *) palloc(sizeof(FuncCallContext)); + MemSet(retval, 0, sizeof(FuncCallContext)); + + /* + * initialize the elements + */ + retval->call_cntr = 0; + retval->max_calls = 0; + retval->slot = NULL; + retval->fctx = NULL; + retval->att_in_funcinfo = NULL; + retval->elements = NULL; + retval->fmctx = fcinfo->flinfo->fn_mcxt; + + /* + * save the pointer for cross-call use + */ + fcinfo->flinfo->fn_extra = retval; + + /* back to the original memory context */ + MemoryContextSwitchTo(oldcontext); + } + else /* second and subsequent calls */ + retval = fcinfo->flinfo->fn_extra; + + return retval; + } + + + /* + * end_MultiFuncCall + * Clean up + */ + void + end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx) + { + MemoryContext oldcontext; + + /* unbind from fcinfo */ + fcinfo->flinfo->fn_extra = NULL; + + /* + * Caller is responsible to free up memory for individual + * struct elements other than att_in_funcinfo and elements. + */ + oldcontext = MemoryContextSwitchTo(funcctx->fmctx); + + if (funcctx->att_in_funcinfo != NULL) + pfree(funcctx->att_in_funcinfo); + + if (funcctx->elements != NULL) + pfree(funcctx->elements); + + pfree(funcctx); + + MemoryContextSwitchTo(oldcontext); + } + + + static Oid + get_type_element(Oid type) + { + HeapTuple typeTuple; + Oid result; + + typeTuple = SearchSysCache(TYPEOID, + ObjectIdGetDatum(type), + 0, 0, 0); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "get_type_element: Cache lookup of type %u failed", type); + result = ((Form_pg_type) GETSTRUCT(typeTuple))->typelem; + ReleaseSysCache(typeTuple); + return result; + } + + + static Oid + get_type_infunc(Type typ) + { + Form_pg_type typtup; + + typtup = (Form_pg_type) GETSTRUCT(typ); + + return typtup->typinput; + } + Index: contrib/showguc/showguc.h =================================================================== RCS file: contrib/showguc/showguc.h diff -N contrib/showguc/showguc.h *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/showguc.h 26 May 2002 02:06:52 -0000 *************** *** 0 **** --- 1,15 ---- + /* + * showguc.c + * + * Sample function to demonstrate a C function which returns setof composite. + * + * Copyright 2002 by PostgreSQL Global Development Group + * Cloned from src/backend/utils/misc/guc.c which was written by Peter Eisentraut + * <peter_e@gmx.net>, and modified to suit. + */ + #ifndef SHOWGUC_H + #define SHOWGUC_H + + extern Datum showguc(PG_FUNCTION_ARGS); + + #endif /* SHOWGUC_H */ Index: contrib/showguc/showguc.sql.in =================================================================== RCS file: contrib/showguc/showguc.sql.in diff -N contrib/showguc/showguc.sql.in *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/showguc.sql.in 26 May 2002 05:36:18 -0000 *************** *** 0 **** --- 1,7 ---- + CREATE TABLE __gucvar( + varname TEXT, + varval TEXT + ); + + CREATE FUNCTION showvars() RETURNS setof __gucvar + AS 'MODULE_PATHNAME','showguc' 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.69 diff -c -r1.69 guc.c *** src/backend/utils/misc/guc.c 17 May 2002 20:32:29 -0000 1.69 --- src/backend/utils/misc/guc.c 26 May 2002 02:20:26 -0000 *************** *** 2539,2541 **** --- 2539,2554 ---- return newarray; } + + struct config_generic ** + get_guc_variables(void) + { + return guc_variables; + } + + int + get_num_guc_variables(void) + { + return num_guc_variables; + } + Index: src/include/utils/guc.h =================================================================== RCS file: /opt/src/cvs/pgsql/src/include/utils/guc.h,v retrieving revision 1.17 diff -c -r1.17 guc.h *** src/include/utils/guc.h 17 May 2002 01:19:19 -0000 1.17 --- src/include/utils/guc.h 26 May 2002 02:21:00 -0000 *************** *** 97,102 **** --- 97,105 ---- extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name, const char *value); extern ArrayType *GUCArrayDelete(ArrayType *array, const char *name); + extern struct config_generic **get_guc_variables(void); + extern int get_num_guc_variables(void); + extern bool Debug_print_query; extern bool Debug_print_plan; extern bool Debug_print_parse;
Joe Conway wrote: > Here is a revised patch for a sample C function returning setof > composite. (Same comments as last time -- It is a clone of SHOW ALL as > an SRF. For the moment, the function is implemented as contrib/showguc, > although a few minor changes to guc.c and guc.h were required to support > it.) > > This version includes pieces that may be appropriate for fmgr.c and > fmgr.h, to hide some of the ugliness and facilitate writing C functions > which return setof composite. The API is something like this: > Sorry -- I was a bit too quick with the last patch :-(. It generates a "Cache reference leak" warning. This one is better. Joe Index: contrib/showguc/Makefile =================================================================== RCS file: contrib/showguc/Makefile diff -N contrib/showguc/Makefile *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/Makefile 27 May 2002 00:24:44 -0000 *************** *** 0 **** --- 1,9 ---- + subdir = contrib/showguc + top_builddir = ../.. + include $(top_builddir)/src/Makefile.global + + MODULES = showguc + DATA_built = showguc.sql + DOCS = README.showguc + + include $(top_srcdir)/contrib/contrib-global.mk Index: contrib/showguc/README.showguc =================================================================== RCS file: contrib/showguc/README.showguc diff -N contrib/showguc/README.showguc *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/README.showguc 26 May 2002 05:46:57 -0000 *************** *** 0 **** --- 1,77 ---- + /* + * showguc + * + * Sample function to demonstrate a C function which returns setof composite. + * + * Copyright 2002 by PostgreSQL Global Development Group + * Cloned from src/backend/utils/misc/guc.c which was written by Peter Eisentraut + * <peter_e@gmx.net>, and modified to suit. + * + * 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 (25 May, 2002): + First release + + Release Notes: + + Version 0.1 + - initial release + + Installation: + Place these files in a directory called 'showguc' under 'contrib' in the PostgreSQL source tree. Then run: + + make + make install + + You can use showguc.sql to create the functions in your database of choice, e.g. + + psql -U postgres template1 < showguc.sql + + installs following functions into database template1: + + showvars() - returns all GUC variables + + Documentation + ================================================================== + Name + + showvars() - returns all GUC variables + + Synopsis + + showvars() + + Inputs + + none + + Outputs + + Returns setof __gucvar, where __gucvar is varname TEXT, varval TEXT. All + GUC variables displayed by SHOW ALL are returned as a set. + + Example usage + + select showvars(); + + ================================================================== + -- Joe Conway + Index: contrib/showguc/showguc.c =================================================================== RCS file: contrib/showguc/showguc.c diff -N contrib/showguc/showguc.c *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/showguc.c 27 May 2002 02:03:19 -0000 *************** *** 0 **** --- 1,726 ---- + /*-------------------------------------------------------------------- + * showguc.c + * + * Sample function to demonstrate a C function which returns setof composite. + * + * Copyright 2002 by PostgreSQL Global Development Group + * Cloned from src/backend/utils/misc/guc.c which was written by Peter Eisentraut + * <peter_e@gmx.net>, and modified to suit. + *-------------------------------------------------------------------- + */ + + #include "postgres.h" + + #include <errno.h> + #include <float.h> + #include <limits.h> + #include <unistd.h> + + #include "fmgr.h" + #include "showguc.h" + + #include "utils/guc.h" + #include "access/xlog.h" + #include "access/heapam.h" + #include "catalog/namespace.h" + #include "catalog/pg_type.h" + #include "commands/async.h" + #include "commands/variable.h" + #include "executor/executor.h" + #include "libpq/auth.h" + #include "libpq/pqcomm.h" + #include "miscadmin.h" + #include "optimizer/cost.h" + #include "optimizer/geqo.h" + #include "optimizer/paths.h" + #include "optimizer/planmain.h" + #include "parser/parse_expr.h" + #include "parser/parse_type.h" + #include "storage/fd.h" + #include "storage/freespace.h" + #include "storage/lock.h" + #include "storage/proc.h" + #include "tcop/tcopprot.h" + #include "utils/array.h" + #include "utils/builtins.h" + #include "utils/datetime.h" + #include "utils/elog.h" + #include "utils/pg_locale.h" + #include "utils/syscache.h" + #include "pgstat.h" + + + /* XXX these should be in other modules' header files */ + extern bool Log_connections; + extern int PreAuthDelay; + extern int AuthenticationTimeout; + extern int CheckPointTimeout; + extern int CommitDelay; + extern int CommitSiblings; + extern bool FixBTree; + + #ifdef HAVE_SYSLOG + extern char *Syslog_facility; + extern char *Syslog_ident; + + #endif + + /* + * Debugging options + */ + #ifdef USE_ASSERT_CHECKING + bool assert_enabled = true; + #endif + bool Debug_print_query = false; + bool Debug_print_plan = false; + bool Debug_print_parse = false; + bool Debug_print_rewritten = false; + bool Debug_pretty_print = false; + + bool Show_parser_stats = false; + bool Show_planner_stats = false; + bool Show_executor_stats = false; + bool Show_query_stats = false; /* this is sort of all three above + * together */ + bool Show_btree_build_stats = false; + + bool Explain_pretty_print = true; + + bool SQL_inheritance = true; + + bool Australian_timezones = false; + + bool Password_encryption = false; + + #ifndef PG_KRB_SRVTAB + #define PG_KRB_SRVTAB "" + #endif + + /* + * Declarations for GUC tables + * + * See src/backend/utils/misc/README for design notes. + */ + enum config_type + { + PGC_BOOL, + PGC_INT, + PGC_REAL, + PGC_STRING + }; + + /* Generic fields applicable to all types of variables */ + struct config_generic + { + /* constant fields, must be set correctly in initial value: */ + const char *name; /* name of variable - MUST BE FIRST */ + GucContext context; /* context required to set the variable */ + int flags; /* flag bits, see below */ + /* variable fields, initialized at runtime: */ + enum config_type vartype; /* type of variable (set only at startup) */ + int status; /* status bits, see below */ + GucSource reset_source; /* source of the reset_value */ + GucSource session_source; /* source of the session_value */ + GucSource tentative_source; /* source of the tentative_value */ + GucSource source; /* source of the current actual value */ + }; + + /* bit values in flags field */ + #define GUC_LIST_INPUT 0x0001 /* input can be list format */ + #define GUC_LIST_QUOTE 0x0002 /* double-quote list elements */ + #define GUC_NO_SHOW_ALL 0x0004 /* exclude from SHOW ALL */ + #define GUC_NO_RESET_ALL 0x0008 /* exclude from RESET ALL */ + + /* bit values in status field */ + #define GUC_HAVE_TENTATIVE 0x0001 /* tentative value is defined */ + #define GUC_HAVE_LOCAL 0x0002 /* a SET LOCAL has been executed */ + + + /* GUC records for specific variable types */ + + struct config_bool + { + struct config_generic gen; + /* these fields must be set correctly in initial value: */ + /* (all but reset_val are constants) */ + bool *variable; + bool reset_val; + bool (*assign_hook) (bool newval, bool doit, bool interactive); + const char *(*show_hook) (void); + /* variable fields, initialized at runtime: */ + bool session_val; + bool tentative_val; + }; + + struct config_int + { + struct config_generic gen; + /* these fields must be set correctly in initial value: */ + /* (all but reset_val are constants) */ + int *variable; + int reset_val; + int min; + int max; + bool (*assign_hook) (int newval, bool doit, bool interactive); + const char *(*show_hook) (void); + /* variable fields, initialized at runtime: */ + int session_val; + int tentative_val; + }; + + struct config_real + { + struct config_generic gen; + /* these fields must be set correctly in initial value: */ + /* (all but reset_val are constants) */ + double *variable; + double reset_val; + double min; + double max; + bool (*assign_hook) (double newval, bool doit, bool interactive); + const char *(*show_hook) (void); + /* variable fields, initialized at runtime: */ + double session_val; + double tentative_val; + }; + + struct config_string + { + struct config_generic gen; + /* these fields must be set correctly in initial value: */ + /* (all are constants) */ + char **variable; + const char *boot_val; + const char *(*assign_hook) (const char *newval, bool doit, bool interactive); + const char *(*show_hook) (void); + /* variable fields, initialized at runtime: */ + char *reset_val; + char *session_val; + char *tentative_val; + }; + + /* Macros for freeing malloc'd pointers only if appropriate to do so */ + /* Some of these tests are probably redundant, but be safe ... */ + #define SET_STRING_VARIABLE(rec, newval) \ + do { \ + if (*(rec)->variable && \ + *(rec)->variable != (rec)->reset_val && \ + *(rec)->variable != (rec)->session_val && \ + *(rec)->variable != (rec)->tentative_val) \ + free(*(rec)->variable); \ + *(rec)->variable = (newval); \ + } while (0) + #define SET_STRING_RESET_VAL(rec, newval) \ + do { \ + if ((rec)->reset_val && \ + (rec)->reset_val != *(rec)->variable && \ + (rec)->reset_val != (rec)->session_val && \ + (rec)->reset_val != (rec)->tentative_val) \ + free((rec)->reset_val); \ + (rec)->reset_val = (newval); \ + } while (0) + #define SET_STRING_SESSION_VAL(rec, newval) \ + do { \ + if ((rec)->session_val && \ + (rec)->session_val != *(rec)->variable && \ + (rec)->session_val != (rec)->reset_val && \ + (rec)->session_val != (rec)->tentative_val) \ + free((rec)->session_val); \ + (rec)->session_val = (newval); \ + } while (0) + #define SET_STRING_TENTATIVE_VAL(rec, newval) \ + do { \ + if ((rec)->tentative_val && \ + (rec)->tentative_val != *(rec)->variable && \ + (rec)->tentative_val != (rec)->reset_val && \ + (rec)->tentative_val != (rec)->session_val) \ + free((rec)->tentative_val); \ + (rec)->tentative_val = (newval); \ + } while (0) + + + /* + * This struct holds function context. + * Use fn_extra to hold a pointer to it across calls + */ + typedef struct + { + /* Number of times we've been called before */ + uint call_cntr; + + /* Maximum number of calls */ + uint max_calls; + + /* pointer to result slot */ + TupleTableSlot *slot; + + /* pointer to misc context info */ + void *fctx; + + /* pointer to array of attribute "type"in finfo */ + FmgrInfo *att_in_funcinfo; + + /* pointer to array of attribute type typelem */ + Oid *elements; + + /* memory context used to initialize structure */ + MemoryContext fmctx; + + } FuncCallContext; + + static char *GetNextGUCConfig(struct config_generic **guc_variables, + uint varnum, char **varname); + static char *_GetOption(struct config_generic *record); + static TupleTableSlot *first_pass_setup(char *relname, FuncCallContext *funcctx); + static TupleTableSlot *store_slot(char **values, FuncCallContext *funcctx); + static FuncCallContext *init_MultiFuncCall(PG_FUNCTION_ARGS); + static void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *fctx); + static Oid get_type_element(Oid type); + static Oid get_type_infunc(Type typ); + + /* + * The following macros are candidates to go into fmgr.h + */ + #define FUNC_MULTIPLE_RESULT(_funcctx, _relname, _max_calls, _fctx) \ + do { \ + _funcctx = init_MultiFuncCall(fcinfo); \ + if (_funcctx->call_cntr == 0) \ + { \ + _funcctx->max_calls = _max_calls; \ + _funcctx->slot = first_pass_setup(_relname, _funcctx); \ + _funcctx->fctx = _fctx; \ + } \ + else \ + ExecClearTuple(_funcctx->slot); \ + } while (0) + + #define FUNC_BUILD_SLOT(_values, _funcctx) \ + do { \ + _funcctx->slot = store_slot(_values, _funcctx); \ + } while (0) + + #define FUNC_RETURN_NEXT(_funcctx) \ + do { \ + ReturnSetInfo *rsi; \ + _funcctx->call_cntr++; \ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ + rsi->isDone = ExprMultipleResult; \ + PG_RETURN_POINTER(_funcctx->slot); \ + } while (0) + + + #define FUNC_RETURN_DONE(_funcctx) \ + do { \ + ReturnSetInfo *rsi; \ + end_MultiFuncCall(fcinfo, _funcctx); \ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ + rsi->isDone = ExprEndResult; \ + _funcctx->slot = NULL; \ + PG_RETURN_POINTER(_funcctx->slot); \ + } while (0) + + /* + * SHOW command + */ + PG_FUNCTION_INFO_V1(showguc); + + Datum + showguc(PG_FUNCTION_ARGS) + { + FuncCallContext *funcctx; + char *relname = "__gucvar"; + char *varname = NULL; + char *varval; + char **values; + uint call_cntr; + uint max_calls; + struct config_generic **fctx; + + /* + * Setup the function context for multiple calls + */ + FUNC_MULTIPLE_RESULT(funcctx, relname, get_num_guc_variables(), get_guc_variables()); + + /* + * All set up now, so use what we've got + */ + call_cntr = funcctx->call_cntr; + max_calls = funcctx->max_calls; + fctx = (struct config_generic **) funcctx->fctx; + + /* + * Are there any more results to send? + */ + if (call_cntr < max_calls) + { + /* + * Get the next GUC variable name and value + */ + varval = GetNextGUCConfig(fctx, call_cntr, &varname); + + /* + * 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] = varname; + values[1] = varval; + + /* Store the values */ + FUNC_BUILD_SLOT(values, funcctx); + + /* Clean up */ + pfree(varname); + pfree(values); + + FUNC_RETURN_NEXT(funcctx); + } + else + { + /* All done! */ + FUNC_RETURN_DONE(funcctx); + } + } + + /* internal functions */ + + /* + * SHOW ALL command + */ + static char * + GetNextGUCConfig(struct config_generic **guc_variables, uint varnum, char **varname) + { + struct config_generic *conf = guc_variables[varnum]; + + *varname = pstrdup(conf->name); + + if ((conf->flags & GUC_NO_SHOW_ALL) == 0) + return _GetOption(conf); + else + return NULL; + + } + + static char * + _GetOption(struct config_generic *record) + { + char buffer[256]; + const char *val; + char *retval; + + switch (record->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) record; + + if (conf->show_hook) + val = (*conf->show_hook) (); + else + val = *conf->variable ? "on" : "off"; + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) record; + + if (conf->show_hook) + val = (*conf->show_hook) (); + else + { + snprintf(buffer, sizeof(buffer), "%d", + *conf->variable); + val = buffer; + } + } + break; + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) record; + + if (conf->show_hook) + val = (*conf->show_hook) (); + else + { + snprintf(buffer, sizeof(buffer), "%g", + *conf->variable); + val = buffer; + } + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) record; + + if (conf->show_hook) + val = (*conf->show_hook) (); + else if (*conf->variable && **conf->variable) + val = *conf->variable; + else + val = "unset"; + } + break; + + default: + /* just to keep compiler quiet */ + val = "???"; + break; + } + retval = pstrdup(val); + + return retval; + } + + /* + * The following functions are candidates to go into fmgr.c + */ + static TupleTableSlot * + first_pass_setup(char *relname, FuncCallContext *funcctx) + { + TupleTableSlot *slot; + Oid relid; + Relation rel; + TupleDesc tupdesc; + int natts; + int i; + Type att_type; + Oid att_in_funcoid; + FmgrInfo *att_in_funcinfo; + Oid *elements; + + /* + * Make a standalone slot + */ + slot = MakeTupleTableSlot(); + + /* + * Open relation and get the tuple description + */ + relid = RelnameGetRelid(relname); + rel = relation_open(relid, AccessShareLock); + tupdesc = CreateTupleDescCopy(rel->rd_att); + relation_close(rel, AccessShareLock); + natts = tupdesc->natts; + + /* + * Bind the tuple description to the slot + */ + ExecSetSlotDescriptor(slot, tupdesc, true); + + /* + * Gather info needed later to call the "in" function for each attribute + */ + att_in_funcinfo = (FmgrInfo *) palloc(natts * sizeof(FmgrInfo)); + elements = (Oid *) palloc(natts * sizeof(Oid)); + + for (i = 0; i < natts; i++) + { + att_type = typeidType(tupdesc->attrs[i]->atttypid); + att_in_funcoid = get_type_infunc(att_type); + fmgr_info(att_in_funcoid, &att_in_funcinfo[i]); + elements[i] = get_type_element(tupdesc->attrs[i]->atttypid); + ReleaseSysCache(att_type); + } + + funcctx->att_in_funcinfo = att_in_funcinfo; + funcctx->elements = elements; + + /* + * Return the slot! + */ + return slot; + } + + static TupleTableSlot * + store_slot(char **values, FuncCallContext *funcctx) + { + TupleTableSlot *slot = funcctx->slot; + TupleDesc tupdesc; + int natts; + HeapTuple tuple; + char *nulls; + int i; + Datum *dvalues; + FmgrInfo att_in_funcinfo; + Oid element; + + /* + * Get the tuple description + */ + tupdesc = slot->ttc_tupleDescriptor; + natts = tupdesc->natts; + + dvalues = (Datum *) palloc(natts * sizeof(Datum)); + /* + * Call the "in" function for each attribute + */ + for (i = 0; i < natts; i++) + { + if (values[i] != NULL) + { + att_in_funcinfo = funcctx->att_in_funcinfo[i]; + element = funcctx->elements[i]; + + dvalues[i] = FunctionCall3(&att_in_funcinfo, CStringGetDatum(values[i]), + ObjectIdGetDatum(element), + Int32GetDatum(tupdesc->attrs[i]->atttypmod)); + } + else + dvalues[i] = PointerGetDatum(NULL); + } + + /* + * Form a tuple + */ + nulls = (char *) palloc(natts * sizeof(char)); + for (i = 0; i < natts; i++) + { + if (DatumGetPointer(dvalues[i]) != NULL) + nulls[i] = ' '; + else + nulls[i] = 'n'; + } + tuple = heap_formtuple(tupdesc, dvalues, nulls); + + /* + * Save the tuple in the tuple slot + */ + slot = ExecStoreTuple(tuple, /* tuple to store */ + slot, /* slot to store in */ + InvalidBuffer, /* buffer associated with + * this tuple */ + true); /* pfree this pointer */ + + /* + * Clean up + */ + pfree(nulls); + pfree(dvalues); + + /* + * Return the slot! + */ + return slot; + } + + + /* + * init_MultiFuncCall + * Create an empty FuncCallContext data structure + * and do some other basic Multi-function call setup + * and error checking + */ + FuncCallContext * + init_MultiFuncCall(PG_FUNCTION_ARGS) + { + FuncCallContext *retval; + + /* + * Bail if we're called in the wrong context + */ + if (fcinfo->resultinfo == NULL || !IsA(fcinfo->resultinfo, ReturnSetInfo)) + elog(ERROR, "function called in context that does not accept a set result"); + + if (fcinfo->flinfo->fn_extra == NULL) + { + /* + * First call + */ + MemoryContext oldcontext; + + /* switch to the appropriate memory context */ + oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); + + /* + * allocate space and zero it + */ + retval = (FuncCallContext *) palloc(sizeof(FuncCallContext)); + MemSet(retval, 0, sizeof(FuncCallContext)); + + /* + * initialize the elements + */ + retval->call_cntr = 0; + retval->max_calls = 0; + retval->slot = NULL; + retval->fctx = NULL; + retval->att_in_funcinfo = NULL; + retval->elements = NULL; + retval->fmctx = fcinfo->flinfo->fn_mcxt; + + /* + * save the pointer for cross-call use + */ + fcinfo->flinfo->fn_extra = retval; + + /* back to the original memory context */ + MemoryContextSwitchTo(oldcontext); + } + else /* second and subsequent calls */ + retval = fcinfo->flinfo->fn_extra; + + return retval; + } + + + /* + * end_MultiFuncCall + * Clean up + */ + void + end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx) + { + MemoryContext oldcontext; + + /* unbind from fcinfo */ + fcinfo->flinfo->fn_extra = NULL; + + /* + * Caller is responsible to free up memory for individual + * struct elements other than att_in_funcinfo and elements. + */ + oldcontext = MemoryContextSwitchTo(funcctx->fmctx); + + if (funcctx->att_in_funcinfo != NULL) + pfree(funcctx->att_in_funcinfo); + + if (funcctx->elements != NULL) + pfree(funcctx->elements); + + pfree(funcctx); + + MemoryContextSwitchTo(oldcontext); + } + + + static Oid + get_type_element(Oid type) + { + HeapTuple typeTuple; + Oid result; + + typeTuple = SearchSysCache(TYPEOID, + ObjectIdGetDatum(type), + 0, 0, 0); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "get_type_element: Cache lookup of type %u failed", type); + result = ((Form_pg_type) GETSTRUCT(typeTuple))->typelem; + ReleaseSysCache(typeTuple); + return result; + } + + + static Oid + get_type_infunc(Type typ) + { + Form_pg_type typtup; + + typtup = (Form_pg_type) GETSTRUCT(typ); + + return typtup->typinput; + } + Index: contrib/showguc/showguc.h =================================================================== RCS file: contrib/showguc/showguc.h diff -N contrib/showguc/showguc.h *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/showguc.h 26 May 2002 02:06:52 -0000 *************** *** 0 **** --- 1,15 ---- + /* + * showguc.c + * + * Sample function to demonstrate a C function which returns setof composite. + * + * Copyright 2002 by PostgreSQL Global Development Group + * Cloned from src/backend/utils/misc/guc.c which was written by Peter Eisentraut + * <peter_e@gmx.net>, and modified to suit. + */ + #ifndef SHOWGUC_H + #define SHOWGUC_H + + extern Datum showguc(PG_FUNCTION_ARGS); + + #endif /* SHOWGUC_H */ Index: contrib/showguc/showguc.sql.in =================================================================== RCS file: contrib/showguc/showguc.sql.in diff -N contrib/showguc/showguc.sql.in *** /dev/null 1 Jan 1970 00:00:00 -0000 --- contrib/showguc/showguc.sql.in 26 May 2002 05:36:18 -0000 *************** *** 0 **** --- 1,7 ---- + CREATE TABLE __gucvar( + varname TEXT, + varval TEXT + ); + + CREATE FUNCTION showvars() RETURNS setof __gucvar + AS 'MODULE_PATHNAME','showguc' 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.69 diff -c -r1.69 guc.c *** src/backend/utils/misc/guc.c 17 May 2002 20:32:29 -0000 1.69 --- src/backend/utils/misc/guc.c 26 May 2002 02:20:26 -0000 *************** *** 2539,2541 **** --- 2539,2554 ---- return newarray; } + + struct config_generic ** + get_guc_variables(void) + { + return guc_variables; + } + + int + get_num_guc_variables(void) + { + return num_guc_variables; + } + Index: src/include/utils/guc.h =================================================================== RCS file: /opt/src/cvs/pgsql/src/include/utils/guc.h,v retrieving revision 1.17 diff -c -r1.17 guc.h *** src/include/utils/guc.h 17 May 2002 01:19:19 -0000 1.17 --- src/include/utils/guc.h 26 May 2002 02:21:00 -0000 *************** *** 97,102 **** --- 97,105 ---- extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name, const char *value); extern ArrayType *GUCArrayDelete(ArrayType *array, const char *name); + extern struct config_generic **get_guc_variables(void); + extern int get_num_guc_variables(void); + extern bool Debug_print_query; extern bool Debug_print_plan; extern bool Debug_print_parse;
Joe Conway <mail@joeconway.com> writes: > If not, prepare an array of C strings representing the > attribute values of your return tuple, and call: > FUNC_BUILD_SLOT(values, funcctx); I think that's a poor choice of abstraction, as it forces the user into the least-efficient-possible way of building a return tuple. What if he's already got a tuple (eg, he read it off disk), or at any rate has datums already in internal format? I'd say make it FUNC_RETURN_NEXT(funcctx, HeapTuple) and let the caller worry about calling heap_formtuple or otherwise constructing the tuple. For similar reasons I think the initial call ought to provide a TupleDesc structure, not a relation name (which is at least two lookups removed from the information you actually need). The max_calls thing doesn't seem quite right either; at least not as something that has to be provided in the "first line after the function declarations". It might be quite expensive to derive, and you don't need to do so on every call. Perhaps better have the macro return a boolean indicating whether this is the first call or not, and then people can do if (FUNC_MULTIPLE_RESULT(funcctx)){ // do one-time setup here, // including possibly computing a max_calls value; // also find or make a TupleDesc to be stored into the // funcctx.} Similarly I'm confused about the usefulness of misc_ctx if it has to be re-provided on every call. regards, tom lane
Joe Conway writes: > Here is a revised patch for a sample C function returning setof > composite. (Same comments as last time -- It is a clone of SHOW ALL as > an SRF. For the moment, the function is implemented as contrib/showguc, > although a few minor changes to guc.c and guc.h were required to support > it.) We need a function like this in the main line. The "show all" variety isn't top priority, but we need something that gets you the "show" result as a query output. The original idea was to make SHOW return a query result directly, but a function is fine with me too. -- Peter Eisentraut peter_e@gmx.net
Peter Eisentraut wrote: > We need a function like this in the main line. The "show all" variety > isn't top priority, but we need something that gets you the "show" result > as a query output. The original idea was to make SHOW return a query > result directly, but a function is fine with me too. > Originally I wrote this as "showvars(varname)" and accepted 'all' in a similar fashion to SHOW ALL. But it seemed redundant since you can still do: test=# select * from showvars() where varname = 'wal_sync_method'; varname | varval -----------------+----------- wal_sync_method | fdatasync (1 row) but you can also do: test=# select * from showvars() where varname like 'show%'; varname | varval ---------------------+-------- show_executor_stats | off show_parser_stats | off show_planner_stats | off show_query_stats | off show_source_port | off (5 rows) which also seemed useful. I was thinking that if we wanted to replace SHOW X with this, it could be done in the parser by rewriting it as "SELECT * FROM showvars() WHERE varname = 'X'", or for SHOW ALL just "SELECT * FROM showvars()". In any case, I'll fit the showvars() function into the backend and submit a patch. Thanks, Joe
Tom Lane wrote:> Joe Conway <mail@joeconway.com> writes:>>> If not, prepare an array of C strings representing the attribute>>values of your return tuple, and call: FUNC_BUILD_SLOT(values,>> funcctx);>> I think that's a poor choice of abstraction,as it forces the user> into the least-efficient-possible way of building a return tuple.> What if he's alreadygot a tuple (eg, he read it off disk), or at> any rate has datums already in internal format? I'd say make it>> FUNC_RETURN_NEXT(funcctx,HeapTuple)>> and let the caller worry about calling heap_formtuple or otherwise> constructing thetuple. Hmmm - well, I agree that FUNC_RETURN_NEXT(funcctx, HeapTuple) is a better abstraction, particularly for experience backend hackers ;) but I was trying to also make this accessable to someone writing a custom C function that isn't necessarily very familiar with forming their own HeapTuples manually. What if we also had something like: FUNC_BUILD_TUPLE(values, funcctx); which returns a tuple for the less experienced folks (or people like me when I'm being lazy :)) It could be used when desired, or skipped entirely if a HeapTuple is already easily available. >> For similar reasons I think the initial call ought to provide a TupleDesc> structure, not a relation name (which is atleast two lookups removed> from the information you actually need). Same comments. How about: FUNC_BUILD_TUPDESC(_relname) and FUNC_MULTIPLE_RESULT(_funcctx, _tupdesc, _max_calls, _fctx) ? Power hackers could skip FUNC_BUILD_TUPDESC if they wanted to or already had a TupleDesc available. Of course you would only want to build your tupdesc during the first pass, so maybe we'd need FUNC_IS_FIRSTPASS() which would just check for (fcinfo->flinfo->fn_extra == NULL) >> The max_calls thing doesn't seem quite right either; at least not as> something that has to be provided in the "firstline after the> function declarations". It might be quite expensive to derive, and> you don't need to do so on everycall. I thought about that, but the value is not required at all, and you can easily set it later when more convenient. Perhaps it should be taken out of the initialization and we just document how it might be used? > Perhaps better have the macro return a boolean indicating whether> this is the first call or not, and then people can do>>if (FUNC_MULTIPLE_RESULT(funcctx)) { // do one-time setup here, //> including possibly computing a max_calls value; //also find or make> a TupleDesc to be stored into the // funcctx. } hmm - see complete new example below. >> Similarly I'm confused about the usefulness of misc_ctx if it has to> be re-provided on every call. Like max_calls, maybe it should be taken out of the initialization and its potential use documented. On second thought, I think maybe I tried to do too much with FUNC_MULTIPLE_RESULT. It does initialization during the first pass, and then does per call setup for subsequent calls. Maybe there should be: FUNC_FIRSTCALL_INIT and FUNC_PERCALL_SETUP Then the whole API looks something like: Datum my_Set_Returning_Function(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; <user defined declarations> /* * Optional - user defined code needed to be called * on every pass */ <user defined code> if(FUNC_IS_FIRSTPASS()) { /* * Optional - user defined initialization which is only * required duringthe first pass through the function */ <user defined code> /* * Optional - if desired, use this to get a TupleDesc * based on the function's return type relation */ FUNC_BUILD_TUPDESC(_relname); /* * Required - memory allocation and initialization * which is only required during the first pass through * the function */ FUNC_FIRSTCALL_INIT(funcctx, tupdesc); /* * optional - total number of tuples to be returned. * */ funcctx->max_calls = my_max_calls; /* * optional - pointer to structure containing * user defined context */ funcctx->fctx = my_func_context_pointer; } /* * Required - per call setup */ FUNC_PERCALL_SETUP(funcctx) /* * Here we need to test whether or not we're all out * of tuples to return. The test does not have to be *this one, but in many cases this is probably what * you'll want. */ if (call_cntr < max_calls) { /* * user code to derive data to be returned */ <user defined code> /* * Optional - 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 */ tuple = FUNC_BUILD_TUPLE(values, funcctx); /* * Required - returns the tuple and notifies * the caller that we still have more to do */ FUNC_RETURN_NEXT(funcctx, HeapTuple); } else { /* * Required - returns NULL tuple and notifies *caller that we're all done now. */ FUNC_RETURN_DONE(funcctx); } } How's this look? Any better? Thanks, Joe
Joe Conway <mail@joeconway.com> writes: > What if we also had something like: > FUNC_BUILD_TUPLE(values, funcctx); > which returns a tuple for the less experienced folks Sure, as long as it's not getting in the way when you don't want it. For that matter the FUNC stuff shouldn't get in the way of using it in other contexts, so I'd suggest decoupling it from funcctx. Why notHeapTuple BuildTupleFromStrings(TupDesc, char**) (better choice of name welcome). > Then the whole API looks something like: > Datum > my_Set_Returning_Function(PG_FUNCTION_ARGS) > { > FuncCallContext *funcctx; > <user defined declarations> > /* > * Optional - user defined code needed to be called > * on every pass > */ > <user defined code> > if(FUNC_IS_FIRSTPASS()) > { > /* > * Optional - user defined initialization which is only > * required during the first pass through the function > */ > <user defined code> > /* > * Optional - if desired, use this to get a TupleDesc > * based on the function's return type relation > */ > FUNC_BUILD_TUPDESC(_relname); > /* > * Required - memory allocation and initialization > * which is only required during the first pass through > * the function > */ > FUNC_FIRSTCALL_INIT(funcctx, tupdesc); I think this should be funcctx = FUNC_FIRSTCALL_INIT(tupdesc); to make it clearer that it is initializing funcctx. Similarly FUNC_BUILD_TUPDESC should be more like tupdesc = RelationNameGetTupleDesc(relname); since it's not particularly tied to this usage. > /* > * optional - total number of tuples to be returned. > * > */ > funcctx->max_calls = my_max_calls; > /* > * optional - pointer to structure containing > * user defined context > */ > funcctx->fctx = my_func_context_pointer; > } > /* > * Required - per call setup > */ > FUNC_PERCALL_SETUP(funcctx) Again I'd prefer funcctx = FUNC_PERCALL_SETUP(); I think this is easier for both humans and compilers to recognize as an initialization of funcctx. > How's this look? Any better? Definitely better. I'd suggest also thinking about whether the same/similar macros can support functions that return a set of a scalar (non-tuple) datatype. In my mind, the cleanest design would be some base macros that support functions-returning-set (of anything), and if you want to return a set of scalar then you just use these directly (handing a Datum to FUNC_RETURN_NEXT). If you want to return a set of tuples then there are a couple extra steps that you need to do to build a tupdesc, build a tuple, and convert the tuple to Datum (which at the moment you do by putting it into a slot, but I think we ought to change that soon). If it were really clean then the macros supporting these extra steps would also work without the SRF macros, so that you could use 'em in a function returning a single tuple. regards, tom lane
Tom Lane wrote: > Definitely better. I'd suggest also thinking about whether the > same/similar macros can support functions that return a set of a > scalar (non-tuple) datatype. In my mind, the cleanest design would > be some base macros that support functions-returning-set (of anything), > and if you want to return a set of scalar then you just use these > directly (handing a Datum to FUNC_RETURN_NEXT). If you want to return > a set of tuples then there are a couple extra steps that you need to > do to build a tupdesc, build a tuple, and convert the tuple to Datum > (which at the moment you do by putting it into a slot, but I think we > ought to change that soon). If it were really clean then the macros > supporting these extra steps would also work without the SRF macros, > so that you could use 'em in a function returning a single tuple. > Sorry for the long delay. I just got back to this today, and I've run into an interesting question. I have a proposal and patch almost ready which I think pretty much meets the above design requirements. I also wanted to incorporate a built-in function for returning guc variables (varname text, varval text), consistent with previous posts. This is both useful and a good test of the Composite & SRF function API (the API includes functions/macros to facilitate returning composite types, and an independent set of functions/macros for returning sets, whether composite or scalar). The question is how to best bootstrap this new function. In order to create the pg_proc entry I need the return type oid. If I understand correctly, in order to get a composite return type, with a known oid, I would need to create a bootstrapped relation and the corresponding bootstrapped pg_type entry. Is there any alternative? It seems ugly to bootstrap so many objects for every (future) builtin function which returns a composite type. Thanks, Joe
Tom Lane wrote: > Definitely better. I'd suggest also thinking about whether the > same/similar macros can support functions that return a set of a > scalar (non-tuple) datatype. In my mind, the cleanest design would > be some base macros that support functions-returning-set (of anything), > and if you want to return a set of scalar then you just use these > directly (handing a Datum to FUNC_RETURN_NEXT). If you want to return > a set of tuples then there are a couple extra steps that you need to > do to build a tupdesc, build a tuple, and convert the tuple to Datum > (which at the moment you do by putting it into a slot, but I think we > ought to change that soon). If it were really clean then the macros > supporting these extra steps would also work without the SRF macros, > so that you could use 'em in a function returning a single tuple. I have a patch ready now which I think meets the design requirements above for the most part. The API is in two pieces: one which aids in creation of functions which return composite types; the other helps with SRFs. The comments in funcapi.h summarize the API: /*------------------------------------------------------------------------- * Support to ease writing Functions returningcomposite types * * External declarations: * TupleDesc RelationNameGetTupleDesc(char *relname) - Use to get a * TupleDesc based on the function's return type relation. * TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)- Use to * get a TupleDesc based on the function's type oid. This can be * used to get a TupleDesc fora base (scalar), or composite type. * TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc) - Initialize a * slot givena TupleDesc. * AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc) - Get a * pointer to AttInMetadata basedon the function's TupleDesc. * AttInMetadata can be used in conjunction with C strings to * produce a properlyformed tuple. Store the metadata here for * use across calls to avoid redundant work. * HeapTuple BuildTupleFromCStrings(AttInMetadata*attinmeta, * char **values) - * builda HeapTuple given user data in C string form. values is an * array of C strings, one for each attribute of the returntuple. * * Macro declarations: * TupleGetDatum(TupleTableSlot *slot, HeapTuple tuple) - get a Datum * given a tupleand a slot. */ /*------------------------------------------------------------------------- * Support for Set Returning Functions(SRFs) * * The basic API for SRFs looks something like: * * Datum * my_Set_Returning_Function(PG_FUNCTION_ARGS)* { * FuncCallContext *funcctx; * Datum result; * <userdefined declarations> * * if(SRF_IS_FIRSTPASS()) * { * <user defined code> * <obtain slot> * funcctx = SRF_FIRSTCALL_INIT(slot); * <user defined code> * } * <user defined code> * funcctx= SRF_PERCALL_SETUP(funcctx); * <user defined code> * * if (funcctx->call_cntr < funcctx->max_calls) * { * <user defined code> * <obtain result Datum> * SRF_RETURN_NEXT(funcctx, result); * } * else * { * SRF_RETURN_DONE(funcctx); * } * } * */ If interested in more details, the patch will be sent shortly to PATCHES. Included in the patch is a reference implementation of the API, show_all_vars() (showguc_all() in guc.c). This returns the same information as SHOW ALL, but as an SRF, allowing, for example, the following: test=# select * from show_all_vars() where varname like 'cpu%'; varname | varval ----------------------+-------- cpu_index_tuple_cost | 0.001 cpu_operator_cost | 0.0025 cpu_tuple_cost | 0.01 (3 rows) Comments/thoughts? Thanks, Joe
Joe Conway <mail@joeconway.com> writes: > The question is how to best bootstrap this new function. In order to > create the pg_proc entry I need the return type oid. If I understand > correctly, in order to get a composite return type, with a known oid, I > would need to create a bootstrapped relation and the corresponding > bootstrapped pg_type entry. Well, we're not doing that; and I see no good reason to make the thing be a builtin function at all. Since it's just an example, it can very well be a contrib item with a creation script. Probably *should* be, in fact, because dynamically created functions are what other people are going to be building; an example of how to do it as a builtin function isn't as helpful. Further down the road it may be that we'll get around to allowing freestanding composite types (ie, ones with no associated table). That would make it less painful to have builtin functions returning tuples --- though not by a lot, since you'd still have to manufacture pg_type and pg_attribute rows for 'em by hand. I'm not in a hurry to do that in any case, because of the extent of restructuring of pg_class, pg_type, and pg_attribute that would be needed. regards, tom lane
Tom Lane wrote: > Well, we're not doing that; and I see no good reason to make the thing > be a builtin function at all. Since it's just an example, it can very > well be a contrib item with a creation script. Probably *should* be, > in fact, because dynamically created functions are what other people are > going to be building; an example of how to do it as a builtin function > isn't as helpful. True enough, although I could always create another example for contrib. Returning GUC variable "SHOW ALL" results as a query result has been discussed before, and I thought there was agreement that it was a desirable backend feature. Is the approach in my patch still too ugly to allow a builtin SRF (set the function return type to 0 in pg_proc.h, create a view and fix the pg_proc entry during initdb)? If so, I'll rework the patch into two patches: one for the composite/set returning function api, and one for show_all_vars() as a contrib/SRF example. If not, I'll just come up with another function for contrib to serve as a reference implementation for others. Thanks, Joe
Joe Conway <mail@joeconway.com> writes: > Returning GUC variable "SHOW ALL" results as a query result has been > discussed before, and I thought there was agreement that it was a > desirable backend feature. So it is, but I had expected it to be implemented by changing the behavior of SHOW, same as we did for EXPLAIN. > Is the approach in my patch still too ugly to allow a builtin SRF (set > the function return type to 0 in pg_proc.h, create a view and fix the > pg_proc entry during initdb)? Too ugly for my taste anyway ... regards, tom lane
> Tom Lane wrote: > > Well, we're not doing that; and I see no good reason to make the thing > > be a builtin function at all. Since it's just an example, it can very > > well be a contrib item with a creation script. Probably *should* be, > > in fact, because dynamically created functions are what other people are > > going to be building; an example of how to do it as a builtin function > > isn't as helpful. > > True enough, although I could always create another example for contrib. > Returning GUC variable "SHOW ALL" results as a query result has been > discussed before, and I thought there was agreement that it was a > desirable backend feature. Sure would be. Means we can show config variables nicely in phpPgAdmin like phpMyAdmin does... Chris