-/**********************************************************************
+/*-------------------------------------------------------------------------
+ *
* pl_handler.c - Handler for the PL/pgSQL
* procedural language
*
- * IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.9 2001/10/09 15:59:56 tgl Exp $
- *
- * This software is copyrighted by Jan Wieck - Hamburg.
- *
- * The author hereby grants permission to use, copy, modify,
- * distribute, and license this software and its documentation
- * for any purpose, provided that existing copyright notices are
- * retained in all copies and that this notice is included
- * verbatim in any distributions. No written agreement, license,
- * or royalty fee is required for any of the authorized uses.
- * Modifications to this software may be copyrighted by their
- * author and need not follow the licensing terms described
- * here, provided that the new terms are clearly indicated on
- * the first page of each file where they apply.
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
*
- * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
- * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
- * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
- * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
- * IF THE AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
*
- * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON
- * AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE NO
- * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
- * ENHANCEMENTS, OR MODIFICATIONS.
+ * IDENTIFICATION
+ * src/pl/plpgsql/src/pl_handler.c
*
- **********************************************************************/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
+ *-------------------------------------------------------------------------
+ */
#include "plpgsql.h"
-#include "pl.tab.h"
-#include "access/heapam.h"
+#include "access/htup_details.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "miscadmin.h"
#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
+PG_MODULE_MAGIC;
+
+/* 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}
+};
+
+int plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
+
+bool plpgsql_print_strict_params = false;
+
+/* Hook for plugins */
+PLpgSQL_plugin **plugin_ptr = NULL;
+
/*
- * Head of list of already-compiled functions
+ * _PG_init() - library load-time initialization
+ *
+ * DO NOT make this static nor change its name!
*/
-static PLpgSQL_function *compiled_functions = NULL;
+void
+_PG_init(void)
+{
+ /* Be sure we do initialization only once (should be redundant now) */
+ static bool inited = false;
+ if (inited)
+ return;
-static bool func_up_to_date(PLpgSQL_function *func);
+ pg_bindtextdomain(TEXTDOMAIN);
+ 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,
+ NULL, NULL, NULL);
+
+ DefineCustomBoolVariable("plpgsql.print_strict_params",
+ gettext_noop("Print information about parameters in the DETAIL part of the error messages generated on INTO .. STRICT failures."),
+ NULL,
+ &plpgsql_print_strict_params,
+ false,
+ PGC_USERSET, 0,
+ NULL, NULL, NULL);
+
+ EmitWarningsOnPlaceholders("plpgsql");
+
+ plpgsql_HashTableInit();
+ RegisterXactCallback(plpgsql_xact_cb, NULL);
+ RegisterSubXactCallback(plpgsql_subxact_cb, NULL);
+
+ /* Set up a rendezvous point with optional instrumentation plugin */
+ plugin_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
+
+ inited = true;
+}
/* ----------
* plpgsql_call_handler
*
- * This is the only visible function of the PL interpreter.
* The PostgreSQL function manager and trigger manager
* call this function for execution of PL/pgSQL procedures.
* ----------
Datum
plpgsql_call_handler(PG_FUNCTION_ARGS)
{
- bool isTrigger = CALLED_AS_TRIGGER(fcinfo);
- Oid funcOid = fcinfo->flinfo->fn_oid;
PLpgSQL_function *func;
+ PLpgSQL_execstate *save_cur_estate;
Datum retval;
+ int rc;
/*
* Connect to SPI manager
*/
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(ERROR, "plpgsql: cannot connect to SPI manager");
+ if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
- /*
- * Check if we already compiled this function and saved the pointer
- * (ie, current FmgrInfo has been used before)
- */
- func = (PLpgSQL_function *) fcinfo->flinfo->fn_extra;
- if (func != NULL)
- {
- Assert(func->fn_oid == funcOid);
- /*
- * But is the function still up to date?
- */
- if (! func_up_to_date(func))
- func = NULL;
- }
+ /* Find or compile the function */
+ func = plpgsql_compile(fcinfo, false);
+
+ /* Must save and restore prior value of cur_estate */
+ save_cur_estate = func->cur_estate;
- if (func == NULL)
+ /* Mark the function as busy, so it can't be deleted from under us */
+ func->use_count++;
+
+ PG_TRY();
{
/*
- * Check if we already compiled this function for another caller
+ * Determine if called as function or trigger and call appropriate
+ * subhandler
*/
- for (func = compiled_functions; func != NULL; func = func->next)
+ if (CALLED_AS_TRIGGER(fcinfo))
+ retval = PointerGetDatum(plpgsql_exec_trigger(func,
+ (TriggerData *) fcinfo->context));
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
{
- if (funcOid == func->fn_oid && func_up_to_date(func))
- break;
+ plpgsql_exec_event_trigger(func,
+ (EventTriggerData *) fcinfo->context);
+ retval = (Datum) 0;
}
+ else
+ retval = plpgsql_exec_function(func, fcinfo, NULL);
+ }
+ PG_CATCH();
+ {
+ /* Decrement use-count, restore cur_estate, and propagate error */
+ func->use_count--;
+ func->cur_estate = save_cur_estate;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
- /*
- * If not, do so and add it to the compiled ones
- */
- if (func == NULL)
- {
- func = plpgsql_compile(funcOid,
- isTrigger ? T_TRIGGER : T_FUNCTION);
- func->next = compiled_functions;
- compiled_functions = func;
- }
+ func->use_count--;
+
+ func->cur_estate = save_cur_estate;
+
+ /*
+ * Disconnect from SPI manager
+ */
+ if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+
+ return retval;
+}
+
+/* ----------
+ * 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;
+ EState *simple_eval_estate;
+ 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);
+
+ /* Mark the function as busy, just pro forma */
+ func->use_count++;
+
+ /*
+ * 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;
+
+ /* 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();
+ {
/*
- * Save pointer in FmgrInfo to avoid search on subsequent calls
+ * 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.
*/
- fcinfo->flinfo->fn_extra = (void *) func;
+ 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();
- /*
- * Determine if called as function or trigger and call appropriate
- * subhandler
- */
- if (isTrigger)
- retval = PointerGetDatum(plpgsql_exec_trigger(func,
- (TriggerData *) fcinfo->context));
- else
- retval = plpgsql_exec_function(func, fcinfo);
+ /* 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);
/*
* Disconnect from SPI manager
*/
- if (SPI_finish() != SPI_OK_FINISH)
- elog(ERROR, "plpgsql: SPI_finish() failed");
+ if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
return retval;
}
-
-/*
- * Check to see if a compiled function is still up-to-date. This
- * is needed because CREATE OR REPLACE FUNCTION can modify the
- * function's pg_proc entry without changing its OID.
+/* ----------
+ * plpgsql_validator
+ *
+ * This function attempts to validate a PL/pgSQL function at
+ * CREATE FUNCTION time.
+ * ----------
*/
-static bool
-func_up_to_date(PLpgSQL_function *func)
+PG_FUNCTION_INFO_V1(plpgsql_validator);
+
+Datum
+plpgsql_validator(PG_FUNCTION_ARGS)
{
- HeapTuple procTup;
- bool result;
+ Oid funcoid = PG_GETARG_OID(0);
+ HeapTuple tuple;
+ Form_pg_proc proc;
+ char functyptype;
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ bool is_dml_trigger = false;
+ bool is_event_trigger = false;
+ int i;
+
+ /* Get the new function's pg_proc entry */
+ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 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 */
+ /* except for TRIGGER, RECORD, VOID, or polymorphic */
+ if (functyptype == TYPTYPE_PSEUDO)
+ {
+ /* we assume OPAQUE with no arguments means a trigger */
+ if (proc->prorettype == TRIGGEROID ||
+ (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
+ is_dml_trigger = true;
+ else if (proc->prorettype == EVTTRIGGEROID)
+ is_event_trigger = true;
+ else if (proc->prorettype != RECORDOID &&
+ proc->prorettype != VOIDOID &&
+ !IsPolymorphicType(proc->prorettype))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/pgSQL functions cannot return type %s",
+ format_type_be(proc->prorettype))));
+ }
+
+ /* Disallow pseudotypes in arguments (either IN or OUT) */
+ /* except for polymorphic */
+ numargs = get_func_arg_info(tuple,
+ &argtypes, &argnames, &argmodes);
+ for (i = 0; i < numargs; i++)
+ {
+ if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO)
+ {
+ if (!IsPolymorphicType(argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/pgSQL functions cannot accept type %s",
+ format_type_be(argtypes[i]))));
+ }
+ }
+
+ /* Postpone body checks if !check_function_bodies */
+ if (check_function_bodies)
+ {
+ FunctionCallInfoData fake_fcinfo;
+ FmgrInfo flinfo;
+ int rc;
+ TriggerData trigdata;
+ EventTriggerData etrigdata;
- procTup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(func->fn_oid),
- 0, 0, 0);
- if (!HeapTupleIsValid(procTup))
- elog(ERROR, "plpgsql: cache lookup for proc %u failed",
- func->fn_oid);
+ /*
+ * Connect to SPI manager (is this needed for compilation?)
+ */
+ if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
- result = (func->fn_xmin == procTup->t_data->t_xmin &&
- func->fn_cmin == procTup->t_data->t_cmin);
+ /*
+ * 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;
+ if (is_dml_trigger)
+ {
+ MemSet(&trigdata, 0, sizeof(trigdata));
+ trigdata.type = T_TriggerData;
+ fake_fcinfo.context = (Node *) &trigdata;
+ }
+ else if (is_event_trigger)
+ {
+ MemSet(&etrigdata, 0, sizeof(etrigdata));
+ etrigdata.type = T_EventTriggerData;
+ fake_fcinfo.context = (Node *) &etrigdata;
+ }
+
+ /* Test-compile the function */
+ plpgsql_compile(&fake_fcinfo, true);
+
+ /*
+ * Disconnect from SPI manager
+ */
+ if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ }
- ReleaseSysCache(procTup);
+ ReleaseSysCache(tuple);
- return result;
+ PG_RETURN_VOID();
}