diff options
Diffstat (limited to 'src/backend/utils/fmgr')
-rw-r--r-- | src/backend/utils/fmgr/Makefile | 22 | ||||
-rw-r--r-- | src/backend/utils/fmgr/README | 331 | ||||
-rw-r--r-- | src/backend/utils/fmgr/dfmgr.c | 755 | ||||
-rw-r--r-- | src/backend/utils/fmgr/fmgr.c | 2090 | ||||
-rw-r--r-- | src/backend/utils/fmgr/funcapi.c | 2003 |
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; +} |