|
|
|
|
@ -7,17 +7,37 @@ |
|
|
|
|
* Copyright (c) 2002-2005, PostgreSQL Global Development Group |
|
|
|
|
* |
|
|
|
|
* IDENTIFICATION |
|
|
|
|
* $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.18 2005/01/01 05:43:08 momjian Exp $ |
|
|
|
|
* $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.19 2005/03/31 22:46:16 tgl Exp $ |
|
|
|
|
* |
|
|
|
|
*------------------------------------------------------------------------- |
|
|
|
|
*/ |
|
|
|
|
#include "postgres.h" |
|
|
|
|
|
|
|
|
|
#include "access/heapam.h" |
|
|
|
|
#include "funcapi.h" |
|
|
|
|
#include "catalog/namespace.h" |
|
|
|
|
#include "catalog/pg_proc.h" |
|
|
|
|
#include "catalog/pg_type.h" |
|
|
|
|
#include "parser/parse_coerce.h" |
|
|
|
|
#include "parser/parse_expr.h" |
|
|
|
|
#include "utils/array.h" |
|
|
|
|
#include "utils/builtins.h" |
|
|
|
|
#include "utils/lsyscache.h" |
|
|
|
|
#include "utils/syscache.h" |
|
|
|
|
#include "utils/typcache.h" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void shutdown_MultiFuncCall(Datum arg); |
|
|
|
|
static TypeFuncClass internal_get_result_type(Oid funcid, |
|
|
|
|
Node *call_expr, |
|
|
|
|
ReturnSetInfo *rsinfo, |
|
|
|
|
Oid *resultTypeId, |
|
|
|
|
TupleDesc *resultTupleDesc); |
|
|
|
|
static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc, |
|
|
|
|
oidvector *declared_args, |
|
|
|
|
Node *call_expr); |
|
|
|
|
static TypeFuncClass get_type_func_class(Oid typid); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* init_MultiFuncCall |
|
|
|
|
@ -156,3 +176,624 @@ shutdown_MultiFuncCall(Datum arg) |
|
|
|
|
|
|
|
|
|
pfree(funcctx); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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. NB: the tupledesc should |
|
|
|
|
* be copied if it is to be accessed over a long period. |
|
|
|
|
* |
|
|
|
|
* 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 ANYELEMENT or ANYARRAY, 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 |
|
|
|
|
{ |
|
|
|
|
/* handle as a generic expression; no chance to resolve RECORD */ |
|
|
|
|
Oid typid = exprType(expr); |
|
|
|
|
|
|
|
|
|
if (resultTypeId) |
|
|
|
|
*resultTypeId = typid; |
|
|
|
|
if (resultTupleDesc) |
|
|
|
|
*resultTupleDesc = NULL; |
|
|
|
|
result = get_type_func_class(typid); |
|
|
|
|
if (result == TYPEFUNC_COMPOSITE && resultTupleDesc) |
|
|
|
|
*resultTupleDesc = lookup_rowtype_tupdesc(typid, -1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get_expr_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; |
|
|
|
|
TupleDesc tupdesc; |
|
|
|
|
|
|
|
|
|
/* First fetch the function's pg_proc row to inspect its rettype */ |
|
|
|
|
tp = SearchSysCache(PROCOID, |
|
|
|
|
ObjectIdGetDatum(funcid), |
|
|
|
|
0, 0, 0); |
|
|
|
|
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 (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) |
|
|
|
|
{ |
|
|
|
|
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); |
|
|
|
|
switch (result) |
|
|
|
|
{ |
|
|
|
|
case TYPEFUNC_COMPOSITE: |
|
|
|
|
if (resultTupleDesc) |
|
|
|
|
*resultTupleDesc = lookup_rowtype_tupdesc(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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Given the result tuple descriptor for a function with OUT parameters, |
|
|
|
|
* replace any polymorphic columns (ANYELEMENT/ANYARRAY) with correct data |
|
|
|
|
* types deduced from the input arguments. Returns TRUE if able to deduce |
|
|
|
|
* all types, FALSE if not. |
|
|
|
|
*/ |
|
|
|
|
static bool |
|
|
|
|
resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, |
|
|
|
|
Node *call_expr) |
|
|
|
|
{ |
|
|
|
|
int natts = tupdesc->natts; |
|
|
|
|
int nargs = declared_args->dim1; |
|
|
|
|
bool have_anyelement_result = false; |
|
|
|
|
bool have_anyarray_result = false; |
|
|
|
|
Oid anyelement_type = InvalidOid; |
|
|
|
|
Oid anyarray_type = InvalidOid; |
|
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
/* See if there are any polymorphic outputs; quick out if not */ |
|
|
|
|
for (i = 0; i < natts; i++) |
|
|
|
|
{ |
|
|
|
|
switch (tupdesc->attrs[i]->atttypid) |
|
|
|
|
{ |
|
|
|
|
case ANYELEMENTOID: |
|
|
|
|
have_anyelement_result = true; |
|
|
|
|
break; |
|
|
|
|
case ANYARRAYOID: |
|
|
|
|
have_anyarray_result = true; |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (!have_anyelement_result && !have_anyarray_result) |
|
|
|
|
return true; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Otherwise, extract actual datatype(s) from input arguments. (We assume |
|
|
|
|
* the parser already validated consistency of the arguments.) |
|
|
|
|
*/ |
|
|
|
|
if (!call_expr) |
|
|
|
|
return false; /* no hope */ |
|
|
|
|
|
|
|
|
|
for (i = 0; i < nargs; i++) |
|
|
|
|
{ |
|
|
|
|
switch (declared_args->values[i]) |
|
|
|
|
{ |
|
|
|
|
case ANYELEMENTOID: |
|
|
|
|
if (!OidIsValid(anyelement_type)) |
|
|
|
|
anyelement_type = get_call_expr_argtype(call_expr, i); |
|
|
|
|
break; |
|
|
|
|
case ANYARRAYOID: |
|
|
|
|
if (!OidIsValid(anyarray_type)) |
|
|
|
|
anyarray_type = get_call_expr_argtype(call_expr, i); |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* If nothing found, parser messed up */ |
|
|
|
|
if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type)) |
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
/* If needed, deduce one polymorphic type from the other */ |
|
|
|
|
if (have_anyelement_result && !OidIsValid(anyelement_type)) |
|
|
|
|
anyelement_type = resolve_generic_type(ANYELEMENTOID, |
|
|
|
|
anyarray_type, |
|
|
|
|
ANYARRAYOID); |
|
|
|
|
if (have_anyarray_result && !OidIsValid(anyarray_type)) |
|
|
|
|
anyarray_type = resolve_generic_type(ANYARRAYOID, |
|
|
|
|
anyelement_type, |
|
|
|
|
ANYELEMENTOID); |
|
|
|
|
|
|
|
|
|
/* And finally replace the tuple column types as needed */ |
|
|
|
|
for (i = 0; i < natts; i++) |
|
|
|
|
{ |
|
|
|
|
switch (tupdesc->attrs[i]->atttypid) |
|
|
|
|
{ |
|
|
|
|
case ANYELEMENTOID: |
|
|
|
|
TupleDescInitEntry(tupdesc, i+1, |
|
|
|
|
NameStr(tupdesc->attrs[i]->attname), |
|
|
|
|
anyelement_type, |
|
|
|
|
-1, |
|
|
|
|
0); |
|
|
|
|
break; |
|
|
|
|
case ANYARRAYOID: |
|
|
|
|
TupleDescInitEntry(tupdesc, i+1, |
|
|
|
|
NameStr(tupdesc->attrs[i]->attname), |
|
|
|
|
anyarray_type, |
|
|
|
|
-1, |
|
|
|
|
0); |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get_type_func_class |
|
|
|
|
* Given the type OID, obtain its TYPEFUNC classification. |
|
|
|
|
* |
|
|
|
|
* 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) |
|
|
|
|
{ |
|
|
|
|
switch (get_typtype(typid)) |
|
|
|
|
{ |
|
|
|
|
case 'c': |
|
|
|
|
return TYPEFUNC_COMPOSITE; |
|
|
|
|
case 'b': |
|
|
|
|
case 'd': |
|
|
|
|
return TYPEFUNC_SCALAR; |
|
|
|
|
case 'p': |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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 ANYELEMENT/ANYARRAY 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) || |
|
|
|
|
heap_attisnull(procTuple, Anum_pg_proc_proargmodes)) |
|
|
|
|
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(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. |
|
|
|
|
* |
|
|
|
|
* Returns NULL if there are not at least two OUT or INOUT arguments. |
|
|
|
|
*/ |
|
|
|
|
TupleDesc |
|
|
|
|
build_function_result_tupdesc_d(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_ELEMTYPE(arr) != OIDOID) |
|
|
|
|
elog(ERROR, "proallargtypes is not a 1-D Oid array"); |
|
|
|
|
argtypes = (Oid *) ARR_DATA_PTR(arr); |
|
|
|
|
arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ |
|
|
|
|
if (ARR_NDIM(arr) != 1 || |
|
|
|
|
ARR_DIMS(arr)[0] != numargs || |
|
|
|
|
ARR_ELEMTYPE(arr) != CHAROID) |
|
|
|
|
elog(ERROR, "proargmodes is not a 1-D char array"); |
|
|
|
|
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_ELEMTYPE(arr) != TEXTOID) |
|
|
|
|
elog(ERROR, "proargnames is not a 1-D text array"); |
|
|
|
|
deconstruct_array(arr, TEXTOID, -1, false, 'i', |
|
|
|
|
&argnames, &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) |
|
|
|
|
continue; |
|
|
|
|
Assert(argmodes[i] == PROARGMODE_OUT || |
|
|
|
|
argmodes[i] == PROARGMODE_INOUT); |
|
|
|
|
outargtypes[numoutargs] = argtypes[i]; |
|
|
|
|
if (argnames) |
|
|
|
|
pname = DatumGetCString(DirectFunctionCall1(textout, argnames[i])); |
|
|
|
|
else |
|
|
|
|
pname = NULL; |
|
|
|
|
if (pname == NULL || pname[0] == '\0') |
|
|
|
|
{ |
|
|
|
|
/* Parameter is not named, so gin up a column name */ |
|
|
|
|
pname = (char *) palloc(32); |
|
|
|
|
snprintf(pname, 32, "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) |
|
|
|
|
return NULL; |
|
|
|
|
|
|
|
|
|
desc = CreateTemplateTupleDesc(numoutargs, false); |
|
|
|
|
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. |
|
|
|
|
*/ |
|
|
|
|
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, "RelationNameGetTupleDesc"); |
|
|
|
|
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. |
|
|
|
|
* |
|
|
|
|
* 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) |
|
|
|
|
{ |
|
|
|
|
TypeFuncClass functypclass = get_type_func_class(typeoid); |
|
|
|
|
TupleDesc tupdesc = NULL; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Build a suitable tupledesc representing the output rows |
|
|
|
|
*/ |
|
|
|
|
if (functypclass == TYPEFUNC_COMPOSITE) |
|
|
|
|
{ |
|
|
|
|
/* Composite data type, e.g. a table's row type */ |
|
|
|
|
tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(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)); |
|
|
|
|
|
|
|
|
|
if (label != NULL) |
|
|
|
|
namestrcpy(&(tupdesc->attrs[varattno]->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, false); |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|