summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/fmgr
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/fmgr')
-rw-r--r--src/backend/utils/fmgr/Makefile22
-rw-r--r--src/backend/utils/fmgr/README331
-rw-r--r--src/backend/utils/fmgr/dfmgr.c755
-rw-r--r--src/backend/utils/fmgr/fmgr.c2090
-rw-r--r--src/backend/utils/fmgr/funcapi.c2003
5 files changed, 5201 insertions, 0 deletions
diff --git a/src/backend/utils/fmgr/Makefile b/src/backend/utils/fmgr/Makefile
new file mode 100644
index 0000000..f552b95
--- /dev/null
+++ b/src/backend/utils/fmgr/Makefile
@@ -0,0 +1,22 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for utils/fmgr
+#
+# IDENTIFICATION
+# src/backend/utils/fmgr/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/utils/fmgr
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ dfmgr.o \
+ fmgr.o \
+ funcapi.o
+
+override CPPFLAGS += -DDLSUFFIX=\"$(DLSUFFIX)\"
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/fmgr/README b/src/backend/utils/fmgr/README
new file mode 100644
index 0000000..1e4c4b9
--- /dev/null
+++ b/src/backend/utils/fmgr/README
@@ -0,0 +1,331 @@
+src/backend/utils/fmgr/README
+
+Function Manager
+================
+
+[This file originally explained the transition from the V0 to the V1
+interface. Now it just explains some internals and rationale for the V1
+interface, while the V0 interface has been removed.]
+
+The V1 Function-Manager Interface
+---------------------------------
+
+The core of the design is data structures for representing the result of a
+function lookup and for representing the parameters passed to a specific
+function invocation. (We want to keep function lookup separate from
+function call, since many parts of the system apply the same function over
+and over; the lookup overhead should be paid once per query, not once per
+tuple.)
+
+
+When a function is looked up in pg_proc, the result is represented as
+
+typedef struct
+{
+ PGFunction fn_addr; /* pointer to function or handler to be called */
+ Oid fn_oid; /* OID of function (NOT of handler, if any) */
+ short fn_nargs; /* number of input args (0..FUNC_MAX_ARGS) */
+ bool fn_strict; /* function is "strict" (NULL in => NULL out) */
+ bool fn_retset; /* function returns a set (over multiple calls) */
+ unsigned char fn_stats; /* collect stats if track_functions > this */
+ void *fn_extra; /* extra space for use by handler */
+ MemoryContext fn_mcxt; /* memory context to store fn_extra in */
+ Node *fn_expr; /* expression parse tree for call, or NULL */
+} FmgrInfo;
+
+For an ordinary built-in function, fn_addr is just the address of the C
+routine that implements the function. Otherwise it is the address of a
+handler for the class of functions that includes the target function.
+The handler can use the function OID and perhaps also the fn_extra slot
+to find the specific code to execute. (fn_oid = InvalidOid can be used
+to denote a not-yet-initialized FmgrInfo struct. fn_extra will always
+be NULL when an FmgrInfo is first filled by the function lookup code, but
+a function handler could set it to avoid making repeated lookups of its
+own when the same FmgrInfo is used repeatedly during a query.) fn_nargs
+is the number of arguments expected by the function, fn_strict is its
+strictness flag, and fn_retset shows whether it returns a set; all of
+these values come from the function's pg_proc entry. fn_stats is also
+set up to control whether or not to track runtime statistics for calling
+this function.
+
+If the function is being called as part of a SQL expression, fn_expr will
+point to the expression parse tree for the function call; this can be used
+to extract parse-time knowledge about the actual arguments. Note that this
+field really is information about the arguments rather than information
+about the function, but it's proven to be more convenient to keep it in
+FmgrInfo than in FunctionCallInfoBaseData where it might more logically go.
+
+
+During a call of a function, the following data structure is created
+and passed to the function:
+
+typedef struct
+{
+ FmgrInfo *flinfo; /* ptr to lookup info used for this call */
+ Node *context; /* pass info about context of call */
+ Node *resultinfo; /* pass or return extra info about result */
+ Oid fncollation; /* collation for function to use */
+ bool isnull; /* function must set true if result is NULL */
+ short nargs; /* # arguments actually passed */
+ NullableDatum args[]; /* Arguments passed to function */
+} FunctionCallInfoBaseData;
+typedef FunctionCallInfoBaseData* FunctionCallInfo;
+
+flinfo points to the lookup info used to make the call. Ordinary functions
+will probably ignore this field, but function class handlers will need it
+to find out the OID of the specific function being called.
+
+context is NULL for an "ordinary" function call, but may point to additional
+info when the function is called in certain contexts. (For example, the
+trigger manager will pass information about the current trigger event here.)
+If context is used, it should point to some subtype of Node; the particular
+kind of context is indicated by the node type field. (A callee should
+always check the node type before assuming it knows what kind of context is
+being passed.) fmgr itself puts no other restrictions on the use of this
+field.
+
+resultinfo is NULL when calling any function from which a simple Datum
+result is expected. It may point to some subtype of Node if the function
+returns more than a Datum. (For example, resultinfo is used when calling a
+function that returns a set, as discussed below.) Like the context field,
+resultinfo is a hook for expansion; fmgr itself doesn't constrain the use
+of the field.
+
+fncollation is the input collation derived by the parser, or InvalidOid
+when there are no inputs of collatable types or they don't share a common
+collation. This is effectively a hidden additional argument, which
+collation-sensitive functions can use to determine their behavior.
+
+nargs and args[] hold the arguments being passed to the function.
+Notice that all the arguments passed to a function (as well as its result
+value) will now uniformly be of type Datum. As discussed below, callers
+and callees should apply the standard Datum-to-and-from-whatever macros
+to convert to the actual argument types of a particular function. The
+value in args[i].value is unspecified when args[i].isnull is true.
+
+It is generally the responsibility of the caller to ensure that the
+number of arguments passed matches what the callee is expecting; except
+for callees that take a variable number of arguments, the callee will
+typically ignore the nargs field and just grab values from args[].
+
+The isnull field will be initialized to "false" before the call. On
+return from the function, isnull is the null flag for the function result:
+if it is true the function's result is NULL, regardless of the actual
+function return value. Note that simple "strict" functions can ignore
+both isnull and args[i].isnull, since they won't even get called when there
+are any TRUE values in args[].isnull.
+
+FunctionCallInfo replaces FmgrValues plus a bunch of ad-hoc parameter
+conventions, global variables (fmgr_pl_finfo and CurrentTriggerData at
+least), and other uglinesses.
+
+
+Callees, whether they be individual functions or function handlers,
+shall always have this signature:
+
+Datum function (FunctionCallInfo fcinfo);
+
+which is represented by the typedef
+
+typedef Datum (*PGFunction) (FunctionCallInfo fcinfo);
+
+The function is responsible for setting fcinfo->isnull appropriately
+as well as returning a result represented as a Datum. Note that since
+all callees will now have exactly the same signature, and will be called
+through a function pointer declared with exactly that signature, we
+should have no portability or optimization problems.
+
+
+Function Coding Conventions
+---------------------------
+
+Here are the proposed macros and coding conventions:
+
+The definition of an fmgr-callable function will always look like
+
+Datum
+function_name(PG_FUNCTION_ARGS)
+{
+ ...
+}
+
+"PG_FUNCTION_ARGS" just expands to "FunctionCallInfo fcinfo". The main
+reason for using this macro is to make it easy for scripts to spot function
+definitions. However, if we ever decide to change the calling convention
+again, it might come in handy to have this macro in place.
+
+A nonstrict function is responsible for checking whether each individual
+argument is null or not, which it can do with PG_ARGISNULL(n) (which is
+just "fcinfo->args[n].isnull"). It should avoid trying to fetch the value
+of any argument that is null.
+
+Both strict and nonstrict functions can return NULL, if needed, with
+ PG_RETURN_NULL();
+which expands to
+ { fcinfo->isnull = true; return (Datum) 0; }
+
+Argument values are ordinarily fetched using code like
+ int32 name = PG_GETARG_INT32(number);
+
+For float4, float8, and int8, the PG_GETARG macros will hide whether the
+types are pass-by-value or pass-by-reference. For example, if float8 is
+pass-by-reference then PG_GETARG_FLOAT8 expands to
+ (* (float8 *) DatumGetPointer(fcinfo->args[number].value))
+and would typically be called like this:
+ float8 arg = PG_GETARG_FLOAT8(0);
+For what are now historical reasons, the float-related typedefs and macros
+express the type width in bytes (4 or 8), whereas we prefer to label the
+widths of integer types in bits.
+
+Non-null values are returned with a PG_RETURN_XXX macro of the appropriate
+type. For example, PG_RETURN_INT32 expands to
+ return Int32GetDatum(x)
+PG_RETURN_FLOAT4, PG_RETURN_FLOAT8, and PG_RETURN_INT64 hide whether their
+data types are pass-by-value or pass-by-reference, by doing a palloc if
+needed.
+
+fmgr.h will provide PG_GETARG and PG_RETURN macros for all the basic data
+types. Modules or header files that define specialized SQL datatypes
+(eg, timestamp) should define appropriate macros for those types, so that
+functions manipulating the types can be coded in the standard style.
+
+For non-primitive data types (particularly variable-length types) it won't
+be very practical to hide the pass-by-reference nature of the data type,
+so the PG_GETARG and PG_RETURN macros for those types won't do much more
+than DatumGetPointer/PointerGetDatum plus the appropriate typecast (but see
+TOAST discussion, below). Functions returning such types will need to
+palloc() their result space explicitly. I recommend naming the GETARG and
+RETURN macros for such types to end in "_P", as a reminder that they
+produce or take a pointer. For example, PG_GETARG_TEXT_P yields "text *".
+
+When a function needs to access fcinfo->flinfo or one of the other auxiliary
+fields of FunctionCallInfo, it should just do it. I doubt that providing
+syntactic-sugar macros for these cases is useful.
+
+
+Support for TOAST-Able Data Types
+---------------------------------
+
+For TOAST-able data types, the PG_GETARG macro will deliver a de-TOASTed
+data value. There might be a few cases where the still-toasted value is
+wanted, but the vast majority of cases want the de-toasted result, so
+that will be the default. To get the argument value without causing
+de-toasting, use PG_GETARG_RAW_VARLENA_P(n).
+
+Some functions require a modifiable copy of their input values. In these
+cases, it's silly to do an extra copy step if we copied the data anyway
+to de-TOAST it. Therefore, each toastable datatype has an additional
+fetch macro, for example PG_GETARG_TEXT_P_COPY(n), which delivers a
+guaranteed-fresh copy, combining this with the detoasting step if possible.
+
+There is also a PG_FREE_IF_COPY(ptr,n) macro, which pfree's the given
+pointer if and only if it is different from the original value of the n'th
+argument. This can be used to free the de-toasted value of the n'th
+argument, if it was actually de-toasted. Currently, doing this is not
+necessary for the majority of functions because the core backend code
+releases temporary space periodically, so that memory leaked in function
+execution isn't a big problem. However, as of 7.1 memory leaks in
+functions that are called by index searches will not be cleaned up until
+end of transaction. Therefore, functions that are listed in pg_amop or
+pg_amproc should be careful not to leak detoasted copies, and so these
+functions do need to use PG_FREE_IF_COPY() for toastable inputs.
+
+A function should never try to re-TOAST its result value; it should just
+deliver an untoasted result that's been palloc'd in the current memory
+context. When and if the value is actually stored into a tuple, the
+tuple toaster will decide whether toasting is needed.
+
+
+Functions Accepting or Returning Sets
+-------------------------------------
+
+If a function is marked in pg_proc as returning a set, then it is called
+with fcinfo->resultinfo pointing to a node of type ReturnSetInfo. A
+function that desires to return a set should raise an error "called in
+context that does not accept a set result" if resultinfo is NULL or does
+not point to a ReturnSetInfo node.
+
+There are currently two modes in which a function can return a set result:
+value-per-call, or materialize. In value-per-call mode, the function returns
+one value each time it is called, and finally reports "done" when it has no
+more values to return. In materialize mode, the function's output set is
+instantiated in a Tuplestore object; all the values are returned in one call.
+Additional modes might be added in future.
+
+ReturnSetInfo contains a field "allowedModes" which is set (by the caller)
+to a bitmask that's the OR of the modes the caller can support. The actual
+mode used by the function is returned in another field "returnMode". For
+backwards-compatibility reasons, returnMode is initialized to value-per-call
+and need only be changed if the function wants to use a different mode.
+The function should ereport() if it cannot use any of the modes the caller is
+willing to support.
+
+Value-per-call mode works like this: ReturnSetInfo contains a field
+"isDone", which should be set to one of these values:
+
+ ExprSingleResult /* expression does not return a set */
+ ExprMultipleResult /* this result is an element of a set */
+ ExprEndResult /* there are no more elements in the set */
+
+(the caller will initialize it to ExprSingleResult). If the function simply
+returns a Datum without touching ReturnSetInfo, then the call is over and a
+single-item set has been returned. To return a set, the function must set
+isDone to ExprMultipleResult for each set element. After all elements have
+been returned, the next call should set isDone to ExprEndResult and return a
+null result. (Note it is possible to return an empty set by doing this on
+the first call.)
+
+Value-per-call functions MUST NOT assume that they will be run to completion;
+the executor might simply stop calling them, for example because of a LIMIT.
+Therefore, it's unsafe to attempt to perform any resource cleanup in the
+final call. It's usually not necessary to clean up memory, anyway. If it's
+necessary to clean up other types of resources, such as file descriptors,
+one can register a shutdown callback function in the ExprContext pointed to
+by the ReturnSetInfo node. (But note that file descriptors are a limited
+resource, so it's generally unwise to hold those open across calls; SRFs
+that need file access are better written to do it in a single call using
+Materialize mode.)
+
+Materialize mode works like this: the function creates a Tuplestore holding
+the (possibly empty) result set, and returns it. There are no multiple calls.
+The function must also return a TupleDesc that indicates the tuple structure.
+The Tuplestore and TupleDesc should be created in the context
+econtext->ecxt_per_query_memory (note this will *not* be the context the
+function is called in). The function stores pointers to the Tuplestore and
+TupleDesc into ReturnSetInfo, sets returnMode to indicate materialize mode,
+and returns null. isDone is not used and should be left at ExprSingleResult.
+
+The Tuplestore must be created with randomAccess = true if
+SFRM_Materialize_Random is set in allowedModes, but it can (and preferably
+should) be created with randomAccess = false if not. Callers that can support
+both ValuePerCall and Materialize mode will set SFRM_Materialize_Preferred,
+or not, depending on which mode they prefer.
+
+If available, the expected tuple descriptor is passed in ReturnSetInfo;
+in other contexts the expectedDesc field will be NULL. The function need
+not pay attention to expectedDesc, but it may be useful in special cases.
+
+There is no support for functions accepting sets; instead, the function will
+be called multiple times, once for each element of the input set.
+
+
+Notes About Function Handlers
+-----------------------------
+
+Handlers for classes of functions should find life much easier and
+cleaner in this design. The OID of the called function is directly
+reachable from the passed parameters; we don't need the global variable
+fmgr_pl_finfo anymore. Also, by modifying fcinfo->flinfo->fn_extra,
+the handler can cache lookup info to avoid repeat lookups when the same
+function is invoked many times. (fn_extra can only be used as a hint,
+since callers are not required to re-use an FmgrInfo struct.
+But in performance-critical paths they normally will do so.)
+
+If the handler wants to allocate memory to hold fn_extra data, it should
+NOT do so in CurrentMemoryContext, since the current context may well be
+much shorter-lived than the context where the FmgrInfo is. Instead,
+allocate the memory in context flinfo->fn_mcxt, or in a long-lived cache
+context. fn_mcxt normally points at the context that was
+CurrentMemoryContext at the time the FmgrInfo structure was created;
+in any case it is required to be a context at least as long-lived as the
+FmgrInfo itself.
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
new file mode 100644
index 0000000..e8c6cdd
--- /dev/null
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -0,0 +1,755 @@
+/*-------------------------------------------------------------------------
+ *
+ * dfmgr.c
+ * Dynamic function manager code.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/fmgr/dfmgr.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#ifdef HAVE_DLOPEN
+#include <dlfcn.h>
+
+/*
+ * On macOS, <dlfcn.h> insists on including <stdbool.h>. If we're not
+ * using stdbool, undef bool to undo the damage.
+ */
+#ifndef PG_USE_STDBOOL
+#ifdef bool
+#undef bool
+#endif
+#endif
+#endif /* HAVE_DLOPEN */
+
+#include "fmgr.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#include "storage/shmem.h"
+#include "utils/hsearch.h"
+
+
+/* signatures for PostgreSQL-specific library init/fini functions */
+typedef void (*PG_init_t) (void);
+typedef void (*PG_fini_t) (void);
+
+/* hashtable entry for rendezvous variables */
+typedef struct
+{
+ char varName[NAMEDATALEN]; /* hash key (must be first) */
+ void *varValue;
+} rendezvousHashEntry;
+
+/*
+ * List of dynamically loaded files (kept in malloc'd memory).
+ */
+
+typedef struct df_files
+{
+ struct df_files *next; /* List link */
+ dev_t device; /* Device file is on */
+#ifndef WIN32 /* ensures we never again depend on this under
+ * win32 */
+ ino_t inode; /* Inode number of file */
+#endif
+ void *handle; /* a handle for pg_dl* functions */
+ char filename[FLEXIBLE_ARRAY_MEMBER]; /* Full pathname of file */
+} DynamicFileList;
+
+static DynamicFileList *file_list = NULL;
+static DynamicFileList *file_tail = NULL;
+
+/* stat() call under Win32 returns an st_ino field, but it has no meaning */
+#ifndef WIN32
+#define SAME_INODE(A,B) ((A).st_ino == (B).inode && (A).st_dev == (B).device)
+#else
+#define SAME_INODE(A,B) false
+#endif
+
+char *Dynamic_library_path;
+
+static void *internal_load_library(const char *libname);
+static void incompatible_module_error(const char *libname,
+ const Pg_magic_struct *module_magic_data) pg_attribute_noreturn();
+static void internal_unload_library(const char *libname);
+static bool file_exists(const char *name);
+static char *expand_dynamic_library_name(const char *name);
+static void check_restricted_library_name(const char *name);
+static char *substitute_libpath_macro(const char *name);
+static char *find_in_dynamic_libpath(const char *basename);
+
+/* Magic structure that module needs to match to be accepted */
+static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
+
+
+/*
+ * Load the specified dynamic-link library file, and look for a function
+ * named funcname in it.
+ *
+ * If the function is not found, we raise an error if signalNotFound is true,
+ * else return NULL. Note that errors in loading the library
+ * will provoke ereport() regardless of signalNotFound.
+ *
+ * If filehandle is not NULL, then *filehandle will be set to a handle
+ * identifying the library file. The filehandle can be used with
+ * lookup_external_function to lookup additional functions in the same file
+ * at less cost than repeating load_external_function.
+ */
+void *
+load_external_function(const char *filename, const char *funcname,
+ bool signalNotFound, void **filehandle)
+{
+ char *fullname;
+ void *lib_handle;
+ void *retval;
+
+ /* Expand the possibly-abbreviated filename to an exact path name */
+ fullname = expand_dynamic_library_name(filename);
+
+ /* Load the shared library, unless we already did */
+ lib_handle = internal_load_library(fullname);
+
+ /* Return handle if caller wants it */
+ if (filehandle)
+ *filehandle = lib_handle;
+
+ /* Look up the function within the library. */
+ retval = dlsym(lib_handle, funcname);
+
+ if (retval == NULL && signalNotFound)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not find function \"%s\" in file \"%s\"",
+ funcname, fullname)));
+
+ pfree(fullname);
+ return retval;
+}
+
+/*
+ * This function loads a shlib file without looking up any particular
+ * function in it. If the same shlib has previously been loaded,
+ * unload and reload it.
+ *
+ * When 'restricted' is true, only libraries in the presumed-secure
+ * directory $libdir/plugins may be referenced.
+ */
+void
+load_file(const char *filename, bool restricted)
+{
+ char *fullname;
+
+ /* Apply security restriction if requested */
+ if (restricted)
+ check_restricted_library_name(filename);
+
+ /* Expand the possibly-abbreviated filename to an exact path name */
+ fullname = expand_dynamic_library_name(filename);
+
+ /* Unload the library if currently loaded */
+ internal_unload_library(fullname);
+
+ /* Load the shared library */
+ (void) internal_load_library(fullname);
+
+ pfree(fullname);
+}
+
+/*
+ * Lookup a function whose library file is already loaded.
+ * Return NULL if not found.
+ */
+void *
+lookup_external_function(void *filehandle, const char *funcname)
+{
+ return dlsym(filehandle, funcname);
+}
+
+
+/*
+ * Load the specified dynamic-link library file, unless it already is
+ * loaded. Return the pg_dl* handle for the file.
+ *
+ * Note: libname is expected to be an exact name for the library file.
+ */
+static void *
+internal_load_library(const char *libname)
+{
+ DynamicFileList *file_scanner;
+ PGModuleMagicFunction magic_func;
+ char *load_error;
+ struct stat stat_buf;
+ PG_init_t PG_init;
+
+ /*
+ * Scan the list of loaded FILES to see if the file has been loaded.
+ */
+ for (file_scanner = file_list;
+ file_scanner != NULL &&
+ strcmp(libname, file_scanner->filename) != 0;
+ file_scanner = file_scanner->next)
+ ;
+
+ if (file_scanner == NULL)
+ {
+ /*
+ * Check for same files - different paths (ie, symlink or link)
+ */
+ if (stat(libname, &stat_buf) == -1)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not access file \"%s\": %m",
+ libname)));
+
+ for (file_scanner = file_list;
+ file_scanner != NULL &&
+ !SAME_INODE(stat_buf, *file_scanner);
+ file_scanner = file_scanner->next)
+ ;
+ }
+
+ if (file_scanner == NULL)
+ {
+ /*
+ * File not loaded yet.
+ */
+ file_scanner = (DynamicFileList *)
+ malloc(offsetof(DynamicFileList, filename) + strlen(libname) + 1);
+ if (file_scanner == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+
+ MemSet(file_scanner, 0, offsetof(DynamicFileList, filename));
+ strcpy(file_scanner->filename, libname);
+ file_scanner->device = stat_buf.st_dev;
+#ifndef WIN32
+ file_scanner->inode = stat_buf.st_ino;
+#endif
+ file_scanner->next = NULL;
+
+ file_scanner->handle = dlopen(file_scanner->filename, RTLD_NOW | RTLD_GLOBAL);
+ if (file_scanner->handle == NULL)
+ {
+ load_error = dlerror();
+ free((char *) file_scanner);
+ /* errcode_for_file_access might not be appropriate here? */
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not load library \"%s\": %s",
+ libname, load_error)));
+ }
+
+ /* Check the magic function to determine compatibility */
+ magic_func = (PGModuleMagicFunction)
+ dlsym(file_scanner->handle, PG_MAGIC_FUNCTION_NAME_STRING);
+ if (magic_func)
+ {
+ const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
+
+ if (magic_data_ptr->len != magic_data.len ||
+ memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0)
+ {
+ /* copy data block before unlinking library */
+ Pg_magic_struct module_magic_data = *magic_data_ptr;
+
+ /* try to close library */
+ dlclose(file_scanner->handle);
+ free((char *) file_scanner);
+
+ /* issue suitable complaint */
+ incompatible_module_error(libname, &module_magic_data);
+ }
+ }
+ else
+ {
+ /* try to close library */
+ dlclose(file_scanner->handle);
+ free((char *) file_scanner);
+ /* complain */
+ ereport(ERROR,
+ (errmsg("incompatible library \"%s\": missing magic block",
+ libname),
+ errhint("Extension libraries are required to use the PG_MODULE_MAGIC macro.")));
+ }
+
+ /*
+ * If the library has a _PG_init() function, call it.
+ */
+ PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init");
+ if (PG_init)
+ (*PG_init) ();
+
+ /* OK to link it into list */
+ if (file_list == NULL)
+ file_list = file_scanner;
+ else
+ file_tail->next = file_scanner;
+ file_tail = file_scanner;
+ }
+
+ return file_scanner->handle;
+}
+
+/*
+ * Report a suitable error for an incompatible magic block.
+ */
+static void
+incompatible_module_error(const char *libname,
+ const Pg_magic_struct *module_magic_data)
+{
+ StringInfoData details;
+
+ /*
+ * If the version doesn't match, just report that, because the rest of the
+ * block might not even have the fields we expect.
+ */
+ if (magic_data.version != module_magic_data->version)
+ {
+ char library_version[32];
+
+ if (module_magic_data->version >= 1000)
+ snprintf(library_version, sizeof(library_version), "%d",
+ module_magic_data->version / 100);
+ else
+ snprintf(library_version, sizeof(library_version), "%d.%d",
+ module_magic_data->version / 100,
+ module_magic_data->version % 100);
+ ereport(ERROR,
+ (errmsg("incompatible library \"%s\": version mismatch",
+ libname),
+ errdetail("Server is version %d, library is version %s.",
+ magic_data.version / 100, library_version)));
+ }
+
+ /*
+ * Otherwise, spell out which fields don't agree.
+ *
+ * XXX this code has to be adjusted any time the set of fields in a magic
+ * block change!
+ */
+ initStringInfo(&details);
+
+ if (module_magic_data->funcmaxargs != magic_data.funcmaxargs)
+ {
+ if (details.len)
+ appendStringInfoChar(&details, '\n');
+ appendStringInfo(&details,
+ _("Server has FUNC_MAX_ARGS = %d, library has %d."),
+ magic_data.funcmaxargs,
+ module_magic_data->funcmaxargs);
+ }
+ if (module_magic_data->indexmaxkeys != magic_data.indexmaxkeys)
+ {
+ if (details.len)
+ appendStringInfoChar(&details, '\n');
+ appendStringInfo(&details,
+ _("Server has INDEX_MAX_KEYS = %d, library has %d."),
+ magic_data.indexmaxkeys,
+ module_magic_data->indexmaxkeys);
+ }
+ if (module_magic_data->namedatalen != magic_data.namedatalen)
+ {
+ if (details.len)
+ appendStringInfoChar(&details, '\n');
+ appendStringInfo(&details,
+ _("Server has NAMEDATALEN = %d, library has %d."),
+ magic_data.namedatalen,
+ module_magic_data->namedatalen);
+ }
+ if (module_magic_data->float8byval != magic_data.float8byval)
+ {
+ if (details.len)
+ appendStringInfoChar(&details, '\n');
+ appendStringInfo(&details,
+ _("Server has FLOAT8PASSBYVAL = %s, library has %s."),
+ magic_data.float8byval ? "true" : "false",
+ module_magic_data->float8byval ? "true" : "false");
+ }
+
+ if (details.len == 0)
+ appendStringInfoString(&details,
+ _("Magic block has unexpected length or padding difference."));
+
+ ereport(ERROR,
+ (errmsg("incompatible library \"%s\": magic block mismatch",
+ libname),
+ errdetail_internal("%s", details.data)));
+}
+
+/*
+ * Unload the specified dynamic-link library file, if it is loaded.
+ *
+ * Note: libname is expected to be an exact name for the library file.
+ *
+ * XXX for the moment, this is disabled, resulting in LOAD of an already-loaded
+ * library always being a no-op. We might re-enable it someday if we can
+ * convince ourselves we have safe protocols for un-hooking from hook function
+ * pointers, releasing custom GUC variables, and perhaps other things that
+ * are definitely unsafe currently.
+ */
+static void
+internal_unload_library(const char *libname)
+{
+#ifdef NOT_USED
+ DynamicFileList *file_scanner,
+ *prv,
+ *nxt;
+ struct stat stat_buf;
+ PG_fini_t PG_fini;
+
+ /*
+ * We need to do stat() in order to determine whether this is the same
+ * file as a previously loaded file; it's also handy so as to give a good
+ * error message if bogus file name given.
+ */
+ if (stat(libname, &stat_buf) == -1)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not access file \"%s\": %m", libname)));
+
+ /*
+ * We have to zap all entries in the list that match on either filename or
+ * inode, else internal_load_library() will still think it's present.
+ */
+ prv = NULL;
+ for (file_scanner = file_list; file_scanner != NULL; file_scanner = nxt)
+ {
+ nxt = file_scanner->next;
+ if (strcmp(libname, file_scanner->filename) == 0 ||
+ SAME_INODE(stat_buf, *file_scanner))
+ {
+ if (prv)
+ prv->next = nxt;
+ else
+ file_list = nxt;
+
+ /*
+ * If the library has a _PG_fini() function, call it.
+ */
+ PG_fini = (PG_fini_t) dlsym(file_scanner->handle, "_PG_fini");
+ if (PG_fini)
+ (*PG_fini) ();
+
+ clear_external_function_hash(file_scanner->handle);
+ dlclose(file_scanner->handle);
+ free((char *) file_scanner);
+ /* prv does not change */
+ }
+ else
+ prv = file_scanner;
+ }
+#endif /* NOT_USED */
+}
+
+static bool
+file_exists(const char *name)
+{
+ struct stat st;
+
+ AssertArg(name != NULL);
+
+ if (stat(name, &st) == 0)
+ return S_ISDIR(st.st_mode) ? false : true;
+ else if (!(errno == ENOENT || errno == ENOTDIR || errno == EACCES))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not access file \"%s\": %m", name)));
+
+ return false;
+}
+
+
+/* Example format: ".so" */
+#ifndef DLSUFFIX
+#error "DLSUFFIX must be defined to compile this file."
+#endif
+
+/*
+ * If name contains a slash, check if the file exists, if so return
+ * the name. Else (no slash) try to expand using search path (see
+ * find_in_dynamic_libpath below); if that works, return the fully
+ * expanded file name. If the previous failed, append DLSUFFIX and
+ * try again. If all fails, just return the original name.
+ *
+ * The result will always be freshly palloc'd.
+ */
+static char *
+expand_dynamic_library_name(const char *name)
+{
+ bool have_slash;
+ char *new;
+ char *full;
+
+ AssertArg(name);
+
+ have_slash = (first_dir_separator(name) != NULL);
+
+ if (!have_slash)
+ {
+ full = find_in_dynamic_libpath(name);
+ if (full)
+ return full;
+ }
+ else
+ {
+ full = substitute_libpath_macro(name);
+ if (file_exists(full))
+ return full;
+ pfree(full);
+ }
+
+ new = psprintf("%s%s", name, DLSUFFIX);
+
+ if (!have_slash)
+ {
+ full = find_in_dynamic_libpath(new);
+ pfree(new);
+ if (full)
+ return full;
+ }
+ else
+ {
+ full = substitute_libpath_macro(new);
+ pfree(new);
+ if (file_exists(full))
+ return full;
+ pfree(full);
+ }
+
+ /*
+ * If we can't find the file, just return the string as-is. The ensuing
+ * load attempt will fail and report a suitable message.
+ */
+ return pstrdup(name);
+}
+
+/*
+ * Check a restricted library name. It must begin with "$libdir/plugins/"
+ * and there must not be any directory separators after that (this is
+ * sufficient to prevent ".." style attacks).
+ */
+static void
+check_restricted_library_name(const char *name)
+{
+ if (strncmp(name, "$libdir/plugins/", 16) != 0 ||
+ first_dir_separator(name + 16) != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("access to library \"%s\" is not allowed",
+ name)));
+}
+
+/*
+ * Substitute for any macros appearing in the given string.
+ * Result is always freshly palloc'd.
+ */
+static char *
+substitute_libpath_macro(const char *name)
+{
+ const char *sep_ptr;
+
+ AssertArg(name != NULL);
+
+ /* Currently, we only recognize $libdir at the start of the string */
+ if (name[0] != '$')
+ return pstrdup(name);
+
+ if ((sep_ptr = first_dir_separator(name)) == NULL)
+ sep_ptr = name + strlen(name);
+
+ if (strlen("$libdir") != sep_ptr - name ||
+ strncmp(name, "$libdir", strlen("$libdir")) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid macro name in dynamic library path: %s",
+ name)));
+
+ return psprintf("%s%s", pkglib_path, sep_ptr);
+}
+
+
+/*
+ * Search for a file called 'basename' in the colon-separated search
+ * path Dynamic_library_path. If the file is found, the full file name
+ * is returned in freshly palloc'd memory. If the file is not found,
+ * return NULL.
+ */
+static char *
+find_in_dynamic_libpath(const char *basename)
+{
+ const char *p;
+ size_t baselen;
+
+ AssertArg(basename != NULL);
+ AssertArg(first_dir_separator(basename) == NULL);
+ AssertState(Dynamic_library_path != NULL);
+
+ p = Dynamic_library_path;
+ if (strlen(p) == 0)
+ return NULL;
+
+ baselen = strlen(basename);
+
+ for (;;)
+ {
+ size_t len;
+ char *piece;
+ char *mangled;
+ char *full;
+
+ piece = first_path_var_separator(p);
+ if (piece == p)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("zero-length component in parameter \"dynamic_library_path\"")));
+
+ if (piece == NULL)
+ len = strlen(p);
+ else
+ len = piece - p;
+
+ piece = palloc(len + 1);
+ strlcpy(piece, p, len + 1);
+
+ mangled = substitute_libpath_macro(piece);
+ pfree(piece);
+
+ canonicalize_path(mangled);
+
+ /* only absolute paths */
+ if (!is_absolute_path(mangled))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("component in parameter \"dynamic_library_path\" is not an absolute path")));
+
+ full = palloc(strlen(mangled) + 1 + baselen + 1);
+ sprintf(full, "%s/%s", mangled, basename);
+ pfree(mangled);
+
+ elog(DEBUG3, "find_in_dynamic_libpath: trying \"%s\"", full);
+
+ if (file_exists(full))
+ return full;
+
+ pfree(full);
+
+ if (p[len] == '\0')
+ break;
+ else
+ p += len + 1;
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Find (or create) a rendezvous variable that one dynamically
+ * loaded library can use to meet up with another.
+ *
+ * On the first call of this function for a particular varName,
+ * a "rendezvous variable" is created with the given name.
+ * The value of the variable is a void pointer (initially set to NULL).
+ * Subsequent calls with the same varName just return the address of
+ * the existing variable. Once created, a rendezvous variable lasts
+ * for the life of the process.
+ *
+ * Dynamically loaded libraries can use rendezvous variables
+ * to find each other and share information: they just need to agree
+ * on the variable name and the data it will point to.
+ */
+void **
+find_rendezvous_variable(const char *varName)
+{
+ static HTAB *rendezvousHash = NULL;
+
+ rendezvousHashEntry *hentry;
+ bool found;
+
+ /* Create a hashtable if we haven't already done so in this process */
+ if (rendezvousHash == NULL)
+ {
+ HASHCTL ctl;
+
+ ctl.keysize = NAMEDATALEN;
+ ctl.entrysize = sizeof(rendezvousHashEntry);
+ rendezvousHash = hash_create("Rendezvous variable hash",
+ 16,
+ &ctl,
+ HASH_ELEM | HASH_STRINGS);
+ }
+
+ /* Find or create the hashtable entry for this varName */
+ hentry = (rendezvousHashEntry *) hash_search(rendezvousHash,
+ varName,
+ HASH_ENTER,
+ &found);
+
+ /* Initialize to NULL if first time */
+ if (!found)
+ hentry->varValue = NULL;
+
+ return &hentry->varValue;
+}
+
+/*
+ * Estimate the amount of space needed to serialize the list of libraries
+ * we have loaded.
+ */
+Size
+EstimateLibraryStateSpace(void)
+{
+ DynamicFileList *file_scanner;
+ Size size = 1;
+
+ for (file_scanner = file_list;
+ file_scanner != NULL;
+ file_scanner = file_scanner->next)
+ size = add_size(size, strlen(file_scanner->filename) + 1);
+
+ return size;
+}
+
+/*
+ * Serialize the list of libraries we have loaded to a chunk of memory.
+ */
+void
+SerializeLibraryState(Size maxsize, char *start_address)
+{
+ DynamicFileList *file_scanner;
+
+ for (file_scanner = file_list;
+ file_scanner != NULL;
+ file_scanner = file_scanner->next)
+ {
+ Size len;
+
+ len = strlcpy(start_address, file_scanner->filename, maxsize) + 1;
+ Assert(len < maxsize);
+ maxsize -= len;
+ start_address += len;
+ }
+ start_address[0] = '\0';
+}
+
+/*
+ * Load every library the serializing backend had loaded.
+ */
+void
+RestoreLibraryState(char *start_address)
+{
+ while (*start_address != '\0')
+ {
+ internal_load_library(start_address);
+ start_address += strlen(start_address) + 1;
+ }
+}
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
new file mode 100644
index 0000000..3dfe6e5
--- /dev/null
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -0,0 +1,2090 @@
+/*-------------------------------------------------------------------------
+ *
+ * fmgr.c
+ * The Postgres function manager.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/fmgr/fmgr.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/detoast.h"
+#include "catalog/pg_language.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "executor/functions.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "pgstat.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/fmgrtab.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+/*
+ * Hooks for function calls
+ */
+PGDLLIMPORT needs_fmgr_hook_type needs_fmgr_hook = NULL;
+PGDLLIMPORT fmgr_hook_type fmgr_hook = NULL;
+
+/*
+ * Hashtable for fast lookup of external C functions
+ */
+typedef struct
+{
+ /* fn_oid is the hash key and so must be first! */
+ Oid fn_oid; /* OID of an external C function */
+ TransactionId fn_xmin; /* for checking up-to-dateness */
+ ItemPointerData fn_tid;
+ PGFunction user_fn; /* the function's address */
+ const Pg_finfo_record *inforec; /* address of its info record */
+} CFuncHashTabEntry;
+
+static HTAB *CFuncHash = NULL;
+
+
+static void fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
+ bool ignore_security);
+static void fmgr_info_C_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple);
+static void fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple);
+static CFuncHashTabEntry *lookup_C_func(HeapTuple procedureTuple);
+static void record_C_func(HeapTuple procedureTuple,
+ PGFunction user_fn, const Pg_finfo_record *inforec);
+
+/* extern so it's callable via JIT */
+extern Datum fmgr_security_definer(PG_FUNCTION_ARGS);
+
+
+/*
+ * Lookup routines for builtin-function table. We can search by either Oid
+ * or name, but search by Oid is much faster.
+ */
+
+static const FmgrBuiltin *
+fmgr_isbuiltin(Oid id)
+{
+ uint16 index;
+
+ /* fast lookup only possible if original oid still assigned */
+ if (id > fmgr_last_builtin_oid)
+ return NULL;
+
+ /*
+ * Lookup function data. If there's a miss in that range it's likely a
+ * nonexistent function, returning NULL here will trigger an ERROR later.
+ */
+ index = fmgr_builtin_oid_index[id];
+ if (index == InvalidOidBuiltinMapping)
+ return NULL;
+
+ return &fmgr_builtins[index];
+}
+
+/*
+ * Lookup a builtin by name. Note there can be more than one entry in
+ * the array with the same name, but they should all point to the same
+ * routine.
+ */
+static const FmgrBuiltin *
+fmgr_lookupByName(const char *name)
+{
+ int i;
+
+ for (i = 0; i < fmgr_nbuiltins; i++)
+ {
+ if (strcmp(name, fmgr_builtins[i].funcName) == 0)
+ return fmgr_builtins + i;
+ }
+ return NULL;
+}
+
+/*
+ * This routine fills a FmgrInfo struct, given the OID
+ * of the function to be called.
+ *
+ * The caller's CurrentMemoryContext is used as the fn_mcxt of the info
+ * struct; this means that any subsidiary data attached to the info struct
+ * (either by fmgr_info itself, or later on by a function call handler)
+ * will be allocated in that context. The caller must ensure that this
+ * context is at least as long-lived as the info struct itself. This is
+ * not a problem in typical cases where the info struct is on the stack or
+ * in freshly-palloc'd space. However, if one intends to store an info
+ * struct in a long-lived table, it's better to use fmgr_info_cxt.
+ */
+void
+fmgr_info(Oid functionId, FmgrInfo *finfo)
+{
+ fmgr_info_cxt_security(functionId, finfo, CurrentMemoryContext, false);
+}
+
+/*
+ * Fill a FmgrInfo struct, specifying a memory context in which its
+ * subsidiary data should go.
+ */
+void
+fmgr_info_cxt(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt)
+{
+ fmgr_info_cxt_security(functionId, finfo, mcxt, false);
+}
+
+/*
+ * This one does the actual work. ignore_security is ordinarily false
+ * but is set to true when we need to avoid recursion.
+ */
+static void
+fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
+ bool ignore_security)
+{
+ const FmgrBuiltin *fbp;
+ HeapTuple procedureTuple;
+ Form_pg_proc procedureStruct;
+ Datum prosrcdatum;
+ bool isnull;
+ char *prosrc;
+
+ /*
+ * fn_oid *must* be filled in last. Some code assumes that if fn_oid is
+ * valid, the whole struct is valid. Some FmgrInfo struct's do survive
+ * elogs.
+ */
+ finfo->fn_oid = InvalidOid;
+ finfo->fn_extra = NULL;
+ finfo->fn_mcxt = mcxt;
+ finfo->fn_expr = NULL; /* caller may set this later */
+
+ if ((fbp = fmgr_isbuiltin(functionId)) != NULL)
+ {
+ /*
+ * Fast path for builtin functions: don't bother consulting pg_proc
+ */
+ finfo->fn_nargs = fbp->nargs;
+ finfo->fn_strict = fbp->strict;
+ finfo->fn_retset = fbp->retset;
+ finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */
+ finfo->fn_addr = fbp->func;
+ finfo->fn_oid = functionId;
+ return;
+ }
+
+ /* Otherwise we need the pg_proc entry */
+ procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId));
+ if (!HeapTupleIsValid(procedureTuple))
+ elog(ERROR, "cache lookup failed for function %u", functionId);
+ procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
+
+ finfo->fn_nargs = procedureStruct->pronargs;
+ finfo->fn_strict = procedureStruct->proisstrict;
+ finfo->fn_retset = procedureStruct->proretset;
+
+ /*
+ * If it has prosecdef set, non-null proconfig, or if a plugin wants to
+ * hook function entry/exit, use fmgr_security_definer call handler ---
+ * unless we are being called again by fmgr_security_definer or
+ * fmgr_info_other_lang.
+ *
+ * When using fmgr_security_definer, function stats tracking is always
+ * disabled at the outer level, and instead we set the flag properly in
+ * fmgr_security_definer's private flinfo and implement the tracking
+ * inside fmgr_security_definer. This loses the ability to charge the
+ * overhead of fmgr_security_definer to the function, but gains the
+ * ability to set the track_functions GUC as a local GUC parameter of an
+ * interesting function and have the right things happen.
+ */
+ if (!ignore_security &&
+ (procedureStruct->prosecdef ||
+ !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) ||
+ FmgrHookIsNeeded(functionId)))
+ {
+ finfo->fn_addr = fmgr_security_definer;
+ finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */
+ finfo->fn_oid = functionId;
+ ReleaseSysCache(procedureTuple);
+ return;
+ }
+
+ switch (procedureStruct->prolang)
+ {
+ case INTERNALlanguageId:
+
+ /*
+ * For an ordinary builtin function, we should never get here
+ * because the fmgr_isbuiltin() search above will have succeeded.
+ * However, if the user has done a CREATE FUNCTION to create an
+ * alias for a builtin function, we can end up here. In that case
+ * we have to look up the function by name. The name of the
+ * internal function is stored in prosrc (it doesn't have to be
+ * the same as the name of the alias!)
+ */
+ prosrcdatum = SysCacheGetAttr(PROCOID, procedureTuple,
+ Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc");
+ prosrc = TextDatumGetCString(prosrcdatum);
+ fbp = fmgr_lookupByName(prosrc);
+ if (fbp == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("internal function \"%s\" is not in internal lookup table",
+ prosrc)));
+ pfree(prosrc);
+ /* Should we check that nargs, strict, retset match the table? */
+ finfo->fn_addr = fbp->func;
+ /* note this policy is also assumed in fast path above */
+ finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */
+ break;
+
+ case ClanguageId:
+ fmgr_info_C_lang(functionId, finfo, procedureTuple);
+ finfo->fn_stats = TRACK_FUNC_PL; /* ie, track if ALL */
+ break;
+
+ case SQLlanguageId:
+ finfo->fn_addr = fmgr_sql;
+ finfo->fn_stats = TRACK_FUNC_PL; /* ie, track if ALL */
+ break;
+
+ default:
+ fmgr_info_other_lang(functionId, finfo, procedureTuple);
+ finfo->fn_stats = TRACK_FUNC_OFF; /* ie, track if not OFF */
+ break;
+ }
+
+ finfo->fn_oid = functionId;
+ ReleaseSysCache(procedureTuple);
+}
+
+/*
+ * Return module and C function name providing implementation of functionId.
+ *
+ * If *mod == NULL and *fn == NULL, no C symbol is known to implement
+ * function.
+ *
+ * If *mod == NULL and *fn != NULL, the function is implemented by a symbol in
+ * the main binary.
+ *
+ * If *mod != NULL and *fn != NULL the function is implemented in an extension
+ * shared object.
+ *
+ * The returned module and function names are pstrdup'ed into the current
+ * memory context.
+ */
+void
+fmgr_symbol(Oid functionId, char **mod, char **fn)
+{
+ HeapTuple procedureTuple;
+ Form_pg_proc procedureStruct;
+ bool isnull;
+ Datum prosrcattr;
+ Datum probinattr;
+
+ procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId));
+ if (!HeapTupleIsValid(procedureTuple))
+ elog(ERROR, "cache lookup failed for function %u", functionId);
+ procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
+
+ if (procedureStruct->prosecdef ||
+ !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) ||
+ FmgrHookIsNeeded(functionId))
+ {
+ *mod = NULL; /* core binary */
+ *fn = pstrdup("fmgr_security_definer");
+ ReleaseSysCache(procedureTuple);
+ return;
+ }
+
+ /* see fmgr_info_cxt_security for the individual cases */
+ switch (procedureStruct->prolang)
+ {
+ case INTERNALlanguageId:
+ prosrcattr = SysCacheGetAttr(PROCOID, procedureTuple,
+ Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc");
+
+ *mod = NULL; /* core binary */
+ *fn = TextDatumGetCString(prosrcattr);
+ break;
+
+ case ClanguageId:
+ prosrcattr = SysCacheGetAttr(PROCOID, procedureTuple,
+ Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc for C function %u", functionId);
+
+ probinattr = SysCacheGetAttr(PROCOID, procedureTuple,
+ Anum_pg_proc_probin, &isnull);
+ if (isnull)
+ elog(ERROR, "null probin for C function %u", functionId);
+
+ /*
+ * No need to check symbol presence / API version here, already
+ * checked in fmgr_info_cxt_security.
+ */
+ *mod = TextDatumGetCString(probinattr);
+ *fn = TextDatumGetCString(prosrcattr);
+ break;
+
+ case SQLlanguageId:
+ *mod = NULL; /* core binary */
+ *fn = pstrdup("fmgr_sql");
+ break;
+
+ default:
+ *mod = NULL;
+ *fn = NULL; /* unknown, pass pointer */
+ break;
+ }
+
+ ReleaseSysCache(procedureTuple);
+}
+
+
+/*
+ * Special fmgr_info processing for C-language functions. Note that
+ * finfo->fn_oid is not valid yet.
+ */
+static void
+fmgr_info_C_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple)
+{
+ CFuncHashTabEntry *hashentry;
+ PGFunction user_fn;
+ const Pg_finfo_record *inforec;
+ bool isnull;
+
+ /*
+ * See if we have the function address cached already
+ */
+ hashentry = lookup_C_func(procedureTuple);
+ if (hashentry)
+ {
+ user_fn = hashentry->user_fn;
+ inforec = hashentry->inforec;
+ }
+ else
+ {
+ Datum prosrcattr,
+ probinattr;
+ char *prosrcstring,
+ *probinstring;
+ void *libraryhandle;
+
+ /*
+ * Get prosrc and probin strings (link symbol and library filename).
+ * While in general these columns might be null, that's not allowed
+ * for C-language functions.
+ */
+ prosrcattr = SysCacheGetAttr(PROCOID, procedureTuple,
+ Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc for C function %u", functionId);
+ prosrcstring = TextDatumGetCString(prosrcattr);
+
+ probinattr = SysCacheGetAttr(PROCOID, procedureTuple,
+ Anum_pg_proc_probin, &isnull);
+ if (isnull)
+ elog(ERROR, "null probin for C function %u", functionId);
+ probinstring = TextDatumGetCString(probinattr);
+
+ /* Look up the function itself */
+ user_fn = load_external_function(probinstring, prosrcstring, true,
+ &libraryhandle);
+
+ /* Get the function information record (real or default) */
+ inforec = fetch_finfo_record(libraryhandle, prosrcstring);
+
+ /* Cache the addresses for later calls */
+ record_C_func(procedureTuple, user_fn, inforec);
+
+ pfree(prosrcstring);
+ pfree(probinstring);
+ }
+
+ switch (inforec->api_version)
+ {
+ case 1:
+ /* New style: call directly */
+ finfo->fn_addr = user_fn;
+ break;
+ default:
+ /* Shouldn't get here if fetch_finfo_record did its job */
+ elog(ERROR, "unrecognized function API version: %d",
+ inforec->api_version);
+ break;
+ }
+}
+
+/*
+ * Special fmgr_info processing for other-language functions. Note
+ * that finfo->fn_oid is not valid yet.
+ */
+static void
+fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple)
+{
+ Form_pg_proc procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
+ Oid language = procedureStruct->prolang;
+ HeapTuple languageTuple;
+ Form_pg_language languageStruct;
+ FmgrInfo plfinfo;
+
+ languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(language));
+ if (!HeapTupleIsValid(languageTuple))
+ elog(ERROR, "cache lookup failed for language %u", language);
+ languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+
+ /*
+ * Look up the language's call handler function, ignoring any attributes
+ * that would normally cause insertion of fmgr_security_definer. We need
+ * to get back a bare pointer to the actual C-language function.
+ */
+ fmgr_info_cxt_security(languageStruct->lanplcallfoid, &plfinfo,
+ CurrentMemoryContext, true);
+ finfo->fn_addr = plfinfo.fn_addr;
+
+ ReleaseSysCache(languageTuple);
+}
+
+/*
+ * Fetch and validate the information record for the given external function.
+ * The function is specified by a handle for the containing library
+ * (obtained from load_external_function) as well as the function name.
+ *
+ * If no info function exists for the given name an error is raised.
+ *
+ * This function is broken out of fmgr_info_C_lang so that fmgr_c_validator
+ * can validate the information record for a function not yet entered into
+ * pg_proc.
+ */
+const Pg_finfo_record *
+fetch_finfo_record(void *filehandle, const char *funcname)
+{
+ char *infofuncname;
+ PGFInfoFunction infofunc;
+ const Pg_finfo_record *inforec;
+
+ infofuncname = psprintf("pg_finfo_%s", funcname);
+
+ /* Try to look up the info function */
+ infofunc = (PGFInfoFunction) lookup_external_function(filehandle,
+ infofuncname);
+ if (infofunc == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not find function information for function \"%s\"",
+ funcname),
+ errhint("SQL-callable functions need an accompanying PG_FUNCTION_INFO_V1(funcname).")));
+ return NULL; /* silence compiler */
+ }
+
+ /* Found, so call it */
+ inforec = (*infofunc) ();
+
+ /* Validate result as best we can */
+ if (inforec == NULL)
+ elog(ERROR, "null result from info function \"%s\"", infofuncname);
+ switch (inforec->api_version)
+ {
+ case 1:
+ /* OK, no additional fields to validate */
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized API version %d reported by info function \"%s\"",
+ inforec->api_version, infofuncname)));
+ break;
+ }
+
+ pfree(infofuncname);
+ return inforec;
+}
+
+
+/*-------------------------------------------------------------------------
+ * Routines for caching lookup information for external C functions.
+ *
+ * The routines in dfmgr.c are relatively slow, so we try to avoid running
+ * them more than once per external function per session. We use a hash table
+ * with the function OID as the lookup key.
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * lookup_C_func: try to find a C function in the hash table
+ *
+ * If an entry exists and is up to date, return it; else return NULL
+ */
+static CFuncHashTabEntry *
+lookup_C_func(HeapTuple procedureTuple)
+{
+ Oid fn_oid = ((Form_pg_proc) GETSTRUCT(procedureTuple))->oid;
+ CFuncHashTabEntry *entry;
+
+ if (CFuncHash == NULL)
+ return NULL; /* no table yet */
+ entry = (CFuncHashTabEntry *)
+ hash_search(CFuncHash,
+ &fn_oid,
+ HASH_FIND,
+ NULL);
+ if (entry == NULL)
+ return NULL; /* no such entry */
+ if (entry->fn_xmin == HeapTupleHeaderGetRawXmin(procedureTuple->t_data) &&
+ ItemPointerEquals(&entry->fn_tid, &procedureTuple->t_self))
+ return entry; /* OK */
+ return NULL; /* entry is out of date */
+}
+
+/*
+ * record_C_func: enter (or update) info about a C function in the hash table
+ */
+static void
+record_C_func(HeapTuple procedureTuple,
+ PGFunction user_fn, const Pg_finfo_record *inforec)
+{
+ Oid fn_oid = ((Form_pg_proc) GETSTRUCT(procedureTuple))->oid;
+ CFuncHashTabEntry *entry;
+ bool found;
+
+ /* Create the hash table if it doesn't exist yet */
+ if (CFuncHash == NULL)
+ {
+ HASHCTL hash_ctl;
+
+ hash_ctl.keysize = sizeof(Oid);
+ hash_ctl.entrysize = sizeof(CFuncHashTabEntry);
+ CFuncHash = hash_create("CFuncHash",
+ 100,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+ }
+
+ entry = (CFuncHashTabEntry *)
+ hash_search(CFuncHash,
+ &fn_oid,
+ HASH_ENTER,
+ &found);
+ /* OID is already filled in */
+ entry->fn_xmin = HeapTupleHeaderGetRawXmin(procedureTuple->t_data);
+ entry->fn_tid = procedureTuple->t_self;
+ entry->user_fn = user_fn;
+ entry->inforec = inforec;
+}
+
+/*
+ * clear_external_function_hash: remove entries for a library being closed
+ *
+ * Presently we just zap the entire hash table, but later it might be worth
+ * the effort to remove only the entries associated with the given handle.
+ */
+void
+clear_external_function_hash(void *filehandle)
+{
+ if (CFuncHash)
+ hash_destroy(CFuncHash);
+ CFuncHash = NULL;
+}
+
+
+/*
+ * Copy an FmgrInfo struct
+ *
+ * This is inherently somewhat bogus since we can't reliably duplicate
+ * language-dependent subsidiary info. We cheat by zeroing fn_extra,
+ * instead, meaning that subsidiary info will have to be recomputed.
+ */
+void
+fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo,
+ MemoryContext destcxt)
+{
+ memcpy(dstinfo, srcinfo, sizeof(FmgrInfo));
+ dstinfo->fn_mcxt = destcxt;
+ dstinfo->fn_extra = NULL;
+}
+
+
+/*
+ * Specialized lookup routine for fmgr_internal_validator: given the alleged
+ * name of an internal function, return the OID of the function.
+ * If the name is not recognized, return InvalidOid.
+ */
+Oid
+fmgr_internal_function(const char *proname)
+{
+ const FmgrBuiltin *fbp = fmgr_lookupByName(proname);
+
+ if (fbp == NULL)
+ return InvalidOid;
+ return fbp->foid;
+}
+
+
+/*
+ * Support for security-definer and proconfig-using functions. We support
+ * both of these features using the same call handler, because they are
+ * often used together and it would be inefficient (as well as notationally
+ * messy) to have two levels of call handler involved.
+ */
+struct fmgr_security_definer_cache
+{
+ FmgrInfo flinfo; /* lookup info for target function */
+ Oid userid; /* userid to set, or InvalidOid */
+ ArrayType *proconfig; /* GUC values to set, or NULL */
+ Datum arg; /* passthrough argument for plugin modules */
+};
+
+/*
+ * Function handler for security-definer/proconfig/plugin-hooked functions.
+ * We extract the OID of the actual function and do a fmgr lookup again.
+ * Then we fetch the pg_proc row and copy the owner ID and proconfig fields.
+ * (All this info is cached for the duration of the current query.)
+ * To execute a call, we temporarily replace the flinfo with the cached
+ * and looked-up one, while keeping the outer fcinfo (which contains all
+ * the actual arguments, etc.) intact. This is not re-entrant, but then
+ * the fcinfo itself can't be used reentrantly anyway.
+ */
+extern Datum
+fmgr_security_definer(PG_FUNCTION_ARGS)
+{
+ Datum result;
+ struct fmgr_security_definer_cache *volatile fcache;
+ FmgrInfo *save_flinfo;
+ Oid save_userid;
+ int save_sec_context;
+ volatile int save_nestlevel;
+ PgStat_FunctionCallUsage fcusage;
+
+ if (!fcinfo->flinfo->fn_extra)
+ {
+ HeapTuple tuple;
+ Form_pg_proc procedureStruct;
+ Datum datum;
+ bool isnull;
+ MemoryContext oldcxt;
+
+ fcache = MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
+ sizeof(*fcache));
+
+ fmgr_info_cxt_security(fcinfo->flinfo->fn_oid, &fcache->flinfo,
+ fcinfo->flinfo->fn_mcxt, true);
+ fcache->flinfo.fn_expr = fcinfo->flinfo->fn_expr;
+
+ tuple = SearchSysCache1(PROCOID,
+ ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u",
+ fcinfo->flinfo->fn_oid);
+ procedureStruct = (Form_pg_proc) GETSTRUCT(tuple);
+
+ if (procedureStruct->prosecdef)
+ fcache->userid = procedureStruct->proowner;
+
+ datum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proconfig,
+ &isnull);
+ if (!isnull)
+ {
+ oldcxt = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
+ fcache->proconfig = DatumGetArrayTypePCopy(datum);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ ReleaseSysCache(tuple);
+
+ fcinfo->flinfo->fn_extra = fcache;
+ }
+ else
+ fcache = fcinfo->flinfo->fn_extra;
+
+ /* GetUserIdAndSecContext is cheap enough that no harm in a wasted call */
+ GetUserIdAndSecContext(&save_userid, &save_sec_context);
+ if (fcache->proconfig) /* Need a new GUC nesting level */
+ save_nestlevel = NewGUCNestLevel();
+ else
+ save_nestlevel = 0; /* keep compiler quiet */
+
+ if (OidIsValid(fcache->userid))
+ SetUserIdAndSecContext(fcache->userid,
+ save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+ if (fcache->proconfig)
+ {
+ ProcessGUCArray(fcache->proconfig,
+ (superuser() ? PGC_SUSET : PGC_USERSET),
+ PGC_S_SESSION,
+ GUC_ACTION_SAVE);
+ }
+
+ /* function manager hook */
+ if (fmgr_hook)
+ (*fmgr_hook) (FHET_START, &fcache->flinfo, &fcache->arg);
+
+ /*
+ * We don't need to restore GUC or userid settings on error, because the
+ * ensuing xact or subxact abort will do that. The PG_TRY block is only
+ * needed to clean up the flinfo link.
+ */
+ save_flinfo = fcinfo->flinfo;
+
+ PG_TRY();
+ {
+ fcinfo->flinfo = &fcache->flinfo;
+
+ /* See notes in fmgr_info_cxt_security */
+ pgstat_init_function_usage(fcinfo, &fcusage);
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /*
+ * We could be calling either a regular or a set-returning function,
+ * so we have to test to see what finalize flag to use.
+ */
+ pgstat_end_function_usage(&fcusage,
+ (fcinfo->resultinfo == NULL ||
+ !IsA(fcinfo->resultinfo, ReturnSetInfo) ||
+ ((ReturnSetInfo *) fcinfo->resultinfo)->isDone != ExprMultipleResult));
+ }
+ PG_CATCH();
+ {
+ fcinfo->flinfo = save_flinfo;
+ if (fmgr_hook)
+ (*fmgr_hook) (FHET_ABORT, &fcache->flinfo, &fcache->arg);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ fcinfo->flinfo = save_flinfo;
+
+ if (fcache->proconfig)
+ AtEOXact_GUC(true, save_nestlevel);
+ if (OidIsValid(fcache->userid))
+ SetUserIdAndSecContext(save_userid, save_sec_context);
+ if (fmgr_hook)
+ (*fmgr_hook) (FHET_END, &fcache->flinfo, &fcache->arg);
+
+ return result;
+}
+
+
+/*-------------------------------------------------------------------------
+ * Support routines for callers of fmgr-compatible functions
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * These are for invocation of a specifically named function with a
+ * directly-computed parameter list. Note that neither arguments nor result
+ * are allowed to be NULL. Also, the function cannot be one that needs to
+ * look at FmgrInfo, since there won't be any.
+ */
+Datum
+DirectFunctionCall1Coll(PGFunction func, Oid collation, Datum arg1)
+{
+ LOCAL_FCINFO(fcinfo, 1);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, NULL, 1, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+Datum
+DirectFunctionCall2Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2)
+{
+ LOCAL_FCINFO(fcinfo, 2);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, NULL, 2, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+Datum
+DirectFunctionCall3Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3)
+{
+ LOCAL_FCINFO(fcinfo, 3);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, NULL, 3, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+Datum
+DirectFunctionCall4Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4)
+{
+ LOCAL_FCINFO(fcinfo, 4);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, NULL, 4, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+Datum
+DirectFunctionCall5Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5)
+{
+ LOCAL_FCINFO(fcinfo, 5);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, NULL, 5, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+Datum
+DirectFunctionCall6Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6)
+{
+ LOCAL_FCINFO(fcinfo, 6);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, NULL, 6, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+ fcinfo->args[5].value = arg6;
+ fcinfo->args[5].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+Datum
+DirectFunctionCall7Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6, Datum arg7)
+{
+ LOCAL_FCINFO(fcinfo, 7);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, NULL, 7, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+ fcinfo->args[5].value = arg6;
+ fcinfo->args[5].isnull = false;
+ fcinfo->args[6].value = arg7;
+ fcinfo->args[6].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+Datum
+DirectFunctionCall8Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6, Datum arg7, Datum arg8)
+{
+ LOCAL_FCINFO(fcinfo, 8);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, NULL, 8, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+ fcinfo->args[5].value = arg6;
+ fcinfo->args[5].isnull = false;
+ fcinfo->args[6].value = arg7;
+ fcinfo->args[6].isnull = false;
+ fcinfo->args[7].value = arg8;
+ fcinfo->args[7].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+Datum
+DirectFunctionCall9Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6, Datum arg7, Datum arg8,
+ Datum arg9)
+{
+ LOCAL_FCINFO(fcinfo, 9);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, NULL, 9, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+ fcinfo->args[5].value = arg6;
+ fcinfo->args[5].isnull = false;
+ fcinfo->args[6].value = arg7;
+ fcinfo->args[6].isnull = false;
+ fcinfo->args[7].value = arg8;
+ fcinfo->args[7].isnull = false;
+ fcinfo->args[8].value = arg9;
+ fcinfo->args[8].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+/*
+ * These functions work like the DirectFunctionCall functions except that
+ * they use the flinfo parameter to initialise the fcinfo for the call.
+ * It's recommended that the callee only use the fn_extra and fn_mcxt
+ * fields, as other fields will typically describe the calling function
+ * not the callee. Conversely, the calling function should not have
+ * used fn_extra, unless its use is known to be compatible with the callee's.
+ */
+
+Datum
+CallerFInfoFunctionCall1(PGFunction func, FmgrInfo *flinfo, Oid collation, Datum arg1)
+{
+ LOCAL_FCINFO(fcinfo, 1);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 1, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+Datum
+CallerFInfoFunctionCall2(PGFunction func, FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2)
+{
+ LOCAL_FCINFO(fcinfo, 2);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 2, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+
+ result = (*func) (fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %p returned NULL", (void *) func);
+
+ return result;
+}
+
+/*
+ * These are for invocation of a previously-looked-up function with a
+ * directly-computed parameter list. Note that neither arguments nor result
+ * are allowed to be NULL.
+ */
+Datum
+FunctionCall0Coll(FmgrInfo *flinfo, Oid collation)
+{
+ LOCAL_FCINFO(fcinfo, 0);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 0, collation, NULL, NULL);
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+Datum
+FunctionCall1Coll(FmgrInfo *flinfo, Oid collation, Datum arg1)
+{
+ LOCAL_FCINFO(fcinfo, 1);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 1, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+Datum
+FunctionCall2Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2)
+{
+ LOCAL_FCINFO(fcinfo, 2);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 2, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+Datum
+FunctionCall3Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3)
+{
+ LOCAL_FCINFO(fcinfo, 3);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 3, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+Datum
+FunctionCall4Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4)
+{
+ LOCAL_FCINFO(fcinfo, 4);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 4, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+Datum
+FunctionCall5Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5)
+{
+ LOCAL_FCINFO(fcinfo, 5);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 5, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+Datum
+FunctionCall6Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6)
+{
+ LOCAL_FCINFO(fcinfo, 6);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 6, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+ fcinfo->args[5].value = arg6;
+ fcinfo->args[5].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+Datum
+FunctionCall7Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6, Datum arg7)
+{
+ LOCAL_FCINFO(fcinfo, 7);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 7, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+ fcinfo->args[5].value = arg6;
+ fcinfo->args[5].isnull = false;
+ fcinfo->args[6].value = arg7;
+ fcinfo->args[6].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+Datum
+FunctionCall8Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6, Datum arg7, Datum arg8)
+{
+ LOCAL_FCINFO(fcinfo, 8);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 8, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+ fcinfo->args[5].value = arg6;
+ fcinfo->args[5].isnull = false;
+ fcinfo->args[6].value = arg7;
+ fcinfo->args[6].isnull = false;
+ fcinfo->args[7].value = arg8;
+ fcinfo->args[7].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+Datum
+FunctionCall9Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6, Datum arg7, Datum arg8,
+ Datum arg9)
+{
+ LOCAL_FCINFO(fcinfo, 9);
+ Datum result;
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 9, collation, NULL, NULL);
+
+ fcinfo->args[0].value = arg1;
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = arg2;
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = arg3;
+ fcinfo->args[2].isnull = false;
+ fcinfo->args[3].value = arg4;
+ fcinfo->args[3].isnull = false;
+ fcinfo->args[4].value = arg5;
+ fcinfo->args[4].isnull = false;
+ fcinfo->args[5].value = arg6;
+ fcinfo->args[5].isnull = false;
+ fcinfo->args[6].value = arg7;
+ fcinfo->args[6].isnull = false;
+ fcinfo->args[7].value = arg8;
+ fcinfo->args[7].isnull = false;
+ fcinfo->args[8].value = arg9;
+ fcinfo->args[8].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+ return result;
+}
+
+
+/*
+ * These are for invocation of a function identified by OID with a
+ * directly-computed parameter list. Note that neither arguments nor result
+ * are allowed to be NULL. These are essentially fmgr_info() followed
+ * by FunctionCallN(). If the same function is to be invoked repeatedly,
+ * do the fmgr_info() once and then use FunctionCallN().
+ */
+Datum
+OidFunctionCall0Coll(Oid functionId, Oid collation)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall0Coll(&flinfo, collation);
+}
+
+Datum
+OidFunctionCall1Coll(Oid functionId, Oid collation, Datum arg1)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall1Coll(&flinfo, collation, arg1);
+}
+
+Datum
+OidFunctionCall2Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall2Coll(&flinfo, collation, arg1, arg2);
+}
+
+Datum
+OidFunctionCall3Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall3Coll(&flinfo, collation, arg1, arg2, arg3);
+}
+
+Datum
+OidFunctionCall4Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall4Coll(&flinfo, collation, arg1, arg2, arg3, arg4);
+}
+
+Datum
+OidFunctionCall5Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall5Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5);
+}
+
+Datum
+OidFunctionCall6Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall6Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5,
+ arg6);
+}
+
+Datum
+OidFunctionCall7Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6, Datum arg7)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall7Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5,
+ arg6, arg7);
+}
+
+Datum
+OidFunctionCall8Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6, Datum arg7, Datum arg8)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall8Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5,
+ arg6, arg7, arg8);
+}
+
+Datum
+OidFunctionCall9Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2,
+ Datum arg3, Datum arg4, Datum arg5,
+ Datum arg6, Datum arg7, Datum arg8,
+ Datum arg9)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+
+ return FunctionCall9Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5,
+ arg6, arg7, arg8, arg9);
+}
+
+
+/*
+ * Special cases for convenient invocation of datatype I/O functions.
+ */
+
+/*
+ * Call a previously-looked-up datatype input function.
+ *
+ * "str" may be NULL to indicate we are reading a NULL. In this case
+ * the caller should assume the result is NULL, but we'll call the input
+ * function anyway if it's not strict. So this is almost but not quite
+ * the same as FunctionCall3.
+ */
+Datum
+InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod)
+{
+ LOCAL_FCINFO(fcinfo, 3);
+ Datum result;
+
+ if (str == NULL && flinfo->fn_strict)
+ return (Datum) 0; /* just return null result */
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 3, InvalidOid, NULL, NULL);
+
+ fcinfo->args[0].value = CStringGetDatum(str);
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = ObjectIdGetDatum(typioparam);
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = Int32GetDatum(typmod);
+ fcinfo->args[2].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Should get null result if and only if str is NULL */
+ if (str == NULL)
+ {
+ if (!fcinfo->isnull)
+ elog(ERROR, "input function %u returned non-NULL",
+ flinfo->fn_oid);
+ }
+ else
+ {
+ if (fcinfo->isnull)
+ elog(ERROR, "input function %u returned NULL",
+ flinfo->fn_oid);
+ }
+
+ return result;
+}
+
+/*
+ * Call a previously-looked-up datatype output function.
+ *
+ * Do not call this on NULL datums.
+ *
+ * This is currently little more than window dressing for FunctionCall1.
+ */
+char *
+OutputFunctionCall(FmgrInfo *flinfo, Datum val)
+{
+ return DatumGetCString(FunctionCall1(flinfo, val));
+}
+
+/*
+ * Call a previously-looked-up datatype binary-input function.
+ *
+ * "buf" may be NULL to indicate we are reading a NULL. In this case
+ * the caller should assume the result is NULL, but we'll call the receive
+ * function anyway if it's not strict. So this is almost but not quite
+ * the same as FunctionCall3.
+ */
+Datum
+ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
+ Oid typioparam, int32 typmod)
+{
+ LOCAL_FCINFO(fcinfo, 3);
+ Datum result;
+
+ if (buf == NULL && flinfo->fn_strict)
+ return (Datum) 0; /* just return null result */
+
+ InitFunctionCallInfoData(*fcinfo, flinfo, 3, InvalidOid, NULL, NULL);
+
+ fcinfo->args[0].value = PointerGetDatum(buf);
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = ObjectIdGetDatum(typioparam);
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = Int32GetDatum(typmod);
+ fcinfo->args[2].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ /* Should get null result if and only if buf is NULL */
+ if (buf == NULL)
+ {
+ if (!fcinfo->isnull)
+ elog(ERROR, "receive function %u returned non-NULL",
+ flinfo->fn_oid);
+ }
+ else
+ {
+ if (fcinfo->isnull)
+ elog(ERROR, "receive function %u returned NULL",
+ flinfo->fn_oid);
+ }
+
+ return result;
+}
+
+/*
+ * Call a previously-looked-up datatype binary-output function.
+ *
+ * Do not call this on NULL datums.
+ *
+ * This is little more than window dressing for FunctionCall1, but it does
+ * guarantee a non-toasted result, which strictly speaking the underlying
+ * function doesn't.
+ */
+bytea *
+SendFunctionCall(FmgrInfo *flinfo, Datum val)
+{
+ return DatumGetByteaP(FunctionCall1(flinfo, val));
+}
+
+/*
+ * As above, for I/O functions identified by OID. These are only to be used
+ * in seldom-executed code paths. They are not only slow but leak memory.
+ */
+Datum
+OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+ return InputFunctionCall(&flinfo, str, typioparam, typmod);
+}
+
+char *
+OidOutputFunctionCall(Oid functionId, Datum val)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+ return OutputFunctionCall(&flinfo, val);
+}
+
+Datum
+OidReceiveFunctionCall(Oid functionId, StringInfo buf,
+ Oid typioparam, int32 typmod)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+ return ReceiveFunctionCall(&flinfo, buf, typioparam, typmod);
+}
+
+bytea *
+OidSendFunctionCall(Oid functionId, Datum val)
+{
+ FmgrInfo flinfo;
+
+ fmgr_info(functionId, &flinfo);
+ return SendFunctionCall(&flinfo, val);
+}
+
+
+/*-------------------------------------------------------------------------
+ * Support routines for standard maybe-pass-by-reference datatypes
+ *
+ * int8 and float8 can be passed by value if Datum is wide enough.
+ * (For backwards-compatibility reasons, we allow pass-by-ref to be chosen
+ * at compile time even if pass-by-val is possible.)
+ *
+ * Note: there is only one switch controlling the pass-by-value option for
+ * both int8 and float8; this is to avoid making things unduly complicated
+ * for the timestamp types, which might have either representation.
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
+
+Datum
+Int64GetDatum(int64 X)
+{
+ int64 *retval = (int64 *) palloc(sizeof(int64));
+
+ *retval = X;
+ return PointerGetDatum(retval);
+}
+
+Datum
+Float8GetDatum(float8 X)
+{
+ float8 *retval = (float8 *) palloc(sizeof(float8));
+
+ *retval = X;
+ return PointerGetDatum(retval);
+}
+#endif /* USE_FLOAT8_BYVAL */
+
+
+/*-------------------------------------------------------------------------
+ * Support routines for toastable datatypes
+ *-------------------------------------------------------------------------
+ */
+
+struct varlena *
+pg_detoast_datum(struct varlena *datum)
+{
+ if (VARATT_IS_EXTENDED(datum))
+ return detoast_attr(datum);
+ else
+ return datum;
+}
+
+struct varlena *
+pg_detoast_datum_copy(struct varlena *datum)
+{
+ if (VARATT_IS_EXTENDED(datum))
+ return detoast_attr(datum);
+ else
+ {
+ /* Make a modifiable copy of the varlena object */
+ Size len = VARSIZE(datum);
+ struct varlena *result = (struct varlena *) palloc(len);
+
+ memcpy(result, datum, len);
+ return result;
+ }
+}
+
+struct varlena *
+pg_detoast_datum_slice(struct varlena *datum, int32 first, int32 count)
+{
+ /* Only get the specified portion from the toast rel */
+ return detoast_attr_slice(datum, first, count);
+}
+
+struct varlena *
+pg_detoast_datum_packed(struct varlena *datum)
+{
+ if (VARATT_IS_COMPRESSED(datum) || VARATT_IS_EXTERNAL(datum))
+ return detoast_attr(datum);
+ else
+ return datum;
+}
+
+/*-------------------------------------------------------------------------
+ * Support routines for extracting info from fn_expr parse tree
+ *
+ * These are needed by polymorphic functions, which accept multiple possible
+ * input types and need help from the parser to know what they've got.
+ * Also, some functions might be interested in whether a parameter is constant.
+ * Functions taking VARIADIC ANY also need to know about the VARIADIC keyword.
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Get the actual type OID of the function return type
+ *
+ * Returns InvalidOid if information is not available
+ */
+Oid
+get_fn_expr_rettype(FmgrInfo *flinfo)
+{
+ Node *expr;
+
+ /*
+ * can't return anything useful if we have no FmgrInfo or if its fn_expr
+ * node has not been initialized
+ */
+ if (!flinfo || !flinfo->fn_expr)
+ return InvalidOid;
+
+ expr = flinfo->fn_expr;
+
+ return exprType(expr);
+}
+
+/*
+ * Get the actual type OID of a specific function argument (counting from 0)
+ *
+ * Returns InvalidOid if information is not available
+ */
+Oid
+get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
+{
+ /*
+ * can't return anything useful if we have no FmgrInfo or if its fn_expr
+ * node has not been initialized
+ */
+ if (!flinfo || !flinfo->fn_expr)
+ return InvalidOid;
+
+ return get_call_expr_argtype(flinfo->fn_expr, argnum);
+}
+
+/*
+ * Get the actual type OID of a specific function argument (counting from 0),
+ * but working from the calling expression tree instead of FmgrInfo
+ *
+ * Returns InvalidOid if information is not available
+ */
+Oid
+get_call_expr_argtype(Node *expr, int argnum)
+{
+ List *args;
+ Oid argtype;
+
+ if (expr == NULL)
+ return InvalidOid;
+
+ if (IsA(expr, FuncExpr))
+ args = ((FuncExpr *) expr)->args;
+ else if (IsA(expr, OpExpr))
+ args = ((OpExpr *) expr)->args;
+ else if (IsA(expr, DistinctExpr))
+ args = ((DistinctExpr *) expr)->args;
+ else if (IsA(expr, ScalarArrayOpExpr))
+ args = ((ScalarArrayOpExpr *) expr)->args;
+ else if (IsA(expr, NullIfExpr))
+ args = ((NullIfExpr *) expr)->args;
+ else if (IsA(expr, WindowFunc))
+ args = ((WindowFunc *) expr)->args;
+ else
+ return InvalidOid;
+
+ if (argnum < 0 || argnum >= list_length(args))
+ return InvalidOid;
+
+ argtype = exprType((Node *) list_nth(args, argnum));
+
+ /*
+ * special hack for ScalarArrayOpExpr: what the underlying function will
+ * actually get passed is the element type of the array.
+ */
+ if (IsA(expr, ScalarArrayOpExpr) &&
+ argnum == 1)
+ argtype = get_base_element_type(argtype);
+
+ return argtype;
+}
+
+/*
+ * Find out whether a specific function argument is constant for the
+ * duration of a query
+ *
+ * Returns false if information is not available
+ */
+bool
+get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum)
+{
+ /*
+ * can't return anything useful if we have no FmgrInfo or if its fn_expr
+ * node has not been initialized
+ */
+ if (!flinfo || !flinfo->fn_expr)
+ return false;
+
+ return get_call_expr_arg_stable(flinfo->fn_expr, argnum);
+}
+
+/*
+ * Find out whether a specific function argument is constant for the
+ * duration of a query, but working from the calling expression tree
+ *
+ * Returns false if information is not available
+ */
+bool
+get_call_expr_arg_stable(Node *expr, int argnum)
+{
+ List *args;
+ Node *arg;
+
+ if (expr == NULL)
+ return false;
+
+ if (IsA(expr, FuncExpr))
+ args = ((FuncExpr *) expr)->args;
+ else if (IsA(expr, OpExpr))
+ args = ((OpExpr *) expr)->args;
+ else if (IsA(expr, DistinctExpr))
+ args = ((DistinctExpr *) expr)->args;
+ else if (IsA(expr, ScalarArrayOpExpr))
+ args = ((ScalarArrayOpExpr *) expr)->args;
+ else if (IsA(expr, NullIfExpr))
+ args = ((NullIfExpr *) expr)->args;
+ else if (IsA(expr, WindowFunc))
+ args = ((WindowFunc *) expr)->args;
+ else
+ return false;
+
+ if (argnum < 0 || argnum >= list_length(args))
+ return false;
+
+ arg = (Node *) list_nth(args, argnum);
+
+ /*
+ * Either a true Const or an external Param will have a value that doesn't
+ * change during the execution of the query. In future we might want to
+ * consider other cases too, e.g. now().
+ */
+ if (IsA(arg, Const))
+ return true;
+ if (IsA(arg, Param) &&
+ ((Param *) arg)->paramkind == PARAM_EXTERN)
+ return true;
+
+ return false;
+}
+
+/*
+ * Get the VARIADIC flag from the function invocation
+ *
+ * Returns false (the default assumption) if information is not available
+ *
+ * Note this is generally only of interest to VARIADIC ANY functions
+ */
+bool
+get_fn_expr_variadic(FmgrInfo *flinfo)
+{
+ Node *expr;
+
+ /*
+ * can't return anything useful if we have no FmgrInfo or if its fn_expr
+ * node has not been initialized
+ */
+ if (!flinfo || !flinfo->fn_expr)
+ return false;
+
+ expr = flinfo->fn_expr;
+
+ if (IsA(expr, FuncExpr))
+ return ((FuncExpr *) expr)->funcvariadic;
+ else
+ return false;
+}
+
+/*
+ * Set options to FmgrInfo of opclass support function.
+ *
+ * Opclass support functions are called outside of expressions. Thanks to that
+ * we can use fn_expr to store opclass options as bytea constant.
+ */
+void
+set_fn_opclass_options(FmgrInfo *flinfo, bytea *options)
+{
+ flinfo->fn_expr = (Node *) makeConst(BYTEAOID, -1, InvalidOid, -1,
+ PointerGetDatum(options),
+ options == NULL, false);
+}
+
+/*
+ * Check if options are defined for opclass support function.
+ */
+bool
+has_fn_opclass_options(FmgrInfo *flinfo)
+{
+ if (flinfo && flinfo->fn_expr && IsA(flinfo->fn_expr, Const))
+ {
+ Const *expr = (Const *) flinfo->fn_expr;
+
+ if (expr->consttype == BYTEAOID)
+ return !expr->constisnull;
+ }
+ return false;
+}
+
+/*
+ * Get options for opclass support function.
+ */
+bytea *
+get_fn_opclass_options(FmgrInfo *flinfo)
+{
+ if (flinfo && flinfo->fn_expr && IsA(flinfo->fn_expr, Const))
+ {
+ Const *expr = (Const *) flinfo->fn_expr;
+
+ if (expr->consttype == BYTEAOID)
+ return expr->constisnull ? NULL : DatumGetByteaP(expr->constvalue);
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("operator class options info is absent in function call context")));
+
+ return NULL;
+}
+
+/*-------------------------------------------------------------------------
+ * Support routines for procedural language implementations
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Verify that a validator is actually associated with the language of a
+ * particular function and that the user has access to both the language and
+ * the function. All validators should call this before doing anything
+ * substantial. Doing so ensures a user cannot achieve anything with explicit
+ * calls to validators that he could not achieve with CREATE FUNCTION or by
+ * simply calling an existing function.
+ *
+ * When this function returns false, callers should skip all validation work
+ * and call PG_RETURN_VOID(). This never happens at present; it is reserved
+ * for future expansion.
+ *
+ * In particular, checking that the validator corresponds to the function's
+ * language allows untrusted language validators to assume they process only
+ * superuser-chosen source code. (Untrusted language call handlers, by
+ * definition, do assume that.) A user lacking the USAGE language privilege
+ * would be unable to reach the validator through CREATE FUNCTION, so we check
+ * that to block explicit calls as well. Checking the EXECUTE privilege on
+ * the function is often superfluous, because most users can clone the
+ * function to get an executable copy. It is meaningful against users with no
+ * database TEMP right and no permanent schema CREATE right, thereby unable to
+ * create any function. Also, if the function tracks persistent state by
+ * function OID or name, validating the original function might permit more
+ * mischief than creating and validating a clone thereof.
+ */
+bool
+CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid)
+{
+ HeapTuple procTup;
+ HeapTuple langTup;
+ Form_pg_proc procStruct;
+ Form_pg_language langStruct;
+ AclResult aclresult;
+
+ /*
+ * Get the function's pg_proc entry. Throw a user-facing error for bad
+ * OID, because validators can be called with user-specified OIDs.
+ */
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid));
+ if (!HeapTupleIsValid(procTup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("function with OID %u does not exist", functionOid)));
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /*
+ * Fetch pg_language entry to know if this is the correct validation
+ * function for that pg_proc entry.
+ */
+ langTup = SearchSysCache1(LANGOID, ObjectIdGetDatum(procStruct->prolang));
+ if (!HeapTupleIsValid(langTup))
+ elog(ERROR, "cache lookup failed for language %u", procStruct->prolang);
+ langStruct = (Form_pg_language) GETSTRUCT(langTup);
+
+ if (langStruct->lanvalidator != validatorOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("language validation function %u called for language %u instead of %u",
+ validatorOid, procStruct->prolang,
+ langStruct->lanvalidator)));
+
+ /* first validate that we have permissions to use the language */
+ aclresult = pg_language_aclcheck(procStruct->prolang, GetUserId(),
+ ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_LANGUAGE,
+ NameStr(langStruct->lanname));
+
+ /*
+ * Check whether we are allowed to execute the function itself. If we can
+ * execute it, there should be no possible side-effect of
+ * compiling/validation that execution can't have.
+ */
+ aclresult = pg_proc_aclcheck(functionOid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_FUNCTION, NameStr(procStruct->proname));
+
+ ReleaseSysCache(procTup);
+ ReleaseSysCache(langTup);
+
+ return true;
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
new file mode 100644
index 0000000..e94b803
--- /dev/null
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -0,0 +1,2003 @@
+/*-------------------------------------------------------------------------
+ *
+ * funcapi.c
+ * Utility and convenience functions for fmgr functions that return
+ * sets and/or composite types, or deal with VARIADIC inputs.
+ *
+ * Copyright (c) 2002-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/fmgr/funcapi.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/relation.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "nodes/nodeFuncs.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/regproc.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+
+typedef struct polymorphic_actuals
+{
+ Oid anyelement_type; /* anyelement mapping, if known */
+ Oid anyarray_type; /* anyarray mapping, if known */
+ Oid anyrange_type; /* anyrange mapping, if known */
+ Oid anymultirange_type; /* anymultirange mapping, if known */
+} polymorphic_actuals;
+
+static void shutdown_MultiFuncCall(Datum arg);
+static TypeFuncClass internal_get_result_type(Oid funcid,
+ Node *call_expr,
+ ReturnSetInfo *rsinfo,
+ Oid *resultTypeId,
+ TupleDesc *resultTupleDesc);
+static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
+static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
+static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
+static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
+ oidvector *declared_args,
+ Node *call_expr);
+static TypeFuncClass get_type_func_class(Oid typid, Oid *base_typeid);
+
+
+/*
+ * 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))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (fcinfo->flinfo->fn_extra == NULL)
+ {
+ /*
+ * First call
+ */
+ ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ MemoryContext multi_call_ctx;
+
+ /*
+ * Create a suitably long-lived context to hold cross-call data
+ */
+ multi_call_ctx = AllocSetContextCreate(fcinfo->flinfo->fn_mcxt,
+ "SRF multi-call context",
+ ALLOCSET_SMALL_SIZES);
+
+ /*
+ * Allocate suitably long-lived space and zero it
+ */
+ retval = (FuncCallContext *)
+ MemoryContextAllocZero(multi_call_ctx,
+ sizeof(FuncCallContext));
+
+ /*
+ * initialize the elements
+ */
+ retval->call_cntr = 0;
+ retval->max_calls = 0;
+ retval->user_fctx = NULL;
+ retval->attinmeta = NULL;
+ retval->tuple_desc = NULL;
+ retval->multi_call_memory_ctx = multi_call_ctx;
+
+ /*
+ * save the pointer for cross-call use
+ */
+ fcinfo->flinfo->fn_extra = retval;
+
+ /*
+ * Ensure we will get shut down cleanly if the exprcontext is not run
+ * to completion.
+ */
+ RegisterExprContextCallback(rsi->econtext,
+ shutdown_MultiFuncCall,
+ PointerGetDatum(fcinfo->flinfo));
+ }
+ else
+ {
+ /* second and subsequent calls */
+ elog(ERROR, "init_MultiFuncCall cannot be called more than once");
+
+ /* never reached, but keep compiler happy */
+ retval = NULL;
+ }
+
+ return retval;
+}
+
+/*
+ * per_MultiFuncCall
+ *
+ * Do Multi-function per-call setup
+ */
+FuncCallContext *
+per_MultiFuncCall(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *retval = (FuncCallContext *) fcinfo->flinfo->fn_extra;
+
+ return retval;
+}
+
+/*
+ * end_MultiFuncCall
+ * Clean up after init_MultiFuncCall
+ */
+void
+end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx)
+{
+ ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* Deregister the shutdown callback */
+ UnregisterExprContextCallback(rsi->econtext,
+ shutdown_MultiFuncCall,
+ PointerGetDatum(fcinfo->flinfo));
+
+ /* But use it to do the real work */
+ shutdown_MultiFuncCall(PointerGetDatum(fcinfo->flinfo));
+}
+
+/*
+ * shutdown_MultiFuncCall
+ * Shutdown function to clean up after init_MultiFuncCall
+ */
+static void
+shutdown_MultiFuncCall(Datum arg)
+{
+ FmgrInfo *flinfo = (FmgrInfo *) DatumGetPointer(arg);
+ FuncCallContext *funcctx = (FuncCallContext *) flinfo->fn_extra;
+
+ /* unbind from flinfo */
+ flinfo->fn_extra = NULL;
+
+ /*
+ * Delete context that holds all multi-call data, including the
+ * FuncCallContext itself
+ */
+ MemoryContextDelete(funcctx->multi_call_memory_ctx);
+}
+
+
+/*
+ * get_call_result_type
+ * Given a function's call info record, determine the kind of datatype
+ * it is supposed to return. If resultTypeId isn't NULL, *resultTypeId
+ * receives the actual datatype OID (this is mainly useful for scalar
+ * result types). If resultTupleDesc isn't NULL, *resultTupleDesc
+ * receives a pointer to a TupleDesc when the result is of a composite
+ * type, or NULL when it's a scalar result.
+ *
+ * One hard case that this handles is resolution of actual rowtypes for
+ * functions returning RECORD (from either the function's OUT parameter
+ * list, or a ReturnSetInfo context node). TYPEFUNC_RECORD is returned
+ * only when we couldn't resolve the actual rowtype for lack of information.
+ *
+ * The other hard case that this handles is resolution of polymorphism.
+ * We will never return polymorphic pseudotypes (ANYELEMENT etc), either
+ * as a scalar result type or as a component of a rowtype.
+ *
+ * This function is relatively expensive --- in a function returning set,
+ * try to call it only the first time through.
+ */
+TypeFuncClass
+get_call_result_type(FunctionCallInfo fcinfo,
+ Oid *resultTypeId,
+ TupleDesc *resultTupleDesc)
+{
+ return internal_get_result_type(fcinfo->flinfo->fn_oid,
+ fcinfo->flinfo->fn_expr,
+ (ReturnSetInfo *) fcinfo->resultinfo,
+ resultTypeId,
+ resultTupleDesc);
+}
+
+/*
+ * get_expr_result_type
+ * As above, but work from a calling expression node tree
+ */
+TypeFuncClass
+get_expr_result_type(Node *expr,
+ Oid *resultTypeId,
+ TupleDesc *resultTupleDesc)
+{
+ TypeFuncClass result;
+
+ if (expr && IsA(expr, FuncExpr))
+ result = internal_get_result_type(((FuncExpr *) expr)->funcid,
+ expr,
+ NULL,
+ resultTypeId,
+ resultTupleDesc);
+ else if (expr && IsA(expr, OpExpr))
+ result = internal_get_result_type(get_opcode(((OpExpr *) expr)->opno),
+ expr,
+ NULL,
+ resultTypeId,
+ resultTupleDesc);
+ else if (expr && IsA(expr, RowExpr) &&
+ ((RowExpr *) expr)->row_typeid == RECORDOID)
+ {
+ /* We can resolve the record type by generating the tupdesc directly */
+ RowExpr *rexpr = (RowExpr *) expr;
+ TupleDesc tupdesc;
+ AttrNumber i = 1;
+ ListCell *lcc,
+ *lcn;
+
+ tupdesc = CreateTemplateTupleDesc(list_length(rexpr->args));
+ Assert(list_length(rexpr->args) == list_length(rexpr->colnames));
+ forboth(lcc, rexpr->args, lcn, rexpr->colnames)
+ {
+ Node *col = (Node *) lfirst(lcc);
+ char *colname = strVal(lfirst(lcn));
+
+ TupleDescInitEntry(tupdesc, i,
+ colname,
+ exprType(col),
+ exprTypmod(col),
+ 0);
+ TupleDescInitEntryCollation(tupdesc, i,
+ exprCollation(col));
+ i++;
+ }
+ if (resultTypeId)
+ *resultTypeId = rexpr->row_typeid;
+ if (resultTupleDesc)
+ *resultTupleDesc = BlessTupleDesc(tupdesc);
+ return TYPEFUNC_COMPOSITE;
+ }
+ else
+ {
+ /* handle as a generic expression; no chance to resolve RECORD */
+ Oid typid = exprType(expr);
+ Oid base_typid;
+
+ if (resultTypeId)
+ *resultTypeId = typid;
+ if (resultTupleDesc)
+ *resultTupleDesc = NULL;
+ result = get_type_func_class(typid, &base_typid);
+ if ((result == TYPEFUNC_COMPOSITE ||
+ result == TYPEFUNC_COMPOSITE_DOMAIN) &&
+ resultTupleDesc)
+ *resultTupleDesc = lookup_rowtype_tupdesc_copy(base_typid, -1);
+ }
+
+ return result;
+}
+
+/*
+ * get_func_result_type
+ * As above, but work from a function's OID only
+ *
+ * This will not be able to resolve pure-RECORD results nor polymorphism.
+ */
+TypeFuncClass
+get_func_result_type(Oid functionId,
+ Oid *resultTypeId,
+ TupleDesc *resultTupleDesc)
+{
+ return internal_get_result_type(functionId,
+ NULL,
+ NULL,
+ resultTypeId,
+ resultTupleDesc);
+}
+
+/*
+ * internal_get_result_type -- workhorse code implementing all the above
+ *
+ * funcid must always be supplied. call_expr and rsinfo can be NULL if not
+ * available. We will return TYPEFUNC_RECORD, and store NULL into
+ * *resultTupleDesc, if we cannot deduce the complete result rowtype from
+ * the available information.
+ */
+static TypeFuncClass
+internal_get_result_type(Oid funcid,
+ Node *call_expr,
+ ReturnSetInfo *rsinfo,
+ Oid *resultTypeId,
+ TupleDesc *resultTupleDesc)
+{
+ TypeFuncClass result;
+ HeapTuple tp;
+ Form_pg_proc procform;
+ Oid rettype;
+ Oid base_rettype;
+ TupleDesc tupdesc;
+
+ /* First fetch the function's pg_proc row to inspect its rettype */
+ tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+ procform = (Form_pg_proc) GETSTRUCT(tp);
+
+ rettype = procform->prorettype;
+
+ /* Check for OUT parameters defining a RECORD result */
+ tupdesc = build_function_result_tupdesc_t(tp);
+ if (tupdesc)
+ {
+ /*
+ * It has OUT parameters, so it's basically like a regular composite
+ * type, except we have to be able to resolve any polymorphic OUT
+ * parameters.
+ */
+ if (resultTypeId)
+ *resultTypeId = rettype;
+
+ if (resolve_polymorphic_tupdesc(tupdesc,
+ &procform->proargtypes,
+ call_expr))
+ {
+ if (tupdesc->tdtypeid == RECORDOID &&
+ tupdesc->tdtypmod < 0)
+ assign_record_type_typmod(tupdesc);
+ if (resultTupleDesc)
+ *resultTupleDesc = tupdesc;
+ result = TYPEFUNC_COMPOSITE;
+ }
+ else
+ {
+ if (resultTupleDesc)
+ *resultTupleDesc = NULL;
+ result = TYPEFUNC_RECORD;
+ }
+
+ ReleaseSysCache(tp);
+
+ return result;
+ }
+
+ /*
+ * If scalar polymorphic result, try to resolve it.
+ */
+ if (IsPolymorphicType(rettype))
+ {
+ Oid newrettype = exprType(call_expr);
+
+ if (newrettype == InvalidOid) /* this probably should not happen */
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not determine actual result type for function \"%s\" declared to return type %s",
+ NameStr(procform->proname),
+ format_type_be(rettype))));
+ rettype = newrettype;
+ }
+
+ if (resultTypeId)
+ *resultTypeId = rettype;
+ if (resultTupleDesc)
+ *resultTupleDesc = NULL; /* default result */
+
+ /* Classify the result type */
+ result = get_type_func_class(rettype, &base_rettype);
+ switch (result)
+ {
+ case TYPEFUNC_COMPOSITE:
+ case TYPEFUNC_COMPOSITE_DOMAIN:
+ if (resultTupleDesc)
+ *resultTupleDesc = lookup_rowtype_tupdesc_copy(base_rettype, -1);
+ /* Named composite types can't have any polymorphic columns */
+ break;
+ case TYPEFUNC_SCALAR:
+ break;
+ case TYPEFUNC_RECORD:
+ /* We must get the tupledesc from call context */
+ if (rsinfo && IsA(rsinfo, ReturnSetInfo) &&
+ rsinfo->expectedDesc != NULL)
+ {
+ result = TYPEFUNC_COMPOSITE;
+ if (resultTupleDesc)
+ *resultTupleDesc = rsinfo->expectedDesc;
+ /* Assume no polymorphic columns here, either */
+ }
+ break;
+ default:
+ break;
+ }
+
+ ReleaseSysCache(tp);
+
+ return result;
+}
+
+/*
+ * get_expr_result_tupdesc
+ * Get a tupdesc describing the result of a composite-valued expression
+ *
+ * If expression is not composite or rowtype can't be determined, returns NULL
+ * if noError is true, else throws error.
+ *
+ * This is a simpler version of get_expr_result_type() for use when the caller
+ * is only interested in determinate rowtype results.
+ */
+TupleDesc
+get_expr_result_tupdesc(Node *expr, bool noError)
+{
+ TupleDesc tupleDesc;
+ TypeFuncClass functypclass;
+
+ functypclass = get_expr_result_type(expr, NULL, &tupleDesc);
+
+ if (functypclass == TYPEFUNC_COMPOSITE ||
+ functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
+ return tupleDesc;
+
+ if (!noError)
+ {
+ Oid exprTypeId = exprType(expr);
+
+ if (exprTypeId != RECORDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type %s is not composite",
+ format_type_be(exprTypeId))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("record type has not been registered")));
+ }
+
+ return NULL;
+}
+
+/*
+ * Resolve actual type of ANYELEMENT from other polymorphic inputs
+ *
+ * Note: the error cases here and in the sibling functions below are not
+ * really user-facing; they could only occur if the function signature is
+ * incorrect or the parser failed to enforce consistency of the actual
+ * argument types. Hence, we don't sweat too much over the error messages.
+ */
+static void
+resolve_anyelement_from_others(polymorphic_actuals *actuals)
+{
+ if (OidIsValid(actuals->anyarray_type))
+ {
+ /* Use the element type corresponding to actual type */
+ Oid array_base_type = getBaseType(actuals->anyarray_type);
+ Oid array_typelem = get_element_type(array_base_type);
+
+ if (!OidIsValid(array_typelem))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("argument declared %s is not an array but type %s",
+ "anyarray",
+ format_type_be(array_base_type))));
+ actuals->anyelement_type = array_typelem;
+ }
+ else if (OidIsValid(actuals->anyrange_type))
+ {
+ /* Use the element type corresponding to actual type */
+ Oid range_base_type = getBaseType(actuals->anyrange_type);
+ Oid range_typelem = get_range_subtype(range_base_type);
+
+ if (!OidIsValid(range_typelem))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("argument declared %s is not a range type but type %s",
+ "anyrange",
+ format_type_be(range_base_type))));
+ actuals->anyelement_type = range_typelem;
+ }
+ else if (OidIsValid(actuals->anymultirange_type))
+ {
+ /* Use the element type based on the multirange type */
+ Oid multirange_base_type;
+ Oid multirange_typelem;
+ Oid range_base_type;
+ Oid range_typelem;
+
+ multirange_base_type = getBaseType(actuals->anymultirange_type);
+ multirange_typelem = get_multirange_range(multirange_base_type);
+ if (!OidIsValid(multirange_typelem))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("argument declared %s is not a multirange type but type %s",
+ "anymultirange",
+ format_type_be(multirange_base_type))));
+
+ range_base_type = getBaseType(multirange_typelem);
+ range_typelem = get_range_subtype(range_base_type);
+
+ if (!OidIsValid(range_typelem))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("argument declared %s does not contain a range type but type %s",
+ "anymultirange",
+ format_type_be(range_base_type))));
+ actuals->anyelement_type = range_typelem;
+ }
+ else
+ elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYARRAY from other polymorphic inputs
+ */
+static void
+resolve_anyarray_from_others(polymorphic_actuals *actuals)
+{
+ /* If we don't know ANYELEMENT, resolve that first */
+ if (!OidIsValid(actuals->anyelement_type))
+ resolve_anyelement_from_others(actuals);
+
+ if (OidIsValid(actuals->anyelement_type))
+ {
+ /* Use the array type corresponding to actual type */
+ Oid array_typeid = get_array_type(actuals->anyelement_type);
+
+ if (!OidIsValid(array_typeid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("could not find array type for data type %s",
+ format_type_be(actuals->anyelement_type))));
+ actuals->anyarray_type = array_typeid;
+ }
+ else
+ elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYRANGE from other polymorphic inputs
+ */
+static void
+resolve_anyrange_from_others(polymorphic_actuals *actuals)
+{
+ /*
+ * We can't deduce a range type from other polymorphic array or base
+ * types, because there may be multiple range types with the same subtype,
+ * but we can deduce it from a polymorphic multirange type.
+ */
+ if (OidIsValid(actuals->anymultirange_type))
+ {
+ /* Use the element type based on the multirange type */
+ Oid multirange_base_type = getBaseType(actuals->anymultirange_type);
+ Oid multirange_typelem = get_multirange_range(multirange_base_type);
+
+ if (!OidIsValid(multirange_typelem))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("argument declared %s is not a multirange type but type %s",
+ "anymultirange",
+ format_type_be(multirange_base_type))));
+ actuals->anyrange_type = multirange_typelem;
+ }
+ else
+ elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+ /*
+ * We can't deduce a multirange type from polymorphic array or base types,
+ * because there may be multiple range types with the same subtype, but we
+ * can deduce it from a polymorphic range type.
+ */
+ if (OidIsValid(actuals->anyrange_type))
+ {
+ Oid range_base_type = getBaseType(actuals->anyrange_type);
+ Oid multirange_typeid = get_range_multirange(range_base_type);
+
+ if (!OidIsValid(multirange_typeid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("could not find multirange type for data type %s",
+ format_type_be(actuals->anyrange_type))));
+ actuals->anymultirange_type = multirange_typeid;
+ }
+ else
+ elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Given the result tuple descriptor for a function with OUT parameters,
+ * replace any polymorphic column types (ANYELEMENT etc) in the tupdesc
+ * with concrete data types deduced from the input arguments.
+ * declared_args is an oidvector of the function's declared input arg types
+ * (showing which are polymorphic), and call_expr is the call expression.
+ *
+ * Returns true if able to deduce all types, false if necessary information
+ * is not provided (call_expr is NULL or arg types aren't identifiable).
+ */
+static bool
+resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
+ Node *call_expr)
+{
+ int natts = tupdesc->natts;
+ int nargs = declared_args->dim1;
+ bool have_polymorphic_result = false;
+ bool have_anyelement_result = false;
+ bool have_anyarray_result = false;
+ bool have_anyrange_result = false;
+ bool have_anymultirange_result = false;
+ bool have_anycompatible_result = false;
+ bool have_anycompatible_array_result = false;
+ bool have_anycompatible_range_result = false;
+ bool have_anycompatible_multirange_result = false;
+ polymorphic_actuals poly_actuals;
+ polymorphic_actuals anyc_actuals;
+ Oid anycollation = InvalidOid;
+ Oid anycompatcollation = InvalidOid;
+ int i;
+
+ /* See if there are any polymorphic outputs; quick out if not */
+ for (i = 0; i < natts; i++)
+ {
+ switch (TupleDescAttr(tupdesc, i)->atttypid)
+ {
+ case ANYELEMENTOID:
+ case ANYNONARRAYOID:
+ case ANYENUMOID:
+ have_polymorphic_result = true;
+ have_anyelement_result = true;
+ break;
+ case ANYARRAYOID:
+ have_polymorphic_result = true;
+ have_anyarray_result = true;
+ break;
+ case ANYRANGEOID:
+ have_polymorphic_result = true;
+ have_anyrange_result = true;
+ break;
+ case ANYMULTIRANGEOID:
+ have_polymorphic_result = true;
+ have_anymultirange_result = true;
+ break;
+ case ANYCOMPATIBLEOID:
+ case ANYCOMPATIBLENONARRAYOID:
+ have_polymorphic_result = true;
+ have_anycompatible_result = true;
+ break;
+ case ANYCOMPATIBLEARRAYOID:
+ have_polymorphic_result = true;
+ have_anycompatible_array_result = true;
+ break;
+ case ANYCOMPATIBLERANGEOID:
+ have_polymorphic_result = true;
+ have_anycompatible_range_result = true;
+ break;
+ case ANYCOMPATIBLEMULTIRANGEOID:
+ have_polymorphic_result = true;
+ have_anycompatible_multirange_result = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (!have_polymorphic_result)
+ return true;
+
+ /*
+ * Otherwise, extract actual datatype(s) from input arguments. (We assume
+ * the parser already validated consistency of the arguments. Also, for
+ * the ANYCOMPATIBLE pseudotype family, we expect that all matching
+ * arguments were coerced to the selected common supertype, so that it
+ * doesn't matter which one's exposed type we look at.)
+ */
+ if (!call_expr)
+ return false; /* no hope */
+
+ memset(&poly_actuals, 0, sizeof(poly_actuals));
+ memset(&anyc_actuals, 0, sizeof(anyc_actuals));
+
+ for (i = 0; i < nargs; i++)
+ {
+ switch (declared_args->values[i])
+ {
+ case ANYELEMENTOID:
+ case ANYNONARRAYOID:
+ case ANYENUMOID:
+ if (!OidIsValid(poly_actuals.anyelement_type))
+ {
+ poly_actuals.anyelement_type =
+ get_call_expr_argtype(call_expr, i);
+ if (!OidIsValid(poly_actuals.anyelement_type))
+ return false;
+ }
+ break;
+ case ANYARRAYOID:
+ if (!OidIsValid(poly_actuals.anyarray_type))
+ {
+ poly_actuals.anyarray_type =
+ get_call_expr_argtype(call_expr, i);
+ if (!OidIsValid(poly_actuals.anyarray_type))
+ return false;
+ }
+ break;
+ case ANYRANGEOID:
+ if (!OidIsValid(poly_actuals.anyrange_type))
+ {
+ poly_actuals.anyrange_type =
+ get_call_expr_argtype(call_expr, i);
+ if (!OidIsValid(poly_actuals.anyrange_type))
+ return false;
+ }
+ break;
+ case ANYMULTIRANGEOID:
+ if (!OidIsValid(poly_actuals.anymultirange_type))
+ {
+ poly_actuals.anymultirange_type =
+ get_call_expr_argtype(call_expr, i);
+ if (!OidIsValid(poly_actuals.anymultirange_type))
+ return false;
+ }
+ break;
+ case ANYCOMPATIBLEOID:
+ case ANYCOMPATIBLENONARRAYOID:
+ if (!OidIsValid(anyc_actuals.anyelement_type))
+ {
+ anyc_actuals.anyelement_type =
+ get_call_expr_argtype(call_expr, i);
+ if (!OidIsValid(anyc_actuals.anyelement_type))
+ return false;
+ }
+ break;
+ case ANYCOMPATIBLEARRAYOID:
+ if (!OidIsValid(anyc_actuals.anyarray_type))
+ {
+ anyc_actuals.anyarray_type =
+ get_call_expr_argtype(call_expr, i);
+ if (!OidIsValid(anyc_actuals.anyarray_type))
+ return false;
+ }
+ break;
+ case ANYCOMPATIBLERANGEOID:
+ if (!OidIsValid(anyc_actuals.anyrange_type))
+ {
+ anyc_actuals.anyrange_type =
+ get_call_expr_argtype(call_expr, i);
+ if (!OidIsValid(anyc_actuals.anyrange_type))
+ return false;
+ }
+ break;
+ case ANYCOMPATIBLEMULTIRANGEOID:
+ if (!OidIsValid(anyc_actuals.anymultirange_type))
+ {
+ anyc_actuals.anymultirange_type =
+ get_call_expr_argtype(call_expr, i);
+ if (!OidIsValid(anyc_actuals.anymultirange_type))
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* If needed, deduce one polymorphic type from others */
+ if (have_anyelement_result && !OidIsValid(poly_actuals.anyelement_type))
+ resolve_anyelement_from_others(&poly_actuals);
+
+ if (have_anyarray_result && !OidIsValid(poly_actuals.anyarray_type))
+ resolve_anyarray_from_others(&poly_actuals);
+
+ if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
+ resolve_anyrange_from_others(&poly_actuals);
+
+ if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+ resolve_anymultirange_from_others(&poly_actuals);
+
+ if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
+ resolve_anyelement_from_others(&anyc_actuals);
+
+ if (have_anycompatible_array_result && !OidIsValid(anyc_actuals.anyarray_type))
+ resolve_anyarray_from_others(&anyc_actuals);
+
+ if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
+ resolve_anyrange_from_others(&anyc_actuals);
+
+ if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+ resolve_anymultirange_from_others(&anyc_actuals);
+
+ /*
+ * Identify the collation to use for polymorphic OUT parameters. (It'll
+ * necessarily be the same for both anyelement and anyarray, likewise for
+ * anycompatible and anycompatiblearray.) Note that range types are not
+ * collatable, so any possible internal collation of a range type is not
+ * considered here.
+ */
+ if (OidIsValid(poly_actuals.anyelement_type))
+ anycollation = get_typcollation(poly_actuals.anyelement_type);
+ else if (OidIsValid(poly_actuals.anyarray_type))
+ anycollation = get_typcollation(poly_actuals.anyarray_type);
+
+ if (OidIsValid(anyc_actuals.anyelement_type))
+ anycompatcollation = get_typcollation(anyc_actuals.anyelement_type);
+ else if (OidIsValid(anyc_actuals.anyarray_type))
+ anycompatcollation = get_typcollation(anyc_actuals.anyarray_type);
+
+ if (OidIsValid(anycollation) || OidIsValid(anycompatcollation))
+ {
+ /*
+ * The types are collatable, so consider whether to use a nondefault
+ * collation. We do so if we can identify the input collation used
+ * for the function.
+ */
+ Oid inputcollation = exprInputCollation(call_expr);
+
+ if (OidIsValid(inputcollation))
+ {
+ if (OidIsValid(anycollation))
+ anycollation = inputcollation;
+ if (OidIsValid(anycompatcollation))
+ anycompatcollation = inputcollation;
+ }
+ }
+
+ /* And finally replace the tuple column types as needed */
+ for (i = 0; i < natts; i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+ switch (att->atttypid)
+ {
+ case ANYELEMENTOID:
+ case ANYNONARRAYOID:
+ case ANYENUMOID:
+ TupleDescInitEntry(tupdesc, i + 1,
+ NameStr(att->attname),
+ poly_actuals.anyelement_type,
+ -1,
+ 0);
+ TupleDescInitEntryCollation(tupdesc, i + 1, anycollation);
+ break;
+ case ANYARRAYOID:
+ TupleDescInitEntry(tupdesc, i + 1,
+ NameStr(att->attname),
+ poly_actuals.anyarray_type,
+ -1,
+ 0);
+ TupleDescInitEntryCollation(tupdesc, i + 1, anycollation);
+ break;
+ case ANYRANGEOID:
+ TupleDescInitEntry(tupdesc, i + 1,
+ NameStr(att->attname),
+ poly_actuals.anyrange_type,
+ -1,
+ 0);
+ /* no collation should be attached to a range type */
+ break;
+ case ANYMULTIRANGEOID:
+ TupleDescInitEntry(tupdesc, i + 1,
+ NameStr(att->attname),
+ poly_actuals.anymultirange_type,
+ -1,
+ 0);
+ /* no collation should be attached to a multirange type */
+ break;
+ case ANYCOMPATIBLEOID:
+ case ANYCOMPATIBLENONARRAYOID:
+ TupleDescInitEntry(tupdesc, i + 1,
+ NameStr(att->attname),
+ anyc_actuals.anyelement_type,
+ -1,
+ 0);
+ TupleDescInitEntryCollation(tupdesc, i + 1, anycompatcollation);
+ break;
+ case ANYCOMPATIBLEARRAYOID:
+ TupleDescInitEntry(tupdesc, i + 1,
+ NameStr(att->attname),
+ anyc_actuals.anyarray_type,
+ -1,
+ 0);
+ TupleDescInitEntryCollation(tupdesc, i + 1, anycompatcollation);
+ break;
+ case ANYCOMPATIBLERANGEOID:
+ TupleDescInitEntry(tupdesc, i + 1,
+ NameStr(att->attname),
+ anyc_actuals.anyrange_type,
+ -1,
+ 0);
+ /* no collation should be attached to a range type */
+ break;
+ case ANYCOMPATIBLEMULTIRANGEOID:
+ TupleDescInitEntry(tupdesc, i + 1,
+ NameStr(att->attname),
+ anyc_actuals.anymultirange_type,
+ -1,
+ 0);
+ /* no collation should be attached to a multirange type */
+ break;
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Given the declared argument types and modes for a function, replace any
+ * polymorphic types (ANYELEMENT etc) in argtypes[] with concrete data types
+ * deduced from the input arguments found in call_expr.
+ *
+ * Returns true if able to deduce all types, false if necessary information
+ * is not provided (call_expr is NULL or arg types aren't identifiable).
+ *
+ * This is the same logic as resolve_polymorphic_tupdesc, but with a different
+ * argument representation, and slightly different output responsibilities.
+ *
+ * argmodes may be NULL, in which case all arguments are assumed to be IN mode.
+ */
+bool
+resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
+ Node *call_expr)
+{
+ bool have_polymorphic_result = false;
+ bool have_anyelement_result = false;
+ bool have_anyarray_result = false;
+ bool have_anyrange_result = false;
+ bool have_anymultirange_result = false;
+ bool have_anycompatible_result = false;
+ bool have_anycompatible_array_result = false;
+ bool have_anycompatible_range_result = false;
+ bool have_anycompatible_multirange_result = false;
+ polymorphic_actuals poly_actuals;
+ polymorphic_actuals anyc_actuals;
+ int inargno;
+ int i;
+
+ /*
+ * First pass: resolve polymorphic inputs, check for outputs. As in
+ * resolve_polymorphic_tupdesc, we rely on the parser to have enforced
+ * type consistency and coerced ANYCOMPATIBLE args to a common supertype.
+ */
+ memset(&poly_actuals, 0, sizeof(poly_actuals));
+ memset(&anyc_actuals, 0, sizeof(anyc_actuals));
+ inargno = 0;
+ for (i = 0; i < numargs; i++)
+ {
+ char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+
+ switch (argtypes[i])
+ {
+ case ANYELEMENTOID:
+ case ANYNONARRAYOID:
+ case ANYENUMOID:
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ {
+ have_polymorphic_result = true;
+ have_anyelement_result = true;
+ }
+ else
+ {
+ if (!OidIsValid(poly_actuals.anyelement_type))
+ {
+ poly_actuals.anyelement_type =
+ get_call_expr_argtype(call_expr, inargno);
+ if (!OidIsValid(poly_actuals.anyelement_type))
+ return false;
+ }
+ argtypes[i] = poly_actuals.anyelement_type;
+ }
+ break;
+ case ANYARRAYOID:
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ {
+ have_polymorphic_result = true;
+ have_anyarray_result = true;
+ }
+ else
+ {
+ if (!OidIsValid(poly_actuals.anyarray_type))
+ {
+ poly_actuals.anyarray_type =
+ get_call_expr_argtype(call_expr, inargno);
+ if (!OidIsValid(poly_actuals.anyarray_type))
+ return false;
+ }
+ argtypes[i] = poly_actuals.anyarray_type;
+ }
+ break;
+ case ANYRANGEOID:
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ {
+ have_polymorphic_result = true;
+ have_anyrange_result = true;
+ }
+ else
+ {
+ if (!OidIsValid(poly_actuals.anyrange_type))
+ {
+ poly_actuals.anyrange_type =
+ get_call_expr_argtype(call_expr, inargno);
+ if (!OidIsValid(poly_actuals.anyrange_type))
+ return false;
+ }
+ argtypes[i] = poly_actuals.anyrange_type;
+ }
+ break;
+ case ANYMULTIRANGEOID:
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ {
+ have_polymorphic_result = true;
+ have_anymultirange_result = true;
+ }
+ else
+ {
+ if (!OidIsValid(poly_actuals.anymultirange_type))
+ {
+ poly_actuals.anymultirange_type =
+ get_call_expr_argtype(call_expr, inargno);
+ if (!OidIsValid(poly_actuals.anymultirange_type))
+ return false;
+ }
+ argtypes[i] = poly_actuals.anymultirange_type;
+ }
+ break;
+ case ANYCOMPATIBLEOID:
+ case ANYCOMPATIBLENONARRAYOID:
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ {
+ have_polymorphic_result = true;
+ have_anycompatible_result = true;
+ }
+ else
+ {
+ if (!OidIsValid(anyc_actuals.anyelement_type))
+ {
+ anyc_actuals.anyelement_type =
+ get_call_expr_argtype(call_expr, inargno);
+ if (!OidIsValid(anyc_actuals.anyelement_type))
+ return false;
+ }
+ argtypes[i] = anyc_actuals.anyelement_type;
+ }
+ break;
+ case ANYCOMPATIBLEARRAYOID:
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ {
+ have_polymorphic_result = true;
+ have_anycompatible_array_result = true;
+ }
+ else
+ {
+ if (!OidIsValid(anyc_actuals.anyarray_type))
+ {
+ anyc_actuals.anyarray_type =
+ get_call_expr_argtype(call_expr, inargno);
+ if (!OidIsValid(anyc_actuals.anyarray_type))
+ return false;
+ }
+ argtypes[i] = anyc_actuals.anyarray_type;
+ }
+ break;
+ case ANYCOMPATIBLERANGEOID:
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ {
+ have_polymorphic_result = true;
+ have_anycompatible_range_result = true;
+ }
+ else
+ {
+ if (!OidIsValid(anyc_actuals.anyrange_type))
+ {
+ anyc_actuals.anyrange_type =
+ get_call_expr_argtype(call_expr, inargno);
+ if (!OidIsValid(anyc_actuals.anyrange_type))
+ return false;
+ }
+ argtypes[i] = anyc_actuals.anyrange_type;
+ }
+ break;
+ case ANYCOMPATIBLEMULTIRANGEOID:
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ {
+ have_polymorphic_result = true;
+ have_anycompatible_multirange_result = true;
+ }
+ else
+ {
+ if (!OidIsValid(anyc_actuals.anymultirange_type))
+ {
+ anyc_actuals.anymultirange_type =
+ get_call_expr_argtype(call_expr, inargno);
+ if (!OidIsValid(anyc_actuals.anymultirange_type))
+ return false;
+ }
+ argtypes[i] = anyc_actuals.anymultirange_type;
+ }
+ break;
+ default:
+ break;
+ }
+ if (argmode != PROARGMODE_OUT && argmode != PROARGMODE_TABLE)
+ inargno++;
+ }
+
+ /* Done? */
+ if (!have_polymorphic_result)
+ return true;
+
+ /* If needed, deduce one polymorphic type from others */
+ if (have_anyelement_result && !OidIsValid(poly_actuals.anyelement_type))
+ resolve_anyelement_from_others(&poly_actuals);
+
+ if (have_anyarray_result && !OidIsValid(poly_actuals.anyarray_type))
+ resolve_anyarray_from_others(&poly_actuals);
+
+ if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
+ resolve_anyrange_from_others(&poly_actuals);
+
+ if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+ resolve_anymultirange_from_others(&poly_actuals);
+
+ if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
+ resolve_anyelement_from_others(&anyc_actuals);
+
+ if (have_anycompatible_array_result && !OidIsValid(anyc_actuals.anyarray_type))
+ resolve_anyarray_from_others(&anyc_actuals);
+
+ if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
+ resolve_anyrange_from_others(&anyc_actuals);
+
+ if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+ resolve_anymultirange_from_others(&anyc_actuals);
+
+ /* And finally replace the output column types as needed */
+ for (i = 0; i < numargs; i++)
+ {
+ switch (argtypes[i])
+ {
+ case ANYELEMENTOID:
+ case ANYNONARRAYOID:
+ case ANYENUMOID:
+ argtypes[i] = poly_actuals.anyelement_type;
+ break;
+ case ANYARRAYOID:
+ argtypes[i] = poly_actuals.anyarray_type;
+ break;
+ case ANYRANGEOID:
+ argtypes[i] = poly_actuals.anyrange_type;
+ break;
+ case ANYMULTIRANGEOID:
+ argtypes[i] = poly_actuals.anymultirange_type;
+ break;
+ case ANYCOMPATIBLEOID:
+ case ANYCOMPATIBLENONARRAYOID:
+ argtypes[i] = anyc_actuals.anyelement_type;
+ break;
+ case ANYCOMPATIBLEARRAYOID:
+ argtypes[i] = anyc_actuals.anyarray_type;
+ break;
+ case ANYCOMPATIBLERANGEOID:
+ argtypes[i] = anyc_actuals.anyrange_type;
+ break;
+ case ANYCOMPATIBLEMULTIRANGEOID:
+ argtypes[i] = anyc_actuals.anymultirange_type;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * get_type_func_class
+ * Given the type OID, obtain its TYPEFUNC classification.
+ * Also, if it's a domain, return the base type OID.
+ *
+ * This is intended to centralize a bunch of formerly ad-hoc code for
+ * classifying types. The categories used here are useful for deciding
+ * how to handle functions returning the datatype.
+ */
+static TypeFuncClass
+get_type_func_class(Oid typid, Oid *base_typeid)
+{
+ *base_typeid = typid;
+
+ switch (get_typtype(typid))
+ {
+ case TYPTYPE_COMPOSITE:
+ return TYPEFUNC_COMPOSITE;
+ case TYPTYPE_BASE:
+ case TYPTYPE_ENUM:
+ case TYPTYPE_RANGE:
+ case TYPTYPE_MULTIRANGE:
+ return TYPEFUNC_SCALAR;
+ case TYPTYPE_DOMAIN:
+ *base_typeid = typid = getBaseType(typid);
+ if (get_typtype(typid) == TYPTYPE_COMPOSITE)
+ return TYPEFUNC_COMPOSITE_DOMAIN;
+ else /* domain base type can't be a pseudotype */
+ return TYPEFUNC_SCALAR;
+ case TYPTYPE_PSEUDO:
+ if (typid == RECORDOID)
+ return TYPEFUNC_RECORD;
+
+ /*
+ * We treat VOID and CSTRING as legitimate scalar datatypes,
+ * mostly for the convenience of the JDBC driver (which wants to
+ * be able to do "SELECT * FROM foo()" for all legitimately
+ * user-callable functions).
+ */
+ if (typid == VOIDOID || typid == CSTRINGOID)
+ return TYPEFUNC_SCALAR;
+ return TYPEFUNC_OTHER;
+ }
+ /* shouldn't get here, probably */
+ return TYPEFUNC_OTHER;
+}
+
+
+/*
+ * get_func_arg_info
+ *
+ * Fetch info about the argument types, names, and IN/OUT modes from the
+ * pg_proc tuple. Return value is the total number of arguments.
+ * Other results are palloc'd. *p_argtypes is always filled in, but
+ * *p_argnames and *p_argmodes will be set NULL in the default cases
+ * (no names, and all IN arguments, respectively).
+ *
+ * Note that this function simply fetches what is in the pg_proc tuple;
+ * it doesn't do any interpretation of polymorphic types.
+ */
+int
+get_func_arg_info(HeapTuple procTup,
+ Oid **p_argtypes, char ***p_argnames, char **p_argmodes)
+{
+ Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+ Datum proallargtypes;
+ Datum proargmodes;
+ Datum proargnames;
+ bool isNull;
+ ArrayType *arr;
+ int numargs;
+ Datum *elems;
+ int nelems;
+ int i;
+
+ /* First discover the total number of parameters and get their types */
+ proallargtypes = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_proallargtypes,
+ &isNull);
+ if (!isNull)
+ {
+ /*
+ * We expect the arrays to be 1-D arrays of the right types; verify
+ * that. For the OID and char arrays, we don't need to use
+ * deconstruct_array() since the array data is just going to look like
+ * a C array of values.
+ */
+ arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */
+ numargs = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numargs < 0 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != OIDOID)
+ elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls");
+ Assert(numargs >= procStruct->pronargs);
+ *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
+ memcpy(*p_argtypes, ARR_DATA_PTR(arr),
+ numargs * sizeof(Oid));
+ }
+ else
+ {
+ /* If no proallargtypes, use proargtypes */
+ numargs = procStruct->proargtypes.dim1;
+ Assert(numargs == procStruct->pronargs);
+ *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
+ memcpy(*p_argtypes, procStruct->proargtypes.values,
+ numargs * sizeof(Oid));
+ }
+
+ /* Get argument names, if available */
+ proargnames = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_proargnames,
+ &isNull);
+ if (isNull)
+ *p_argnames = NULL;
+ else
+ {
+ deconstruct_array(DatumGetArrayTypeP(proargnames),
+ TEXTOID, -1, false, TYPALIGN_INT,
+ &elems, NULL, &nelems);
+ if (nelems != numargs) /* should not happen */
+ elog(ERROR, "proargnames must have the same number of elements as the function has arguments");
+ *p_argnames = (char **) palloc(sizeof(char *) * numargs);
+ for (i = 0; i < numargs; i++)
+ (*p_argnames)[i] = TextDatumGetCString(elems[i]);
+ }
+
+ /* Get argument modes, if available */
+ proargmodes = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_proargmodes,
+ &isNull);
+ if (isNull)
+ *p_argmodes = NULL;
+ else
+ {
+ arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_DIMS(arr)[0] != numargs ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls",
+ numargs);
+ *p_argmodes = (char *) palloc(numargs * sizeof(char));
+ memcpy(*p_argmodes, ARR_DATA_PTR(arr),
+ numargs * sizeof(char));
+ }
+
+ return numargs;
+}
+
+/*
+ * get_func_trftypes
+ *
+ * Returns the number of transformed types used by the function.
+ * If there are any, a palloc'd array of the type OIDs is returned
+ * into *p_trftypes.
+ */
+int
+get_func_trftypes(HeapTuple procTup,
+ Oid **p_trftypes)
+{
+ Datum protrftypes;
+ ArrayType *arr;
+ int nelems;
+ bool isNull;
+
+ protrftypes = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_protrftypes,
+ &isNull);
+ if (!isNull)
+ {
+ /*
+ * We expect the arrays to be 1-D arrays of the right types; verify
+ * that. For the OID and char arrays, we don't need to use
+ * deconstruct_array() since the array data is just going to look like
+ * a C array of values.
+ */
+ arr = DatumGetArrayTypeP(protrftypes); /* ensure not toasted */
+ nelems = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ nelems < 0 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != OIDOID)
+ elog(ERROR, "protrftypes is not a 1-D Oid array or it contains nulls");
+ *p_trftypes = (Oid *) palloc(nelems * sizeof(Oid));
+ memcpy(*p_trftypes, ARR_DATA_PTR(arr),
+ nelems * sizeof(Oid));
+
+ return nelems;
+ }
+ else
+ return 0;
+}
+
+/*
+ * get_func_input_arg_names
+ *
+ * Extract the names of input arguments only, given a function's
+ * proargnames and proargmodes entries in Datum form.
+ *
+ * Returns the number of input arguments, which is the length of the
+ * palloc'd array returned to *arg_names. Entries for unnamed args
+ * are set to NULL. You don't get anything if proargnames is NULL.
+ */
+int
+get_func_input_arg_names(Datum proargnames, Datum proargmodes,
+ char ***arg_names)
+{
+ ArrayType *arr;
+ int numargs;
+ Datum *argnames;
+ char *argmodes;
+ char **inargnames;
+ int numinargs;
+ int i;
+
+ /* Do nothing if null proargnames */
+ if (proargnames == PointerGetDatum(NULL))
+ {
+ *arg_names = NULL;
+ return 0;
+ }
+
+ /*
+ * We expect the arrays to be 1-D arrays of the right types; verify that.
+ * For proargmodes, we don't need to use deconstruct_array() since the
+ * array data is just going to look like a C array of values.
+ */
+ arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != TEXTOID)
+ elog(ERROR, "proargnames is not a 1-D text array or it contains nulls");
+ deconstruct_array(arr, TEXTOID, -1, false, TYPALIGN_INT,
+ &argnames, NULL, &numargs);
+ if (proargmodes != PointerGetDatum(NULL))
+ {
+ arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_DIMS(arr)[0] != numargs ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls",
+ numargs);
+ argmodes = (char *) ARR_DATA_PTR(arr);
+ }
+ else
+ argmodes = NULL;
+
+ /* zero elements probably shouldn't happen, but handle it gracefully */
+ if (numargs <= 0)
+ {
+ *arg_names = NULL;
+ return 0;
+ }
+
+ /* extract input-argument names */
+ inargnames = (char **) palloc(numargs * sizeof(char *));
+ numinargs = 0;
+ for (i = 0; i < numargs; i++)
+ {
+ if (argmodes == NULL ||
+ argmodes[i] == PROARGMODE_IN ||
+ argmodes[i] == PROARGMODE_INOUT ||
+ argmodes[i] == PROARGMODE_VARIADIC)
+ {
+ char *pname = TextDatumGetCString(argnames[i]);
+
+ if (pname[0] != '\0')
+ inargnames[numinargs] = pname;
+ else
+ inargnames[numinargs] = NULL;
+ numinargs++;
+ }
+ }
+
+ *arg_names = inargnames;
+ return numinargs;
+}
+
+
+/*
+ * get_func_result_name
+ *
+ * If the function has exactly one output parameter, and that parameter
+ * is named, return the name (as a palloc'd string). Else return NULL.
+ *
+ * This is used to determine the default output column name for functions
+ * returning scalar types.
+ */
+char *
+get_func_result_name(Oid functionId)
+{
+ char *result;
+ HeapTuple procTuple;
+ Datum proargmodes;
+ Datum proargnames;
+ bool isnull;
+ ArrayType *arr;
+ int numargs;
+ char *argmodes;
+ Datum *argnames;
+ int numoutargs;
+ int nargnames;
+ int i;
+
+ /* First fetch the function's pg_proc row */
+ procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId));
+ if (!HeapTupleIsValid(procTuple))
+ elog(ERROR, "cache lookup failed for function %u", functionId);
+
+ /* If there are no named OUT parameters, return NULL */
+ if (heap_attisnull(procTuple, Anum_pg_proc_proargmodes, NULL) ||
+ heap_attisnull(procTuple, Anum_pg_proc_proargnames, NULL))
+ result = NULL;
+ else
+ {
+ /* Get the data out of the tuple */
+ proargmodes = SysCacheGetAttr(PROCOID, procTuple,
+ Anum_pg_proc_proargmodes,
+ &isnull);
+ Assert(!isnull);
+ proargnames = SysCacheGetAttr(PROCOID, procTuple,
+ Anum_pg_proc_proargnames,
+ &isnull);
+ Assert(!isnull);
+
+ /*
+ * We expect the arrays to be 1-D arrays of the right types; verify
+ * that. For the char array, we don't need to use deconstruct_array()
+ * since the array data is just going to look like a C array of
+ * values.
+ */
+ arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */
+ numargs = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numargs < 0 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "proargmodes is not a 1-D char array or it contains nulls");
+ argmodes = (char *) ARR_DATA_PTR(arr);
+ arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_DIMS(arr)[0] != numargs ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != TEXTOID)
+ elog(ERROR, "proargnames is not a 1-D text array of length %d or it contains nulls",
+ numargs);
+ deconstruct_array(arr, TEXTOID, -1, false, TYPALIGN_INT,
+ &argnames, NULL, &nargnames);
+ Assert(nargnames == numargs);
+
+ /* scan for output argument(s) */
+ result = NULL;
+ numoutargs = 0;
+ for (i = 0; i < numargs; i++)
+ {
+ if (argmodes[i] == PROARGMODE_IN ||
+ argmodes[i] == PROARGMODE_VARIADIC)
+ continue;
+ Assert(argmodes[i] == PROARGMODE_OUT ||
+ argmodes[i] == PROARGMODE_INOUT ||
+ argmodes[i] == PROARGMODE_TABLE);
+ if (++numoutargs > 1)
+ {
+ /* multiple out args, so forget it */
+ result = NULL;
+ break;
+ }
+ result = TextDatumGetCString(argnames[i]);
+ if (result == NULL || result[0] == '\0')
+ {
+ /* Parameter is not named, so forget it */
+ result = NULL;
+ break;
+ }
+ }
+ }
+
+ ReleaseSysCache(procTuple);
+
+ return result;
+}
+
+
+/*
+ * build_function_result_tupdesc_t
+ *
+ * Given a pg_proc row for a function, return a tuple descriptor for the
+ * result rowtype, or NULL if the function does not have OUT parameters.
+ *
+ * Note that this does not handle resolution of polymorphic types;
+ * that is deliberate.
+ */
+TupleDesc
+build_function_result_tupdesc_t(HeapTuple procTuple)
+{
+ Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(procTuple);
+ Datum proallargtypes;
+ Datum proargmodes;
+ Datum proargnames;
+ bool isnull;
+
+ /* Return NULL if the function isn't declared to return RECORD */
+ if (procform->prorettype != RECORDOID)
+ return NULL;
+
+ /* If there are no OUT parameters, return NULL */
+ if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes, NULL) ||
+ heap_attisnull(procTuple, Anum_pg_proc_proargmodes, NULL))
+ return NULL;
+
+ /* Get the data out of the tuple */
+ proallargtypes = SysCacheGetAttr(PROCOID, procTuple,
+ Anum_pg_proc_proallargtypes,
+ &isnull);
+ Assert(!isnull);
+ proargmodes = SysCacheGetAttr(PROCOID, procTuple,
+ Anum_pg_proc_proargmodes,
+ &isnull);
+ Assert(!isnull);
+ proargnames = SysCacheGetAttr(PROCOID, procTuple,
+ Anum_pg_proc_proargnames,
+ &isnull);
+ if (isnull)
+ proargnames = PointerGetDatum(NULL); /* just to be sure */
+
+ return build_function_result_tupdesc_d(procform->prokind,
+ proallargtypes,
+ proargmodes,
+ proargnames);
+}
+
+/*
+ * build_function_result_tupdesc_d
+ *
+ * Build a RECORD function's tupledesc from the pg_proc proallargtypes,
+ * proargmodes, and proargnames arrays. This is split out for the
+ * convenience of ProcedureCreate, which needs to be able to compute the
+ * tupledesc before actually creating the function.
+ *
+ * For functions (but not for procedures), returns NULL if there are not at
+ * least two OUT or INOUT arguments.
+ */
+TupleDesc
+build_function_result_tupdesc_d(char prokind,
+ Datum proallargtypes,
+ Datum proargmodes,
+ Datum proargnames)
+{
+ TupleDesc desc;
+ ArrayType *arr;
+ int numargs;
+ Oid *argtypes;
+ char *argmodes;
+ Datum *argnames = NULL;
+ Oid *outargtypes;
+ char **outargnames;
+ int numoutargs;
+ int nargnames;
+ int i;
+
+ /* Can't have output args if columns are null */
+ if (proallargtypes == PointerGetDatum(NULL) ||
+ proargmodes == PointerGetDatum(NULL))
+ return NULL;
+
+ /*
+ * We expect the arrays to be 1-D arrays of the right types; verify that.
+ * For the OID and char arrays, we don't need to use deconstruct_array()
+ * since the array data is just going to look like a C array of values.
+ */
+ arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */
+ numargs = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numargs < 0 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != OIDOID)
+ elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls");
+ argtypes = (Oid *) ARR_DATA_PTR(arr);
+ arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_DIMS(arr)[0] != numargs ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls",
+ numargs);
+ argmodes = (char *) ARR_DATA_PTR(arr);
+ if (proargnames != PointerGetDatum(NULL))
+ {
+ arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_DIMS(arr)[0] != numargs ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != TEXTOID)
+ elog(ERROR, "proargnames is not a 1-D text array of length %d or it contains nulls",
+ numargs);
+ deconstruct_array(arr, TEXTOID, -1, false, TYPALIGN_INT,
+ &argnames, NULL, &nargnames);
+ Assert(nargnames == numargs);
+ }
+
+ /* zero elements probably shouldn't happen, but handle it gracefully */
+ if (numargs <= 0)
+ return NULL;
+
+ /* extract output-argument types and names */
+ outargtypes = (Oid *) palloc(numargs * sizeof(Oid));
+ outargnames = (char **) palloc(numargs * sizeof(char *));
+ numoutargs = 0;
+ for (i = 0; i < numargs; i++)
+ {
+ char *pname;
+
+ if (argmodes[i] == PROARGMODE_IN ||
+ argmodes[i] == PROARGMODE_VARIADIC)
+ continue;
+ Assert(argmodes[i] == PROARGMODE_OUT ||
+ argmodes[i] == PROARGMODE_INOUT ||
+ argmodes[i] == PROARGMODE_TABLE);
+ outargtypes[numoutargs] = argtypes[i];
+ if (argnames)
+ pname = TextDatumGetCString(argnames[i]);
+ else
+ pname = NULL;
+ if (pname == NULL || pname[0] == '\0')
+ {
+ /* Parameter is not named, so gin up a column name */
+ pname = psprintf("column%d", numoutargs + 1);
+ }
+ outargnames[numoutargs] = pname;
+ numoutargs++;
+ }
+
+ /*
+ * If there is no output argument, or only one, the function does not
+ * return tuples.
+ */
+ if (numoutargs < 2 && prokind != PROKIND_PROCEDURE)
+ return NULL;
+
+ desc = CreateTemplateTupleDesc(numoutargs);
+ for (i = 0; i < numoutargs; i++)
+ {
+ TupleDescInitEntry(desc, i + 1,
+ outargnames[i],
+ outargtypes[i],
+ -1,
+ 0);
+ }
+
+ return desc;
+}
+
+
+/*
+ * RelationNameGetTupleDesc
+ *
+ * Given a (possibly qualified) relation name, build a TupleDesc.
+ *
+ * Note: while this works as advertised, it's seldom the best way to
+ * build a tupdesc for a function's result type. It's kept around
+ * only for backwards compatibility with existing user-written code.
+ */
+TupleDesc
+RelationNameGetTupleDesc(const char *relname)
+{
+ RangeVar *relvar;
+ Relation rel;
+ TupleDesc tupdesc;
+ List *relname_list;
+
+ /* Open relation and copy the tuple description */
+ relname_list = stringToQualifiedNameList(relname);
+ relvar = makeRangeVarFromNameList(relname_list);
+ rel = relation_openrv(relvar, AccessShareLock);
+ tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+ relation_close(rel, AccessShareLock);
+
+ return tupdesc;
+}
+
+/*
+ * TypeGetTupleDesc
+ *
+ * Given a type Oid, build a TupleDesc. (In most cases you should be
+ * using get_call_result_type or one of its siblings instead of this
+ * routine, so that you can handle OUT parameters, RECORD result type,
+ * and polymorphic results.)
+ *
+ * If the type is composite, *and* a colaliases List is provided, *and*
+ * the List is of natts length, use the aliases instead of the relation
+ * attnames. (NB: this usage is deprecated since it may result in
+ * creation of unnecessary transient record types.)
+ *
+ * If the type is a base type, a single item alias List is required.
+ */
+TupleDesc
+TypeGetTupleDesc(Oid typeoid, List *colaliases)
+{
+ Oid base_typeoid;
+ TypeFuncClass functypclass = get_type_func_class(typeoid, &base_typeoid);
+ TupleDesc tupdesc = NULL;
+
+ /*
+ * Build a suitable tupledesc representing the output rows. We
+ * intentionally do not support TYPEFUNC_COMPOSITE_DOMAIN here, as it's
+ * unlikely that legacy callers of this obsolete function would be
+ * prepared to apply domain constraints.
+ */
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ tupdesc = lookup_rowtype_tupdesc_copy(base_typeoid, -1);
+
+ if (colaliases != NIL)
+ {
+ int natts = tupdesc->natts;
+ int varattno;
+
+ /* does the list length match the number of attributes? */
+ if (list_length(colaliases) != natts)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("number of aliases does not match number of columns")));
+
+ /* OK, use the aliases instead */
+ for (varattno = 0; varattno < natts; varattno++)
+ {
+ char *label = strVal(list_nth(colaliases, varattno));
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
+
+ if (label != NULL)
+ namestrcpy(&(attr->attname), label);
+ }
+
+ /* The tuple type is now an anonymous record type */
+ tupdesc->tdtypeid = RECORDOID;
+ tupdesc->tdtypmod = -1;
+ }
+ }
+ else if (functypclass == TYPEFUNC_SCALAR)
+ {
+ /* Base data type, i.e. scalar */
+ char *attname;
+
+ /* the alias list is required for base types */
+ if (colaliases == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("no column alias was provided")));
+
+ /* the alias list length must be 1 */
+ if (list_length(colaliases) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("number of aliases does not match number of columns")));
+
+ /* OK, get the column alias */
+ attname = strVal(linitial(colaliases));
+
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) 1,
+ attname,
+ typeoid,
+ -1,
+ 0);
+ }
+ else if (functypclass == TYPEFUNC_RECORD)
+ {
+ /* XXX can't support this because typmod wasn't passed in ... */
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not determine row description for function returning record")));
+ }
+ else
+ {
+ /* crummy error message, but parser should have caught this */
+ elog(ERROR, "function in FROM has unsupported return type");
+ }
+
+ return tupdesc;
+}
+
+/*
+ * extract_variadic_args
+ *
+ * Extract a set of argument values, types and NULL markers for a given
+ * input function which makes use of a VARIADIC input whose argument list
+ * depends on the caller context. When doing a VARIADIC call, the caller
+ * has provided one argument made of an array of values, so deconstruct the
+ * array data before using it for the next processing. If no VARIADIC call
+ * is used, just fill in the status data based on all the arguments given
+ * by the caller.
+ *
+ * This function returns the number of arguments generated, or -1 in the
+ * case of "VARIADIC NULL".
+ */
+int
+extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start,
+ bool convert_unknown, Datum **args, Oid **types,
+ bool **nulls)
+{
+ bool variadic = get_fn_expr_variadic(fcinfo->flinfo);
+ Datum *args_res;
+ bool *nulls_res;
+ Oid *types_res;
+ int nargs,
+ i;
+
+ *args = NULL;
+ *types = NULL;
+ *nulls = NULL;
+
+ if (variadic)
+ {
+ ArrayType *array_in;
+ Oid element_type;
+ bool typbyval;
+ char typalign;
+ int16 typlen;
+
+ Assert(PG_NARGS() == variadic_start + 1);
+
+ if (PG_ARGISNULL(variadic_start))
+ return -1;
+
+ array_in = PG_GETARG_ARRAYTYPE_P(variadic_start);
+ element_type = ARR_ELEMTYPE(array_in);
+
+ get_typlenbyvalalign(element_type,
+ &typlen, &typbyval, &typalign);
+ deconstruct_array(array_in, element_type, typlen, typbyval,
+ typalign, &args_res, &nulls_res,
+ &nargs);
+
+ /* All the elements of the array have the same type */
+ types_res = (Oid *) palloc0(nargs * sizeof(Oid));
+ for (i = 0; i < nargs; i++)
+ types_res[i] = element_type;
+ }
+ else
+ {
+ nargs = PG_NARGS() - variadic_start;
+ Assert(nargs > 0);
+ nulls_res = (bool *) palloc0(nargs * sizeof(bool));
+ args_res = (Datum *) palloc0(nargs * sizeof(Datum));
+ types_res = (Oid *) palloc0(nargs * sizeof(Oid));
+
+ for (i = 0; i < nargs; i++)
+ {
+ nulls_res[i] = PG_ARGISNULL(i + variadic_start);
+ types_res[i] = get_fn_expr_argtype(fcinfo->flinfo,
+ i + variadic_start);
+
+ /*
+ * Turn a constant (more or less literal) value that's of unknown
+ * type into text if required. Unknowns come in as a cstring
+ * pointer. Note: for functions declared as taking type "any", the
+ * parser will not do any type conversion on unknown-type literals
+ * (that is, undecorated strings or NULLs).
+ */
+ if (convert_unknown &&
+ types_res[i] == UNKNOWNOID &&
+ get_fn_expr_arg_stable(fcinfo->flinfo, i + variadic_start))
+ {
+ types_res[i] = TEXTOID;
+
+ if (PG_ARGISNULL(i + variadic_start))
+ args_res[i] = (Datum) 0;
+ else
+ args_res[i] =
+ CStringGetTextDatum(PG_GETARG_POINTER(i + variadic_start));
+ }
+ else
+ {
+ /* no conversion needed, just take the datum as given */
+ args_res[i] = PG_GETARG_DATUM(i + variadic_start);
+ }
+
+ if (!OidIsValid(types_res[i]) ||
+ (convert_unknown && types_res[i] == UNKNOWNOID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determine data type for argument %d",
+ i + 1)));
+ }
+ }
+
+ /* Fill in results */
+ *args = args_res;
+ *nulls = nulls_res;
+ *types = types_res;
+
+ return nargs;
+}