2006-03-09 22:29:38 +01:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1998-08-22 14:38:39 +02:00
|
|
|
* pl_handler.c - Handler for the PL/pgSQL
|
|
|
|
* procedural language
|
|
|
|
*
|
2017-01-03 19:48:53 +01:00
|
|
|
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
|
2006-03-09 22:29:38 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1998-08-22 14:38:39 +02:00
|
|
|
*
|
|
|
|
*
|
2006-03-09 22:29:38 +01:00
|
|
|
* IDENTIFICATION
|
2010-09-20 22:08:53 +02:00
|
|
|
* src/pl/plpgsql/src/pl_handler.c
|
1998-08-22 14:38:39 +02:00
|
|
|
*
|
2006-03-09 22:29:38 +01:00
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
1998-08-22 14:38:39 +02:00
|
|
|
|
|
|
|
#include "plpgsql.h"
|
|
|
|
|
2012-08-30 22:15:44 +02:00
|
|
|
#include "access/htup_details.h"
|
1998-08-22 14:38:39 +02:00
|
|
|
#include "catalog/pg_proc.h"
|
|
|
|
#include "catalog/pg_type.h"
|
2005-12-28 19:11:25 +01:00
|
|
|
#include "funcapi.h"
|
2009-08-04 23:22:46 +02:00
|
|
|
#include "miscadmin.h"
|
2000-05-28 19:56:29 +02:00
|
|
|
#include "utils/builtins.h"
|
2011-09-04 07:13:16 +02:00
|
|
|
#include "utils/guc.h"
|
2004-03-19 19:58:07 +01:00
|
|
|
#include "utils/lsyscache.h"
|
2000-05-28 19:56:29 +02:00
|
|
|
#include "utils/syscache.h"
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2014-04-06 18:21:51 +02:00
|
|
|
|
|
|
|
static bool plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source);
|
|
|
|
static void plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra);
|
|
|
|
static void plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra);
|
|
|
|
|
2006-05-31 00:12:16 +02:00
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
|
2009-11-13 23:43:42 +01:00
|
|
|
/* Custom GUC variable */
|
|
|
|
static const struct config_enum_entry variable_conflict_options[] = {
|
|
|
|
{"error", PLPGSQL_RESOLVE_ERROR, false},
|
|
|
|
{"use_variable", PLPGSQL_RESOLVE_VARIABLE, false},
|
|
|
|
{"use_column", PLPGSQL_RESOLVE_COLUMN, false},
|
|
|
|
{NULL, 0, false}
|
|
|
|
};
|
|
|
|
|
2010-02-26 03:01:40 +01:00
|
|
|
int plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
|
2009-11-13 23:43:42 +01:00
|
|
|
|
2013-10-07 21:38:49 +02:00
|
|
|
bool plpgsql_print_strict_params = false;
|
|
|
|
|
2015-03-26 00:05:20 +01:00
|
|
|
bool plpgsql_check_asserts = true;
|
|
|
|
|
2014-05-06 18:12:18 +02:00
|
|
|
char *plpgsql_extra_warnings_string = NULL;
|
|
|
|
char *plpgsql_extra_errors_string = NULL;
|
2014-04-06 18:21:51 +02:00
|
|
|
int plpgsql_extra_warnings;
|
|
|
|
int plpgsql_extra_errors;
|
|
|
|
|
2009-11-13 23:43:42 +01:00
|
|
|
/* Hook for plugins */
|
2016-03-03 10:45:59 +01:00
|
|
|
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
|
2006-08-15 21:01:17 +02:00
|
|
|
|
2003-07-31 20:36:46 +02:00
|
|
|
|
2014-04-06 18:21:51 +02:00
|
|
|
static bool
|
|
|
|
plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
|
|
|
|
{
|
|
|
|
char *rawstring;
|
|
|
|
List *elemlist;
|
|
|
|
ListCell *l;
|
2014-05-06 18:12:18 +02:00
|
|
|
int extrachecks = 0;
|
2014-04-06 18:21:51 +02:00
|
|
|
int *myextra;
|
|
|
|
|
|
|
|
if (pg_strcasecmp(*newvalue, "all") == 0)
|
|
|
|
extrachecks = PLPGSQL_XCHECK_ALL;
|
|
|
|
else if (pg_strcasecmp(*newvalue, "none") == 0)
|
|
|
|
extrachecks = PLPGSQL_XCHECK_NONE;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Need a modifiable copy of string */
|
|
|
|
rawstring = pstrdup(*newvalue);
|
|
|
|
|
|
|
|
/* Parse string into list of identifiers */
|
|
|
|
if (!SplitIdentifierString(rawstring, ',', &elemlist))
|
|
|
|
{
|
|
|
|
/* syntax error in list */
|
|
|
|
GUC_check_errdetail("List syntax is invalid.");
|
|
|
|
pfree(rawstring);
|
|
|
|
list_free(elemlist);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach(l, elemlist)
|
|
|
|
{
|
|
|
|
char *tok = (char *) lfirst(l);
|
|
|
|
|
|
|
|
if (pg_strcasecmp(tok, "shadowed_variables") == 0)
|
|
|
|
extrachecks |= PLPGSQL_XCHECK_SHADOWVAR;
|
|
|
|
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
|
|
|
|
{
|
|
|
|
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
|
|
|
|
pfree(rawstring);
|
|
|
|
list_free(elemlist);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
|
|
|
|
pfree(rawstring);
|
|
|
|
list_free(elemlist);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pfree(rawstring);
|
|
|
|
list_free(elemlist);
|
|
|
|
}
|
|
|
|
|
|
|
|
myextra = (int *) malloc(sizeof(int));
|
2016-06-20 21:36:54 +02:00
|
|
|
if (!myextra)
|
|
|
|
return false;
|
2014-04-06 18:21:51 +02:00
|
|
|
*myextra = extrachecks;
|
|
|
|
*extra = (void *) myextra;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra)
|
|
|
|
{
|
|
|
|
plpgsql_extra_warnings = *((int *) extra);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra)
|
|
|
|
{
|
|
|
|
plpgsql_extra_errors = *((int *) extra);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-07-31 20:36:46 +02:00
|
|
|
/*
|
2006-08-08 21:15:09 +02:00
|
|
|
* _PG_init() - library load-time initialization
|
2003-07-31 20:36:46 +02:00
|
|
|
*
|
2006-08-08 21:15:09 +02:00
|
|
|
* DO NOT make this static nor change its name!
|
2003-07-31 20:36:46 +02:00
|
|
|
*/
|
|
|
|
void
|
2006-08-08 21:15:09 +02:00
|
|
|
_PG_init(void)
|
2003-07-31 20:36:46 +02:00
|
|
|
{
|
2006-08-08 21:15:09 +02:00
|
|
|
/* Be sure we do initialization only once (should be redundant now) */
|
|
|
|
static bool inited = false;
|
|
|
|
|
|
|
|
if (inited)
|
2003-07-31 20:36:46 +02:00
|
|
|
return;
|
|
|
|
|
2008-12-11 08:34:09 +01:00
|
|
|
pg_bindtextdomain(TEXTDOMAIN);
|
2008-10-09 19:24:05 +02:00
|
|
|
|
2009-11-13 23:43:42 +01:00
|
|
|
DefineCustomEnumVariable("plpgsql.variable_conflict",
|
|
|
|
gettext_noop("Sets handling of conflicts between PL/pgSQL variable names and table column names."),
|
|
|
|
NULL,
|
|
|
|
&plpgsql_variable_conflict,
|
|
|
|
PLPGSQL_RESOLVE_ERROR,
|
|
|
|
variable_conflict_options,
|
|
|
|
PGC_SUSET, 0,
|
2011-04-07 06:11:01 +02:00
|
|
|
NULL, NULL, NULL);
|
2009-11-13 23:43:42 +01:00
|
|
|
|
2013-10-07 21:38:49 +02:00
|
|
|
DefineCustomBoolVariable("plpgsql.print_strict_params",
|
2014-08-09 06:07:00 +02:00
|
|
|
gettext_noop("Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."),
|
2013-10-07 21:38:49 +02:00
|
|
|
NULL,
|
|
|
|
&plpgsql_print_strict_params,
|
|
|
|
false,
|
|
|
|
PGC_USERSET, 0,
|
|
|
|
NULL, NULL, NULL);
|
|
|
|
|
2015-03-26 00:05:20 +01:00
|
|
|
DefineCustomBoolVariable("plpgsql.check_asserts",
|
|
|
|
gettext_noop("Perform checks given in ASSERT statements."),
|
|
|
|
NULL,
|
|
|
|
&plpgsql_check_asserts,
|
|
|
|
true,
|
|
|
|
PGC_USERSET, 0,
|
|
|
|
NULL, NULL, NULL);
|
|
|
|
|
2014-04-06 18:21:51 +02:00
|
|
|
DefineCustomStringVariable("plpgsql.extra_warnings",
|
2014-08-09 06:07:00 +02:00
|
|
|
gettext_noop("List of programming constructs that should produce a warning."),
|
2014-04-06 18:21:51 +02:00
|
|
|
NULL,
|
|
|
|
&plpgsql_extra_warnings_string,
|
|
|
|
"none",
|
|
|
|
PGC_USERSET, GUC_LIST_INPUT,
|
|
|
|
plpgsql_extra_checks_check_hook,
|
|
|
|
plpgsql_extra_warnings_assign_hook,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
DefineCustomStringVariable("plpgsql.extra_errors",
|
2014-08-09 06:07:00 +02:00
|
|
|
gettext_noop("List of programming constructs that should produce an error."),
|
2014-04-06 18:21:51 +02:00
|
|
|
NULL,
|
|
|
|
&plpgsql_extra_errors_string,
|
|
|
|
"none",
|
|
|
|
PGC_USERSET, GUC_LIST_INPUT,
|
|
|
|
plpgsql_extra_checks_check_hook,
|
|
|
|
plpgsql_extra_errors_assign_hook,
|
|
|
|
NULL);
|
|
|
|
|
2009-11-13 23:43:42 +01:00
|
|
|
EmitWarningsOnPlaceholders("plpgsql");
|
|
|
|
|
2003-07-31 20:36:46 +02:00
|
|
|
plpgsql_HashTableInit();
|
2004-08-01 19:32:22 +02:00
|
|
|
RegisterXactCallback(plpgsql_xact_cb, NULL);
|
2007-01-28 17:15:49 +01:00
|
|
|
RegisterSubXactCallback(plpgsql_subxact_cb, NULL);
|
2003-07-31 20:36:46 +02:00
|
|
|
|
2006-08-15 21:01:17 +02:00
|
|
|
/* Set up a rendezvous point with optional instrumentation plugin */
|
2016-03-03 10:45:59 +01:00
|
|
|
plpgsql_plugin_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
|
2006-08-15 21:01:17 +02:00
|
|
|
|
2006-08-08 21:15:09 +02:00
|
|
|
inited = true;
|
2003-07-31 20:36:46 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
|
|
|
/* ----------
|
2000-05-28 19:56:29 +02:00
|
|
|
* plpgsql_call_handler
|
|
|
|
*
|
|
|
|
* The PostgreSQL function manager and trigger manager
|
|
|
|
* call this function for execution of PL/pgSQL procedures.
|
1998-08-22 14:38:39 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-11-20 21:36:57 +01:00
|
|
|
PG_FUNCTION_INFO_V1(plpgsql_call_handler);
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
Datum
|
2000-05-28 19:56:29 +02:00
|
|
|
plpgsql_call_handler(PG_FUNCTION_ARGS)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
2000-05-28 19:56:29 +02:00
|
|
|
PLpgSQL_function *func;
|
2009-11-04 23:26:08 +01:00
|
|
|
PLpgSQL_execstate *save_cur_estate;
|
2012-07-22 01:39:03 +02:00
|
|
|
Datum retval;
|
2004-07-31 22:55:45 +02:00
|
|
|
int rc;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-05-28 19:56:29 +02:00
|
|
|
* Connect to SPI manager
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2004-07-31 22:55:45 +02:00
|
|
|
if ((rc = SPI_connect()) != SPI_OK_CONNECT)
|
|
|
|
elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2003-07-01 23:47:09 +02:00
|
|
|
/* Find or compile the function */
|
2004-03-19 19:58:07 +01:00
|
|
|
func = plpgsql_compile(fcinfo, false);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2009-11-04 23:26:08 +01:00
|
|
|
/* Must save and restore prior value of cur_estate */
|
|
|
|
save_cur_estate = func->cur_estate;
|
|
|
|
|
2007-01-30 23:05:13 +01:00
|
|
|
/* Mark the function as busy, so it can't be deleted from under us */
|
|
|
|
func->use_count++;
|
|
|
|
|
|
|
|
PG_TRY();
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Determine if called as function or trigger and call appropriate
|
|
|
|
* subhandler
|
|
|
|
*/
|
|
|
|
if (CALLED_AS_TRIGGER(fcinfo))
|
|
|
|
retval = PointerGetDatum(plpgsql_exec_trigger(func,
|
2005-10-15 04:49:52 +02:00
|
|
|
(TriggerData *) fcinfo->context));
|
2012-07-20 17:38:47 +02:00
|
|
|
else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
|
2012-07-22 01:39:03 +02:00
|
|
|
{
|
2012-07-20 17:38:47 +02:00
|
|
|
plpgsql_exec_event_trigger(func,
|
|
|
|
(EventTriggerData *) fcinfo->context);
|
2012-07-22 01:39:03 +02:00
|
|
|
retval = (Datum) 0;
|
|
|
|
}
|
2007-01-30 23:05:13 +01:00
|
|
|
else
|
Prevent leakage of cached plans and execution trees in plpgsql DO blocks.
plpgsql likes to cache query plans and simple-expression execution state
trees across calls. This is a considerable win for multiple executions
of the same function. However, it's useless for DO blocks, since by
definition those are executed only once and discarded. Nonetheless,
we were allowing a DO block's expression execution trees to survive
until end of transaction, resulting in a significant intra-transaction
memory leak, as reported by Yeb Havinga. Worse, if the DO block exited
with an error, the compiled form of the block's code was leaked till
end of session --- along with subsidiary plancache entries.
To fix, make DO blocks keep their expression execution trees in a private
EState that's deleted at exit from the block, and add a PG_TRY block
to plpgsql_inline_handler to make sure that memory cleanup happens
even on error exits. Also add a regression test covering error handling
in a DO block, because my first try at this broke that. (The test is
not meant to prove that we don't leak memory anymore, though it could
be used for that with a much larger loop count.)
Ideally we'd back-patch this into all versions supporting DO blocks;
but the patch needs to add a field to struct PLpgSQL_execstate, and that
would break ABI compatibility for third-party plugins such as the plpgsql
debugger. Given the small number of complaints so far, fixing this in
HEAD only seems like an acceptable choice.
2013-11-15 19:52:03 +01:00
|
|
|
retval = plpgsql_exec_function(func, fcinfo, NULL);
|
2007-01-30 23:05:13 +01:00
|
|
|
}
|
|
|
|
PG_CATCH();
|
|
|
|
{
|
2009-11-04 23:26:08 +01:00
|
|
|
/* Decrement use-count, restore cur_estate, and propagate error */
|
2007-01-30 23:05:13 +01:00
|
|
|
func->use_count--;
|
2009-11-04 23:26:08 +01:00
|
|
|
func->cur_estate = save_cur_estate;
|
2007-01-30 23:05:13 +01:00
|
|
|
PG_RE_THROW();
|
|
|
|
}
|
|
|
|
PG_END_TRY();
|
|
|
|
|
|
|
|
func->use_count--;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2009-11-04 23:26:08 +01:00
|
|
|
func->cur_estate = save_cur_estate;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-05-28 19:56:29 +02:00
|
|
|
* Disconnect from SPI manager
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2004-07-31 22:55:45 +02:00
|
|
|
if ((rc = SPI_finish()) != SPI_OK_FINISH)
|
|
|
|
elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2000-05-28 19:56:29 +02:00
|
|
|
return retval;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
2004-03-19 19:58:07 +01:00
|
|
|
|
2009-09-23 01:43:43 +02:00
|
|
|
/* ----------
|
|
|
|
* plpgsql_inline_handler
|
|
|
|
*
|
|
|
|
* Called by PostgreSQL to execute an anonymous code block
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
PG_FUNCTION_INFO_V1(plpgsql_inline_handler);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
plpgsql_inline_handler(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
|
|
|
|
PLpgSQL_function *func;
|
|
|
|
FunctionCallInfoData fake_fcinfo;
|
|
|
|
FmgrInfo flinfo;
|
Prevent leakage of cached plans and execution trees in plpgsql DO blocks.
plpgsql likes to cache query plans and simple-expression execution state
trees across calls. This is a considerable win for multiple executions
of the same function. However, it's useless for DO blocks, since by
definition those are executed only once and discarded. Nonetheless,
we were allowing a DO block's expression execution trees to survive
until end of transaction, resulting in a significant intra-transaction
memory leak, as reported by Yeb Havinga. Worse, if the DO block exited
with an error, the compiled form of the block's code was leaked till
end of session --- along with subsidiary plancache entries.
To fix, make DO blocks keep their expression execution trees in a private
EState that's deleted at exit from the block, and add a PG_TRY block
to plpgsql_inline_handler to make sure that memory cleanup happens
even on error exits. Also add a regression test covering error handling
in a DO block, because my first try at this broke that. (The test is
not meant to prove that we don't leak memory anymore, though it could
be used for that with a much larger loop count.)
Ideally we'd back-patch this into all versions supporting DO blocks;
but the patch needs to add a field to struct PLpgSQL_execstate, and that
would break ABI compatibility for third-party plugins such as the plpgsql
debugger. Given the small number of complaints so far, fixing this in
HEAD only seems like an acceptable choice.
2013-11-15 19:52:03 +01:00
|
|
|
EState *simple_eval_estate;
|
2009-09-23 01:43:43 +02:00
|
|
|
Datum retval;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
Assert(IsA(codeblock, InlineCodeBlock));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Connect to SPI manager
|
|
|
|
*/
|
|
|
|
if ((rc = SPI_connect()) != SPI_OK_CONNECT)
|
|
|
|
elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
|
|
|
|
|
|
|
|
/* Compile the anonymous code block */
|
|
|
|
func = plpgsql_compile_inline(codeblock->source_text);
|
|
|
|
|
2011-03-27 18:51:04 +02:00
|
|
|
/* Mark the function as busy, just pro forma */
|
|
|
|
func->use_count++;
|
|
|
|
|
2009-09-23 01:43:43 +02:00
|
|
|
/*
|
|
|
|
* Set up a fake fcinfo with just enough info to satisfy
|
|
|
|
* plpgsql_exec_function(). In particular note that this sets things up
|
|
|
|
* with no arguments passed.
|
|
|
|
*/
|
|
|
|
MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
|
|
|
|
MemSet(&flinfo, 0, sizeof(flinfo));
|
|
|
|
fake_fcinfo.flinfo = &flinfo;
|
|
|
|
flinfo.fn_oid = InvalidOid;
|
|
|
|
flinfo.fn_mcxt = CurrentMemoryContext;
|
|
|
|
|
Prevent leakage of cached plans and execution trees in plpgsql DO blocks.
plpgsql likes to cache query plans and simple-expression execution state
trees across calls. This is a considerable win for multiple executions
of the same function. However, it's useless for DO blocks, since by
definition those are executed only once and discarded. Nonetheless,
we were allowing a DO block's expression execution trees to survive
until end of transaction, resulting in a significant intra-transaction
memory leak, as reported by Yeb Havinga. Worse, if the DO block exited
with an error, the compiled form of the block's code was leaked till
end of session --- along with subsidiary plancache entries.
To fix, make DO blocks keep their expression execution trees in a private
EState that's deleted at exit from the block, and add a PG_TRY block
to plpgsql_inline_handler to make sure that memory cleanup happens
even on error exits. Also add a regression test covering error handling
in a DO block, because my first try at this broke that. (The test is
not meant to prove that we don't leak memory anymore, though it could
be used for that with a much larger loop count.)
Ideally we'd back-patch this into all versions supporting DO blocks;
but the patch needs to add a field to struct PLpgSQL_execstate, and that
would break ABI compatibility for third-party plugins such as the plpgsql
debugger. Given the small number of complaints so far, fixing this in
HEAD only seems like an acceptable choice.
2013-11-15 19:52:03 +01:00
|
|
|
/* Create a private EState for simple-expression execution */
|
|
|
|
simple_eval_estate = CreateExecutorState();
|
|
|
|
|
|
|
|
/* And run the function */
|
|
|
|
PG_TRY();
|
|
|
|
{
|
|
|
|
retval = plpgsql_exec_function(func, &fake_fcinfo, simple_eval_estate);
|
|
|
|
}
|
|
|
|
PG_CATCH();
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We need to clean up what would otherwise be long-lived resources
|
|
|
|
* accumulated by the failed DO block, principally cached plans for
|
|
|
|
* statements (which can be flushed with plpgsql_free_function_memory)
|
|
|
|
* and execution trees for simple expressions, which are in the
|
|
|
|
* private EState.
|
|
|
|
*
|
|
|
|
* Before releasing the private EState, we must clean up any
|
|
|
|
* simple_econtext_stack entries pointing into it, which we can do by
|
|
|
|
* invoking the subxact callback. (It will be called again later if
|
|
|
|
* some outer control level does a subtransaction abort, but no harm
|
|
|
|
* is done.) We cheat a bit knowing that plpgsql_subxact_cb does not
|
|
|
|
* pay attention to its parentSubid argument.
|
|
|
|
*/
|
|
|
|
plpgsql_subxact_cb(SUBXACT_EVENT_ABORT_SUB,
|
|
|
|
GetCurrentSubTransactionId(),
|
|
|
|
0, NULL);
|
|
|
|
|
|
|
|
/* Clean up the private EState */
|
|
|
|
FreeExecutorState(simple_eval_estate);
|
|
|
|
|
|
|
|
/* Function should now have no remaining use-counts ... */
|
|
|
|
func->use_count--;
|
|
|
|
Assert(func->use_count == 0);
|
|
|
|
|
|
|
|
/* ... so we can free subsidiary storage */
|
|
|
|
plpgsql_free_function_memory(func);
|
|
|
|
|
|
|
|
/* And propagate the error */
|
|
|
|
PG_RE_THROW();
|
|
|
|
}
|
|
|
|
PG_END_TRY();
|
|
|
|
|
|
|
|
/* Clean up the private EState */
|
|
|
|
FreeExecutorState(simple_eval_estate);
|
2009-09-23 01:43:43 +02:00
|
|
|
|
2011-03-27 18:51:04 +02:00
|
|
|
/* Function should now have no remaining use-counts ... */
|
|
|
|
func->use_count--;
|
|
|
|
Assert(func->use_count == 0);
|
|
|
|
|
|
|
|
/* ... so we can free subsidiary storage */
|
|
|
|
plpgsql_free_function_memory(func);
|
|
|
|
|
2009-09-23 01:43:43 +02:00
|
|
|
/*
|
|
|
|
* Disconnect from SPI manager
|
|
|
|
*/
|
|
|
|
if ((rc = SPI_finish()) != SPI_OK_FINISH)
|
|
|
|
elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2004-03-19 19:58:07 +01:00
|
|
|
/* ----------
|
|
|
|
* plpgsql_validator
|
|
|
|
*
|
|
|
|
* This function attempts to validate a PL/pgSQL function at
|
|
|
|
* CREATE FUNCTION time.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
PG_FUNCTION_INFO_V1(plpgsql_validator);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
plpgsql_validator(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
Oid funcoid = PG_GETARG_OID(0);
|
|
|
|
HeapTuple tuple;
|
|
|
|
Form_pg_proc proc;
|
|
|
|
char functyptype;
|
2005-12-28 19:11:25 +01:00
|
|
|
int numargs;
|
|
|
|
Oid *argtypes;
|
|
|
|
char **argnames;
|
|
|
|
char *argmodes;
|
2012-07-20 17:38:47 +02:00
|
|
|
bool is_dml_trigger = false;
|
|
|
|
bool is_event_trigger = false;
|
2004-03-19 19:58:07 +01:00
|
|
|
int i;
|
|
|
|
|
2014-02-17 15:33:31 +01:00
|
|
|
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
|
2004-03-19 19:58:07 +01:00
|
|
|
/* Get the new function's pg_proc entry */
|
2010-02-14 19:42:19 +01:00
|
|
|
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
|
2004-03-19 19:58:07 +01:00
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
|
|
elog(ERROR, "cache lookup failed for function %u", funcoid);
|
|
|
|
proc = (Form_pg_proc) GETSTRUCT(tuple);
|
|
|
|
|
|
|
|
functyptype = get_typtype(proc->prorettype);
|
|
|
|
|
|
|
|
/* Disallow pseudotype result */
|
2007-04-02 05:49:42 +02:00
|
|
|
/* except for TRIGGER, RECORD, VOID, or polymorphic */
|
|
|
|
if (functyptype == TYPTYPE_PSEUDO)
|
2004-03-19 19:58:07 +01:00
|
|
|
{
|
|
|
|
/* we assume OPAQUE with no arguments means a trigger */
|
|
|
|
if (proc->prorettype == TRIGGEROID ||
|
|
|
|
(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
|
2012-07-20 17:38:47 +02:00
|
|
|
is_dml_trigger = true;
|
|
|
|
else if (proc->prorettype == EVTTRIGGEROID)
|
|
|
|
is_event_trigger = true;
|
2004-03-19 19:58:07 +01:00
|
|
|
else if (proc->prorettype != RECORDOID &&
|
2005-12-28 19:11:25 +01:00
|
|
|
proc->prorettype != VOIDOID &&
|
2007-04-02 05:49:42 +02:00
|
|
|
!IsPolymorphicType(proc->prorettype))
|
2004-03-19 19:58:07 +01:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2009-02-18 12:33:04 +01:00
|
|
|
errmsg("PL/pgSQL functions cannot return type %s",
|
2004-03-19 19:58:07 +01:00
|
|
|
format_type_be(proc->prorettype))));
|
|
|
|
}
|
|
|
|
|
2005-12-28 19:11:25 +01:00
|
|
|
/* Disallow pseudotypes in arguments (either IN or OUT) */
|
2007-04-02 05:49:42 +02:00
|
|
|
/* except for polymorphic */
|
2005-12-28 19:11:25 +01:00
|
|
|
numargs = get_func_arg_info(tuple,
|
|
|
|
&argtypes, &argnames, &argmodes);
|
|
|
|
for (i = 0; i < numargs; i++)
|
2004-03-19 19:58:07 +01:00
|
|
|
{
|
2007-04-02 05:49:42 +02:00
|
|
|
if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO)
|
2004-03-19 19:58:07 +01:00
|
|
|
{
|
2007-04-02 05:49:42 +02:00
|
|
|
if (!IsPolymorphicType(argtypes[i]))
|
2004-03-19 19:58:07 +01:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2009-02-18 12:33:04 +01:00
|
|
|
errmsg("PL/pgSQL functions cannot accept type %s",
|
2006-10-04 02:30:14 +02:00
|
|
|
format_type_be(argtypes[i]))));
|
2004-03-19 19:58:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Postpone body checks if !check_function_bodies */
|
|
|
|
if (check_function_bodies)
|
|
|
|
{
|
|
|
|
FunctionCallInfoData fake_fcinfo;
|
|
|
|
FmgrInfo flinfo;
|
2004-07-31 22:55:45 +02:00
|
|
|
int rc;
|
2012-07-21 03:25:26 +02:00
|
|
|
TriggerData trigdata;
|
|
|
|
EventTriggerData etrigdata;
|
2004-03-19 19:58:07 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Connect to SPI manager (is this needed for compilation?)
|
|
|
|
*/
|
2004-07-31 22:55:45 +02:00
|
|
|
if ((rc = SPI_connect()) != SPI_OK_CONNECT)
|
|
|
|
elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
|
2004-03-19 19:58:07 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up a fake fcinfo with just enough info to satisfy
|
|
|
|
* plpgsql_compile().
|
|
|
|
*/
|
|
|
|
MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
|
|
|
|
MemSet(&flinfo, 0, sizeof(flinfo));
|
|
|
|
fake_fcinfo.flinfo = &flinfo;
|
|
|
|
flinfo.fn_oid = funcoid;
|
|
|
|
flinfo.fn_mcxt = CurrentMemoryContext;
|
2012-07-20 17:38:47 +02:00
|
|
|
if (is_dml_trigger)
|
2004-03-19 19:58:07 +01:00
|
|
|
{
|
|
|
|
MemSet(&trigdata, 0, sizeof(trigdata));
|
|
|
|
trigdata.type = T_TriggerData;
|
|
|
|
fake_fcinfo.context = (Node *) &trigdata;
|
|
|
|
}
|
2012-07-20 17:38:47 +02:00
|
|
|
else if (is_event_trigger)
|
|
|
|
{
|
2012-07-21 03:25:26 +02:00
|
|
|
MemSet(&etrigdata, 0, sizeof(etrigdata));
|
|
|
|
etrigdata.type = T_EventTriggerData;
|
|
|
|
fake_fcinfo.context = (Node *) &etrigdata;
|
2012-07-20 17:38:47 +02:00
|
|
|
}
|
2004-03-19 19:58:07 +01:00
|
|
|
|
|
|
|
/* Test-compile the function */
|
|
|
|
plpgsql_compile(&fake_fcinfo, true);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Disconnect from SPI manager
|
|
|
|
*/
|
2004-07-31 22:55:45 +02:00
|
|
|
if ((rc = SPI_finish()) != SPI_OK_FINISH)
|
|
|
|
elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
|
2004-03-19 19:58:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
}
|