From d6d07a0eeabc8dfa6f8803193c2896d3e2e53a3c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 1 Jul 2003 00:04:39 +0000 Subject: [PATCH] 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. --- src/backend/catalog/pg_proc.c | 86 ++++++++++++++++++------- src/backend/executor/functions.c | 68 +++++++++++++++---- src/backend/optimizer/util/clauses.c | 13 +++- src/backend/utils/adt/array_userfuncs.c | 6 +- src/backend/utils/adt/arrayfuncs.c | 4 +- src/backend/utils/fmgr/fmgr.c | 21 +++--- src/include/catalog/pg_proc.h | 5 +- src/include/fmgr.h | 6 +- 8 files changed, 152 insertions(+), 57 deletions(-) diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 48b90e56ef..4ec4c44bd4 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -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); diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index a0e0919fd0..e0ab9e92d5 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -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); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 54f2d7bd69..3da79cc495 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -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; diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c index 3aa70b0d33..6c28b211ce 100644 --- a/src/backend/utils/adt/array_userfuncs.c +++ b/src/backend/utils/adt/array_userfuncs.c @@ -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; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 808353a63d..d0a876a877 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -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; diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 0d69ac1083..04e72e5341 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -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; diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 578a6b7d42..b1f16c2d42 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -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 */ diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 51844eac38..1cc6ed023e 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -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