2005 lines
57 KiB
C
2005 lines
57 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* funcapi.c
|
|
* Utility and convenience functions for fmgr functions that return
|
|
* sets and/or composite types, or deal with VARIADIC inputs.
|
|
*
|
|
* Copyright (c) 2002-2020, 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 function.
|
|
*/
|
|
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");
|
|
Assert(nelems >= ((Form_pg_proc) GETSTRUCT(procTup))->pronargs);
|
|
*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(char prokind,
|
|
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_OUT && prokind == PROKIND_PROCEDURE) ||
|
|
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;
|
|
}
|