1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* pg_proc.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* routines to support manipulation of the pg_proc relation
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
|
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2000-10-07 02:58:23 +02:00
|
|
|
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.49 2000/10/07 00:58:15 tgl Exp $
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
1998-04-27 06:08:07 +02:00
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "access/heapam.h"
|
|
|
|
#include "catalog/catname.h"
|
|
|
|
#include "catalog/indexing.h"
|
2000-05-28 19:56:29 +02:00
|
|
|
#include "catalog/pg_language.h"
|
1998-04-27 06:08:07 +02:00
|
|
|
#include "catalog/pg_proc.h"
|
|
|
|
#include "catalog/pg_type.h"
|
2000-08-21 22:55:31 +02:00
|
|
|
#include "executor/executor.h"
|
1998-04-27 06:08:07 +02:00
|
|
|
#include "miscadmin.h"
|
2000-08-21 22:55:31 +02:00
|
|
|
#include "parser/parse_expr.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "parser/parse_type.h"
|
1998-04-27 06:08:07 +02:00
|
|
|
#include "tcop/tcopprot.h"
|
|
|
|
#include "utils/builtins.h"
|
|
|
|
#include "utils/lsyscache.h"
|
|
|
|
#include "utils/sets.h"
|
|
|
|
#include "utils/syscache.h"
|
|
|
|
|
1996-11-04 00:27:08 +01:00
|
|
|
|
2000-08-21 22:55:31 +02:00
|
|
|
static void checkretval(Oid rettype, List *queryTreeList);
|
|
|
|
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/* ----------------------------------------------------------------
|
1999-04-18 04:57:22 +02:00
|
|
|
* ProcedureCreate
|
1996-07-09 08:22:35 +02:00
|
|
|
* ----------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
Oid
|
|
|
|
ProcedureCreate(char *procedureName,
|
1997-09-07 07:04:48 +02:00
|
|
|
bool returnsSet,
|
|
|
|
char *returnTypeName,
|
|
|
|
char *languageName,
|
|
|
|
char *prosrc,
|
|
|
|
char *probin,
|
|
|
|
bool trusted,
|
2000-05-28 19:56:29 +02:00
|
|
|
bool canCache,
|
|
|
|
bool isStrict,
|
1997-09-07 07:04:48 +02:00
|
|
|
int32 byte_pct,
|
|
|
|
int32 perbyte_cpu,
|
|
|
|
int32 percall_cpu,
|
|
|
|
int32 outin_ratio,
|
1997-09-08 23:56:23 +02:00
|
|
|
List *argList,
|
1997-09-07 07:04:48 +02:00
|
|
|
CommandDest dest)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1998-02-11 20:14:04 +01:00
|
|
|
int i;
|
1998-08-19 04:04:17 +02:00
|
|
|
Relation rel;
|
1997-09-08 04:41:22 +02:00
|
|
|
HeapTuple tup;
|
|
|
|
bool defined;
|
|
|
|
uint16 parameterCount;
|
|
|
|
char nulls[Natts_pg_proc];
|
|
|
|
Datum values[Natts_pg_proc];
|
|
|
|
Oid languageObjectId;
|
|
|
|
Oid typeObjectId;
|
|
|
|
List *x;
|
1999-05-13 09:29:22 +02:00
|
|
|
List *querytree_list;
|
2000-01-10 18:14:46 +01:00
|
|
|
Oid typev[FUNC_MAX_ARGS];
|
1997-09-08 04:41:22 +02:00
|
|
|
Oid relid;
|
|
|
|
Oid toid;
|
1998-04-01 17:35:33 +02:00
|
|
|
NameData procname;
|
1997-09-08 04:41:22 +02:00
|
|
|
TupleDesc tupDesc;
|
2000-06-14 06:53:44 +02:00
|
|
|
Oid retval;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/* ----------------
|
|
|
|
* sanity checks
|
|
|
|
* ----------------
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
1997-09-07 07:04:48 +02:00
|
|
|
Assert(PointerIsValid(prosrc));
|
|
|
|
Assert(PointerIsValid(probin));
|
|
|
|
|
2000-05-28 19:56:29 +02:00
|
|
|
tup = SearchSysCacheTuple(LANGNAME,
|
|
|
|
PointerGetDatum(languageName),
|
|
|
|
0, 0, 0);
|
|
|
|
|
|
|
|
if (!HeapTupleIsValid(tup))
|
|
|
|
elog(ERROR, "ProcedureCreate: no such language '%s'", languageName);
|
|
|
|
|
|
|
|
languageObjectId = tup->t_data->t_oid;
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
parameterCount = 0;
|
2000-01-10 18:14:46 +01:00
|
|
|
MemSet(typev, 0, FUNC_MAX_ARGS * sizeof(Oid));
|
1997-09-07 07:04:48 +02:00
|
|
|
foreach(x, argList)
|
|
|
|
{
|
2000-10-07 02:58:23 +02:00
|
|
|
TypeName *t = (TypeName *) lfirst(x);
|
|
|
|
char *typnam = TypeNameToInternalName(t);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-01-11 06:22:25 +01:00
|
|
|
if (parameterCount >= FUNC_MAX_ARGS)
|
|
|
|
elog(ERROR, "Procedures cannot take more than %d arguments",
|
|
|
|
FUNC_MAX_ARGS);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-10-07 02:58:23 +02:00
|
|
|
if (strcmp(typnam, "opaque") == 0)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2000-05-28 19:56:29 +02:00
|
|
|
if (languageObjectId == SQLlanguageId)
|
1999-04-18 04:57:22 +02:00
|
|
|
elog(ERROR, "ProcedureCreate: sql functions cannot take type \"opaque\"");
|
2000-10-07 02:58:23 +02:00
|
|
|
toid = InvalidOid;
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2000-10-07 02:58:23 +02:00
|
|
|
toid = TypeGet(typnam, &defined);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (!OidIsValid(toid))
|
1998-01-06 20:42:33 +01:00
|
|
|
elog(ERROR, "ProcedureCreate: arg type '%s' is not defined",
|
2000-10-07 02:58:23 +02:00
|
|
|
typnam);
|
1997-09-07 07:04:48 +02:00
|
|
|
if (!defined)
|
|
|
|
elog(NOTICE, "ProcedureCreate: arg type '%s' is only a shell",
|
2000-10-07 02:58:23 +02:00
|
|
|
typnam);
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
|
2000-10-07 02:58:23 +02:00
|
|
|
if (t->setof)
|
|
|
|
elog(ERROR, "ProcedureCreate: functions cannot accept set arguments");
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
typev[parameterCount++] = toid;
|
|
|
|
}
|
|
|
|
|
1999-11-22 18:56:41 +01:00
|
|
|
tup = SearchSysCacheTuple(PROCNAME,
|
1997-09-07 07:04:48 +02:00
|
|
|
PointerGetDatum(procedureName),
|
|
|
|
UInt16GetDatum(parameterCount),
|
|
|
|
PointerGetDatum(typev),
|
|
|
|
0);
|
|
|
|
|
|
|
|
if (HeapTupleIsValid(tup))
|
1998-01-06 20:42:33 +01:00
|
|
|
elog(ERROR, "ProcedureCreate: procedure %s already exists with same arguments",
|
1997-09-07 07:04:48 +02:00
|
|
|
procedureName);
|
|
|
|
|
2000-05-28 19:56:29 +02:00
|
|
|
if (languageObjectId == SQLlanguageId)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/*
|
|
|
|
* If this call is defining a set, check if the set is already
|
|
|
|
* defined by looking to see whether this call's function text
|
|
|
|
* matches a function already in pg_proc. If so just return the
|
|
|
|
* OID of the existing set.
|
|
|
|
*/
|
2000-05-28 19:56:29 +02:00
|
|
|
if (strcmp(procedureName, GENERICSETNAME) == 0)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
1999-09-30 12:31:47 +02:00
|
|
|
#ifdef SETS_FIXED
|
|
|
|
/* ----------
|
|
|
|
* The code below doesn't work any more because the
|
|
|
|
* PROSRC system cache and the pg_proc_prosrc_index
|
|
|
|
* have been removed. Instead a sequential heap scan
|
|
|
|
* or something better must get implemented. The reason
|
|
|
|
* for removing is that nbtree index crashes if sources
|
2000-05-28 19:56:29 +02:00
|
|
|
* exceed 2K --- what's likely for procedural languages.
|
1999-09-30 12:31:47 +02:00
|
|
|
*
|
|
|
|
* 1999/09/30 Jan
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
text *prosrctext;
|
|
|
|
|
2000-07-06 01:12:09 +02:00
|
|
|
prosrctext = DatumGetTextP(DirectFunctionCall1(textin,
|
|
|
|
CStringGetDatum(prosrc)));
|
1997-09-07 07:04:48 +02:00
|
|
|
tup = SearchSysCacheTuple(PROSRC,
|
|
|
|
PointerGetDatum(prosrctext),
|
|
|
|
0, 0, 0);
|
1998-08-31 19:49:18 +02:00
|
|
|
pfree(prosrctext);
|
1997-09-07 07:04:48 +02:00
|
|
|
if (HeapTupleIsValid(tup))
|
1998-11-27 20:52:36 +01:00
|
|
|
return tup->t_data->t_oid;
|
1999-09-30 12:31:47 +02:00
|
|
|
#else
|
|
|
|
elog(ERROR, "lookup for procedure by source needs fix (Jan)");
|
2000-04-12 19:17:23 +02:00
|
|
|
#endif /* SETS_FIXED */
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (strcmp(returnTypeName, "opaque") == 0)
|
|
|
|
{
|
2000-05-28 19:56:29 +02:00
|
|
|
if (languageObjectId == SQLlanguageId)
|
1998-01-06 20:42:33 +01:00
|
|
|
elog(ERROR, "ProcedureCreate: sql functions cannot return type \"opaque\"");
|
2000-10-07 02:58:23 +02:00
|
|
|
typeObjectId = InvalidOid;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
typeObjectId = TypeGet(returnTypeName, &defined);
|
|
|
|
|
|
|
|
if (!OidIsValid(typeObjectId))
|
|
|
|
{
|
|
|
|
elog(NOTICE, "ProcedureCreate: type '%s' is not yet defined",
|
|
|
|
returnTypeName);
|
|
|
|
typeObjectId = TypeShellMake(returnTypeName);
|
|
|
|
if (!OidIsValid(typeObjectId))
|
1998-01-06 20:42:33 +01:00
|
|
|
elog(ERROR, "ProcedureCreate: could not create type '%s'",
|
1997-09-07 07:04:48 +02:00
|
|
|
returnTypeName);
|
|
|
|
}
|
|
|
|
else if (!defined)
|
|
|
|
elog(NOTICE, "ProcedureCreate: return type '%s' is only a shell",
|
|
|
|
returnTypeName);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* don't allow functions of complex types that have the same name as
|
|
|
|
* existing attributes of the type
|
|
|
|
*/
|
|
|
|
if (parameterCount == 1 &&
|
|
|
|
(toid = TypeGet(strVal(lfirst(argList)), &defined)) &&
|
|
|
|
defined &&
|
1997-11-25 23:07:18 +01:00
|
|
|
(relid = typeidTypeRelid(toid)) != 0 &&
|
1997-09-07 07:04:48 +02:00
|
|
|
get_attnum(relid, procedureName) != InvalidAttrNumber)
|
1998-01-06 20:42:33 +01:00
|
|
|
elog(ERROR, "method %s already an attribute of type %s",
|
1997-09-07 07:04:48 +02:00
|
|
|
procedureName, strVal(lfirst(argList)));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If this is a postquel procedure, we parse it here in order to be
|
|
|
|
* sure that it contains no syntax errors. We should store the plan
|
|
|
|
* in an Inversion file for use later, but for now, we just store the
|
|
|
|
* procedure's text in the prosrc attribute.
|
|
|
|
*/
|
|
|
|
|
2000-05-28 19:56:29 +02:00
|
|
|
if (languageObjectId == SQLlanguageId)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2000-06-28 05:33:33 +02:00
|
|
|
querytree_list = pg_parse_and_rewrite(prosrc, typev, parameterCount);
|
1997-09-07 07:04:48 +02:00
|
|
|
/* typecheck return value */
|
2000-08-21 22:55:31 +02:00
|
|
|
checkretval(typeObjectId, querytree_list);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1999-04-18 04:57:22 +02:00
|
|
|
/*
|
|
|
|
* If this is an internal procedure, check that the given internal
|
|
|
|
* function name (the 'prosrc' value) is a known builtin function.
|
|
|
|
*
|
|
|
|
* NOTE: in Postgres versions before 6.5, the SQL name of the created
|
1999-05-25 18:15:34 +02:00
|
|
|
* function could not be different from the internal name, and
|
|
|
|
* 'prosrc' wasn't used. So there is code out there that does CREATE
|
|
|
|
* FUNCTION xyz AS '' LANGUAGE 'internal'. To preserve some modicum
|
|
|
|
* of backwards compatibility, accept an empty 'prosrc' value as
|
|
|
|
* meaning the supplied SQL function name.
|
2000-05-28 19:56:29 +02:00
|
|
|
*
|
|
|
|
* XXX: we could treat "internal" and "newinternal" language specs
|
|
|
|
* as equivalent, and take the actual language ID from the table of
|
|
|
|
* known builtin functions. Is that a better idea than making the
|
|
|
|
* user specify the right thing? Not sure.
|
1999-04-18 04:57:22 +02:00
|
|
|
*/
|
|
|
|
|
2000-05-28 19:56:29 +02:00
|
|
|
if (languageObjectId == INTERNALlanguageId ||
|
|
|
|
languageObjectId == NEWINTERNALlanguageId)
|
1999-04-18 04:57:22 +02:00
|
|
|
{
|
2000-05-28 19:56:29 +02:00
|
|
|
Oid actualLangID;
|
|
|
|
|
1999-04-18 04:57:22 +02:00
|
|
|
if (strlen(prosrc) == 0)
|
|
|
|
prosrc = procedureName;
|
2000-05-28 19:56:29 +02:00
|
|
|
actualLangID = fmgr_internal_language(prosrc);
|
|
|
|
if (actualLangID == InvalidOid)
|
1999-04-18 04:57:22 +02:00
|
|
|
elog(ERROR,
|
2000-05-28 19:56:29 +02:00
|
|
|
"ProcedureCreate: there is no builtin function named \"%s\"",
|
1999-04-18 04:57:22 +02:00
|
|
|
prosrc);
|
2000-05-28 19:56:29 +02:00
|
|
|
if (actualLangID != languageObjectId)
|
|
|
|
elog(ERROR,
|
|
|
|
"ProcedureCreate: \"%s\" is not %s internal function",
|
|
|
|
prosrc,
|
|
|
|
((languageObjectId == INTERNALlanguageId) ?
|
|
|
|
"an old-style" : "a new-style"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If this is a dynamically loadable procedure, make sure that the
|
|
|
|
* library file exists, is loadable, and contains the specified link
|
|
|
|
* symbol.
|
|
|
|
*
|
|
|
|
* We used to perform these checks only when the function was first
|
|
|
|
* called, but it seems friendlier to verify the library's validity
|
|
|
|
* at CREATE FUNCTION time.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (languageObjectId == ClanguageId ||
|
|
|
|
languageObjectId == NEWClanguageId)
|
|
|
|
{
|
|
|
|
/* If link symbol is specified as "-", substitute procedure name */
|
|
|
|
if (strcmp(prosrc, "-") == 0)
|
|
|
|
prosrc = procedureName;
|
|
|
|
(void) load_external_function(probin, prosrc);
|
1999-04-18 04:57:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* All seems OK; prepare the tuple to be inserted into pg_proc.
|
|
|
|
*/
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
for (i = 0; i < Natts_pg_proc; ++i)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
nulls[i] = ' ';
|
|
|
|
values[i] = (Datum) NULL;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
i = 0;
|
1998-04-01 17:35:33 +02:00
|
|
|
namestrcpy(&procname, procedureName);
|
|
|
|
values[i++] = NameGetDatum(&procname);
|
1997-09-07 07:04:48 +02:00
|
|
|
values[i++] = Int32GetDatum(GetUserId());
|
|
|
|
values[i++] = ObjectIdGetDatum(languageObjectId);
|
|
|
|
/* XXX isinherited is always false for now */
|
2000-08-21 19:22:36 +02:00
|
|
|
values[i++] = BoolGetDatum(false);
|
|
|
|
values[i++] = BoolGetDatum(trusted);
|
|
|
|
values[i++] = BoolGetDatum(canCache);
|
|
|
|
values[i++] = BoolGetDatum(isStrict);
|
1997-09-07 07:04:48 +02:00
|
|
|
values[i++] = UInt16GetDatum(parameterCount);
|
2000-08-21 19:22:36 +02:00
|
|
|
values[i++] = BoolGetDatum(returnsSet);
|
1997-09-07 07:04:48 +02:00
|
|
|
values[i++] = ObjectIdGetDatum(typeObjectId);
|
2000-08-21 19:22:36 +02:00
|
|
|
values[i++] = PointerGetDatum(typev);
|
1997-09-07 07:04:48 +02:00
|
|
|
values[i++] = Int32GetDatum(byte_pct); /* probyte_pct */
|
|
|
|
values[i++] = Int32GetDatum(perbyte_cpu); /* properbyte_cpu */
|
|
|
|
values[i++] = Int32GetDatum(percall_cpu); /* propercall_cpu */
|
|
|
|
values[i++] = Int32GetDatum(outin_ratio); /* prooutin_ratio */
|
2000-07-06 01:12:09 +02:00
|
|
|
values[i++] = DirectFunctionCall1(textin, /* prosrc */
|
|
|
|
CStringGetDatum(prosrc));
|
|
|
|
values[i++] = DirectFunctionCall1(textin, /* probin */
|
|
|
|
CStringGetDatum(probin));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1999-09-18 21:08:25 +02:00
|
|
|
rel = heap_openr(ProcedureRelationName, RowExclusiveLock);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1998-08-19 04:04:17 +02:00
|
|
|
tupDesc = rel->rd_att;
|
1997-09-07 07:04:48 +02:00
|
|
|
tup = heap_formtuple(tupDesc,
|
|
|
|
values,
|
|
|
|
nulls);
|
|
|
|
|
1998-08-19 04:04:17 +02:00
|
|
|
heap_insert(rel, tup);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1998-09-01 05:29:17 +02:00
|
|
|
if (RelationGetForm(rel)->relhasindex)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
Relation idescs[Num_pg_proc_indices];
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
CatalogOpenIndices(Num_pg_proc_indices, Name_pg_proc_indices, idescs);
|
1998-08-19 04:04:17 +02:00
|
|
|
CatalogIndexInsert(idescs, Num_pg_proc_indices, rel, tup);
|
1997-09-07 07:04:48 +02:00
|
|
|
CatalogCloseIndices(Num_pg_proc_indices, idescs);
|
|
|
|
}
|
1999-09-18 21:08:25 +02:00
|
|
|
heap_close(rel, RowExclusiveLock);
|
2000-06-14 06:53:44 +02:00
|
|
|
retval = tup->t_data->t_oid;
|
|
|
|
heap_freetuple(tup);
|
|
|
|
return retval;
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2000-08-21 22:55:31 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* checkretval() -- 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.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
checkretval(Oid rettype, List *queryTreeList)
|
|
|
|
{
|
|
|
|
Query *parse;
|
|
|
|
int cmd;
|
|
|
|
List *tlist;
|
|
|
|
List *tlistitem;
|
|
|
|
int tlistlen;
|
|
|
|
Type typ;
|
|
|
|
Resdom *resnode;
|
|
|
|
Relation reln;
|
|
|
|
Oid relid;
|
|
|
|
int relnatts;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* find the final query */
|
|
|
|
parse = (Query *) nth(length(queryTreeList) - 1, queryTreeList);
|
|
|
|
|
|
|
|
cmd = parse->commandType;
|
|
|
|
tlist = parse->targetList;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The last query must be a SELECT if and only if there is a return type.
|
|
|
|
*/
|
|
|
|
if (rettype == InvalidOid)
|
|
|
|
{
|
|
|
|
if (cmd == CMD_SELECT)
|
|
|
|
elog(ERROR, "function declared with no return type, but final query is a SELECT");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* by here, the function is declared to return some type */
|
|
|
|
if ((typ = typeidType(rettype)) == NULL)
|
|
|
|
elog(ERROR, "can't find return type %u for function", rettype);
|
|
|
|
|
|
|
|
if (cmd != CMD_SELECT)
|
|
|
|
elog(ERROR, "function declared to return %s, but final query is not a SELECT", typeTypeName(typ));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Count the non-junk entries in the result targetlist.
|
|
|
|
*/
|
|
|
|
tlistlen = ExecCleanTargetListLength(tlist);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For base-type returns, the target list should have exactly one entry,
|
|
|
|
* and its type should agree with what the user declared.
|
|
|
|
*/
|
|
|
|
if (typeTypeRelid(typ) == InvalidOid)
|
|
|
|
{
|
|
|
|
if (tlistlen != 1)
|
|
|
|
elog(ERROR, "function declared to return %s returns multiple columns in final SELECT", typeTypeName(typ));
|
|
|
|
|
|
|
|
resnode = (Resdom *) ((TargetEntry *) lfirst(tlist))->resdom;
|
|
|
|
if (resnode->restype != rettype)
|
|
|
|
elog(ERROR, "return type mismatch in function: declared to return %s, returns %s", typeTypeName(typ), typeidTypeName(resnode->restype));
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the target list is of length 1, and the type of the varnode in
|
|
|
|
* the target list is the same as the declared return type, this is
|
|
|
|
* okay. This can happen, for example, where the body of the function
|
|
|
|
* is 'SELECT (x = func2())', where func2 has the same return type
|
|
|
|
* as the function that's calling it.
|
|
|
|
*/
|
|
|
|
if (tlistlen == 1)
|
|
|
|
{
|
|
|
|
resnode = (Resdom *) ((TargetEntry *) lfirst(tlist))->resdom;
|
|
|
|
if (resnode->restype == rettype)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* By here, the procedure returns a tuple or set of tuples. This part of
|
|
|
|
* the typechecking is a hack. We look up the relation that is the
|
|
|
|
* declared return type, and be sure that attributes 1 .. n in the target
|
|
|
|
* list match the declared types.
|
|
|
|
*/
|
|
|
|
reln = heap_open(typeTypeRelid(typ), AccessShareLock);
|
|
|
|
relid = reln->rd_id;
|
|
|
|
relnatts = reln->rd_rel->relnatts;
|
|
|
|
|
|
|
|
if (tlistlen != relnatts)
|
|
|
|
elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)", typeTypeName(typ), relnatts);
|
|
|
|
|
|
|
|
/* expect attributes 1 .. n in order */
|
|
|
|
i = 0;
|
|
|
|
foreach(tlistitem, tlist)
|
|
|
|
{
|
|
|
|
TargetEntry *tle = (TargetEntry *) lfirst(tlistitem);
|
|
|
|
Oid tletype;
|
|
|
|
|
|
|
|
if (tle->resdom->resjunk)
|
|
|
|
continue;
|
|
|
|
tletype = exprType(tle->expr);
|
|
|
|
if (tletype != reln->rd_att->attrs[i]->atttypid)
|
|
|
|
elog(ERROR, "function declared to return %s returns %s instead of %s at column %d",
|
|
|
|
typeTypeName(typ),
|
|
|
|
typeidTypeName(tletype),
|
|
|
|
typeidTypeName(reln->rd_att->attrs[i]->atttypid),
|
|
|
|
i+1);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* this shouldn't happen, but let's just check... */
|
|
|
|
if (i != relnatts)
|
|
|
|
elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)", typeTypeName(typ), relnatts);
|
|
|
|
|
|
|
|
heap_close(reln, AccessShareLock);
|
|
|
|
}
|