mirror of
https://github.com/postgres/postgres.git
synced 2025-11-10 17:42:29 +03:00
First phase of OUT-parameters project. We can now define and use SQL
functions with OUT parameters. The various PLs still need work, as does pg_dump. Rudimentary docs and regression tests included.
This commit is contained in:
38
src/backend/utils/cache/lsyscache.c
vendored
38
src/backend/utils/cache/lsyscache.c
vendored
@@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.121 2005/03/29 00:17:11 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.122 2005/03/31 22:46:14 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* Eventually, the index information should go through here, too.
|
||||
@@ -1543,42 +1543,6 @@ get_typtype(Oid typid)
|
||||
return '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_typ_typrelid
|
||||
*
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.92 2005/03/29 03:01:31 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.93 2005/03/31 22:46:16 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -403,7 +403,7 @@ fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple)
|
||||
* We want to raise an error here only if the info function returns
|
||||
* something bogus.
|
||||
*
|
||||
* This function is broken out of fmgr_info_C_lang() so that ProcedureCreate()
|
||||
* 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.
|
||||
*/
|
||||
@@ -576,8 +576,8 @@ fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo,
|
||||
|
||||
|
||||
/*
|
||||
* Specialized lookup routine for ProcedureCreate(): given the alleged name
|
||||
* of an internal function, return the OID of the function.
|
||||
* 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
|
||||
@@ -1869,10 +1869,6 @@ get_fn_expr_rettype(FmgrInfo *flinfo)
|
||||
Oid
|
||||
get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
|
||||
{
|
||||
Node *expr;
|
||||
List *args;
|
||||
Oid argtype;
|
||||
|
||||
/*
|
||||
* can't return anything useful if we have no FmgrInfo or if its
|
||||
* fn_expr node has not been initialized
|
||||
@@ -1880,7 +1876,23 @@ get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
|
||||
if (!flinfo || !flinfo->fn_expr)
|
||||
return InvalidOid;
|
||||
|
||||
expr = flinfo->fn_expr;
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user