Thread: revised sample SRF C function; proposed SRF API

revised sample SRF C function; proposed SRF API

From
Joe Conway
Date:
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;

Re: revised sample SRF C function; proposed SRF API

From
Joe Conway
Date:
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;

Re: revised sample SRF C function; proposed SRF API

From
Tom Lane
Date:
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


Re: revised sample SRF C function; proposed SRF API

From
Peter Eisentraut
Date:
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



Re: revised sample SRF C function; proposed SRF API

From
Joe Conway
Date:
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



Re: revised sample SRF C function; proposed SRF API

From
Joe Conway
Date:
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



Re: revised sample SRF C function; proposed SRF API

From
Tom Lane
Date:
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


Re: revised sample SRF C function; proposed SRF API

From
Joe Conway
Date:
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





Re: revised sample SRF C function; proposed SRF API

From
Joe Conway
Date:
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



Re: revised sample SRF C function; proposed SRF API

From
Tom Lane
Date:
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


Re: revised sample SRF C function; proposed SRF API

From
Joe Conway
Date:
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



Re: revised sample SRF C function; proposed SRF API

From
Tom Lane
Date:
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


Re: revised sample SRF C function; proposed SRF API

From
"Christopher Kings-Lynne"
Date:
> 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