]> granicus.if.org Git - postgresql/blobdiff - src/pl/plpgsql/src/pl_handler.c
Update copyright for 2014
[postgresql] / src / pl / plpgsql / src / pl_handler.c
index bacd287fdda04c5b3a248e408190e3e4ebc87813..f02203a5fb8eeed8a7de3d6f07a6be2ca5bdddf0 100644 (file)
@@ -1,70 +1,96 @@
-/**********************************************************************
+/*-------------------------------------------------------------------------
+ *
  * 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.
  * ----------
@@ -74,102 +100,287 @@ PG_FUNCTION_INFO_V1(plpgsql_call_handler);
 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();
 }