SQL functions can have arguments and results declared ANYARRAY or

ANYELEMENT.  The effect is to postpone typechecking of the function
body until runtime.  Documentation is still lacking.

Original patch by Joe Conway, modified to postpone type checking
by Tom Lane.
This commit is contained in:
Tom Lane 2003-07-01 00:04:39 +00:00
parent 71e9f3b07f
commit d6d07a0eea
8 changed files with 152 additions and 57 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.97 2003/06/15 17:59:10 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.98 2003/07/01 00:04:37 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -33,7 +33,6 @@
#include "utils/syscache.h"
static void checkretval(Oid rettype, char fn_typtype, List *queryTreeList);
Datum fmgr_internal_validator(PG_FUNCTION_ARGS);
Datum fmgr_c_validator(PG_FUNCTION_ARGS);
Datum fmgr_sql_validator(PG_FUNCTION_ARGS);
@ -317,15 +316,20 @@ ProcedureCreate(const char *procedureName,
}
/*
* checkretval() -- check return value of a list of sql parse trees.
* check_sql_fn_retval() -- check return value of a list of sql parse trees.
*
* The return value of a sql function is the value returned by
* the final query in the function. We do some ad-hoc define-time
* type checking here to be sure that the user is returning the
* type he claims.
* the final query in the function. We do some ad-hoc type checking here
* to be sure that the user is returning the type he claims.
*
* This is normally applied during function definition, but in the case
* of a function with polymorphic arguments, we instead apply it during
* function execution startup. The rettype is then the actual resolved
* output type of the function, rather than the declared type. (Therefore,
* we should never see ANYARRAY or ANYELEMENT as rettype.)
*/
static void
checkretval(Oid rettype, char fn_typtype, List *queryTreeList)
void
check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList)
{
Query *parse;
int cmd;
@ -472,7 +476,7 @@ checkretval(Oid rettype, char fn_typtype, List *queryTreeList)
relation_close(reln, AccessShareLock);
}
else if (fn_typtype == 'p' && rettype == RECORDOID)
else if (rettype == RECORDOID)
{
/* Shouldn't have a typerelid */
Assert(typerelid == InvalidOid);
@ -482,6 +486,14 @@ checkretval(Oid rettype, char fn_typtype, List *queryTreeList)
* tuple.
*/
}
else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
{
/*
* This should already have been caught ...
*/
elog(ERROR, "functions returning ANYARRAY or ANYELEMENT must " \
"have at least one argument of either type");
}
else
elog(ERROR, "return type %s is not supported for SQL functions",
format_type_be(rettype));
@ -505,7 +517,9 @@ fmgr_internal_validator(PG_FUNCTION_ARGS)
Datum tmp;
char *prosrc;
tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
tuple = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcoid),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup of function %u failed", funcoid);
proc = (Form_pg_proc) GETSTRUCT(tuple);
@ -544,7 +558,9 @@ fmgr_c_validator(PG_FUNCTION_ARGS)
char *prosrc;
char *probin;
tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
tuple = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcoid),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup of function %u failed", funcoid);
proc = (Form_pg_proc) GETSTRUCT(tuple);
@ -585,38 +601,62 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
Datum tmp;
char *prosrc;
char functyptype;
bool haspolyarg;
int i;
tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
tuple = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcoid),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup of function %u failed", funcoid);
proc = (Form_pg_proc) GETSTRUCT(tuple);
functyptype = get_typtype(proc->prorettype);
/* Disallow pseudotypes in arguments and result */
/* except that return type can be RECORD or VOID */
/* Disallow pseudotype result */
/* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */
if (functyptype == 'p' &&
proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID)
proc->prorettype != VOIDOID &&
proc->prorettype != ANYARRAYOID &&
proc->prorettype != ANYELEMENTOID)
elog(ERROR, "SQL functions cannot return type %s",
format_type_be(proc->prorettype));
/* Disallow pseudotypes in arguments */
/* except for ANYARRAY or ANYELEMENT */
haspolyarg = false;
for (i = 0; i < proc->pronargs; i++)
{
if (get_typtype(proc->proargtypes[i]) == 'p')
elog(ERROR, "SQL functions cannot have arguments of type %s",
format_type_be(proc->proargtypes[i]));
{
if (proc->proargtypes[i] == ANYARRAYOID ||
proc->proargtypes[i] == ANYELEMENTOID)
haspolyarg = true;
else
elog(ERROR, "SQL functions cannot have arguments of type %s",
format_type_be(proc->proargtypes[i]));
}
}
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
/*
* We can't precheck the function definition if there are any polymorphic
* input types, because actual datatypes of expression results will be
* unresolvable. The check will be done at runtime instead.
*/
if (!haspolyarg)
{
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
querytree_list = pg_parse_and_rewrite(prosrc, proc->proargtypes, proc->pronargs);
checkretval(proc->prorettype, functyptype, querytree_list);
querytree_list = pg_parse_and_rewrite(prosrc,
proc->proargtypes,
proc->pronargs);
check_sql_fn_retval(proc->prorettype, functyptype, querytree_list);
}
ReleaseSysCache(tuple);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.66 2003/06/12 17:29:26 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.67 2003/07/01 00:04:37 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -24,6 +24,7 @@
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@ -76,7 +77,8 @@ typedef SQLFunctionCache *SQLFunctionCachePtr;
/* non-export function prototypes */
static execution_state *init_execution_state(char *src,
Oid *argOidVect, int nargs);
Oid *argOidVect, int nargs,
Oid rettype, bool haspolyarg);
static void init_sql_fcache(FmgrInfo *finfo);
static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
static TupleTableSlot *postquel_getnext(execution_state *es);
@ -90,7 +92,8 @@ static void ShutdownSQLFunction(Datum arg);
static execution_state *
init_execution_state(char *src, Oid *argOidVect, int nargs)
init_execution_state(char *src, Oid *argOidVect, int nargs,
Oid rettype, bool haspolyarg)
{
execution_state *firstes;
execution_state *preves;
@ -99,6 +102,13 @@ init_execution_state(char *src, Oid *argOidVect, int nargs)
queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs);
/*
* If the function has any arguments declared as polymorphic types,
* then it wasn't type-checked at definition time; must do so now.
*/
if (haspolyarg)
check_sql_fn_retval(rettype, get_typtype(rettype), queryTree_list);
firstes = NULL;
preves = NULL;
@ -133,17 +143,21 @@ static void
init_sql_fcache(FmgrInfo *finfo)
{
Oid foid = finfo->fn_oid;
Oid rettype;
HeapTuple procedureTuple;
HeapTuple typeTuple;
Form_pg_proc procedureStruct;
Form_pg_type typeStruct;
SQLFunctionCachePtr fcache;
Oid *argOidVect;
bool haspolyarg;
char *src;
int nargs;
Datum tmp;
bool isNull;
fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache));
/*
* get the procedure tuple corresponding to the given function Oid
*/
@ -153,30 +167,37 @@ init_sql_fcache(FmgrInfo *finfo)
if (!HeapTupleIsValid(procedureTuple))
elog(ERROR, "init_sql_fcache: Cache lookup failed for procedure %u",
foid);
procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
/*
* get the return type from the procedure tuple
* get the result type from the procedure tuple, and check for
* polymorphic result type; if so, find out the actual result type.
*/
rettype = procedureStruct->prorettype;
if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
{
rettype = get_fn_expr_rettype(finfo);
if (rettype == InvalidOid)
elog(ERROR, "could not determine actual result type for function declared %s",
format_type_be(procedureStruct->prorettype));
}
/* Now look up the actual result type */
typeTuple = SearchSysCache(TYPEOID,
ObjectIdGetDatum(procedureStruct->prorettype),
ObjectIdGetDatum(rettype),
0, 0, 0);
if (!HeapTupleIsValid(typeTuple))
elog(ERROR, "init_sql_fcache: Cache lookup failed for type %u",
procedureStruct->prorettype);
rettype);
typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache));
/*
* get the type length and by-value flag from the type tuple
*/
fcache->typlen = typeStruct->typlen;
if (typeStruct->typtype != 'c' &&
procedureStruct->prorettype != RECORDOID)
if (typeStruct->typtype != 'c' && rettype != RECORDOID)
{
/* The return type is not a composite type, so just use byval */
fcache->typbyval = typeStruct->typbyval;
@ -205,17 +226,35 @@ init_sql_fcache(FmgrInfo *finfo)
fcache->funcSlot = NULL;
/*
* Parse and plan the queries. We need the argument info to pass
* Parse and plan the queries. We need the argument type info to pass
* to the parser.
*/
nargs = procedureStruct->pronargs;
haspolyarg = false;
if (nargs > 0)
{
int argnum;
argOidVect = (Oid *) palloc(nargs * sizeof(Oid));
memcpy(argOidVect,
procedureStruct->proargtypes,
nargs * sizeof(Oid));
/* Resolve any polymorphic argument types */
for (argnum = 0; argnum < nargs; argnum++)
{
Oid argtype = argOidVect[argnum];
if (argtype == ANYARRAYOID || argtype == ANYELEMENTOID)
{
argtype = get_fn_expr_argtype(finfo, argnum);
if (argtype == InvalidOid)
elog(ERROR, "could not determine actual type of argument declared %s",
format_type_be(argOidVect[argnum]));
argOidVect[argnum] = argtype;
haspolyarg = true;
}
}
}
else
argOidVect = (Oid *) NULL;
@ -229,7 +268,8 @@ init_sql_fcache(FmgrInfo *finfo)
foid);
src = DatumGetCString(DirectFunctionCall1(textout, tmp));
fcache->func_state = init_execution_state(src, argOidVect, nargs);
fcache->func_state = init_execution_state(src, argOidVect, nargs,
rettype, haspolyarg);
pfree(src);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.142 2003/06/29 00:33:43 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.143 2003/07/01 00:04:37 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -1731,6 +1731,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
int *usecounts;
List *arg;
int i;
int j;
/*
* Forget it if the function is not SQL-language or has other
@ -1742,12 +1743,20 @@ inline_function(Oid funcid, Oid result_type, List *args,
funcform->pronargs != length(args))
return NULL;
/* Forget it if declared return type is tuple or void */
/* Forget it if declared return type is not base or domain */
result_typtype = get_typtype(funcform->prorettype);
if (result_typtype != 'b' &&
result_typtype != 'd')
return NULL;
/* Forget it if any declared argument type is polymorphic */
for (j = 0; j < funcform->pronargs; j++)
{
if (funcform->proargtypes[j] == ANYARRAYOID ||
funcform->proargtypes[j] == ANYELEMENTOID)
return NULL;
}
/* Check for recursive function, and give up trying to expand if so */
if (oidMember(funcid, active_fns))
return NULL;

View File

@ -6,7 +6,7 @@
* Copyright (c) 2003, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.4 2003/06/27 00:33:25 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.5 2003/07/01 00:04:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -37,8 +37,8 @@ array_push(PG_FUNCTION_ARGS)
int16 typlen;
bool typbyval;
char typalign;
Oid arg0_typeid = get_fn_expr_argtype(fcinfo, 0);
Oid arg1_typeid = get_fn_expr_argtype(fcinfo, 1);
Oid arg0_typeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
Oid arg0_elemid;
Oid arg1_elemid;
ArrayMetaState *my_extra;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.92 2003/06/27 00:33:25 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.93 2003/07/01 00:04:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2792,7 +2792,7 @@ array_type_coerce(PG_FUNCTION_ARGS)
if (my_extra->srctype != src_elem_type)
{
Oid tgt_type = get_fn_expr_rettype(fcinfo);
Oid tgt_type = get_fn_expr_rettype(fmgr_info);
Oid tgt_elem_type;
Oid funcId;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.71 2003/06/29 00:33:44 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.72 2003/07/01 00:04:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1616,16 +1616,19 @@ pg_detoast_datum_slice(struct varlena * datum, int32 first, int32 count)
/*-------------------------------------------------------------------------
* Support routines for extracting info from fn_expr parse tree
*
* These are needed by polymorphic functions, which accept multiple possible
* input types and need help from the parser to know what they've got.
*-------------------------------------------------------------------------
*/
/*
* Get the OID of the function return type
* Get the actual type OID of the function return type
*
* Returns InvalidOid if information is not available
*/
Oid
get_fn_expr_rettype(FunctionCallInfo fcinfo)
get_fn_expr_rettype(FmgrInfo *flinfo)
{
Node *expr;
@ -1633,21 +1636,21 @@ get_fn_expr_rettype(FunctionCallInfo fcinfo)
* can't return anything useful if we have no FmgrInfo or if
* its fn_expr node has not been initialized
*/
if (!fcinfo || !fcinfo->flinfo || !fcinfo->flinfo->fn_expr)
if (!flinfo || !flinfo->fn_expr)
return InvalidOid;
expr = fcinfo->flinfo->fn_expr;
expr = flinfo->fn_expr;
return exprType(expr);
}
/*
* Get the type OID of a specific function argument (counting from 0)
* Get the actual type OID of a specific function argument (counting from 0)
*
* Returns InvalidOid if information is not available
*/
Oid
get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum)
get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
{
Node *expr;
List *args;
@ -1657,10 +1660,10 @@ get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum)
* can't return anything useful if we have no FmgrInfo or if
* its fn_expr node has not been initialized
*/
if (!fcinfo || !fcinfo->flinfo || !fcinfo->flinfo->fn_expr)
if (!flinfo || !flinfo->fn_expr)
return InvalidOid;
expr = fcinfo->flinfo->fn_expr;
expr = flinfo->fn_expr;
if (IsA(expr, FuncExpr))
args = ((FuncExpr *) expr)->args;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: pg_proc.h,v 1.308 2003/06/27 00:33:25 tgl Exp $
* $Id: pg_proc.h,v 1.309 2003/07/01 00:04:38 tgl Exp $
*
* NOTES
* The script catalog/genbki.sh reads this file and generates .bki
@ -3438,4 +3438,7 @@ extern Oid ProcedureCreate(const char *procedureName,
int parameterCount,
const Oid *parameterTypes);
extern void check_sql_fn_retval(Oid rettype, char fn_typtype,
List *queryTreeList);
#endif /* PG_PROC_H */

View File

@ -11,7 +11,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: fmgr.h,v 1.29 2003/06/25 21:30:32 momjian Exp $
* $Id: fmgr.h,v 1.30 2003/07/01 00:04:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -378,8 +378,8 @@ extern Datum OidFunctionCall9(Oid functionId, Datum arg1, Datum arg2,
*/
extern Pg_finfo_record *fetch_finfo_record(void *filehandle, char *funcname);
extern Oid fmgr_internal_function(const char *proname);
extern Oid get_fn_expr_rettype(FunctionCallInfo fcinfo);
extern Oid get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum);
extern Oid get_fn_expr_rettype(FmgrInfo *flinfo);
extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
/*
* Routines in dfmgr.c