summaryrefslogtreecommitdiffstats
path: root/src/pl/plpython/plpy_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pl/plpython/plpy_main.c')
-rw-r--r--src/pl/plpython/plpy_main.c419
1 files changed, 419 insertions, 0 deletions
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
new file mode 100644
index 0000000..010a973
--- /dev/null
+++ b/src/pl/plpython/plpy_main.c
@@ -0,0 +1,419 @@
+/*
+ * PL/Python main entry points
+ *
+ * src/pl/plpython/plpy_main.c
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "plpy_elog.h"
+#include "plpy_exec.h"
+#include "plpy_main.h"
+#include "plpy_plpymodule.h"
+#include "plpy_procedure.h"
+#include "plpy_subxactobject.h"
+#include "plpython.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * exported functions
+ */
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(plpython3_validator);
+PG_FUNCTION_INFO_V1(plpython3_call_handler);
+PG_FUNCTION_INFO_V1(plpython3_inline_handler);
+
+
+static bool PLy_procedure_is_trigger(Form_pg_proc procStruct);
+static void plpython_error_callback(void *arg);
+static void plpython_inline_error_callback(void *arg);
+static void PLy_init_interp(void);
+
+static PLyExecutionContext *PLy_push_execution_context(bool atomic_context);
+static void PLy_pop_execution_context(void);
+
+/* static state for Python library conflict detection */
+static int *plpython_version_bitmask_ptr = NULL;
+static int plpython_version_bitmask = 0;
+
+/* initialize global variables */
+PyObject *PLy_interp_globals = NULL;
+
+/* this doesn't need to be global; use PLy_current_execution_context() */
+static PLyExecutionContext *PLy_execution_contexts = NULL;
+
+
+void
+_PG_init(void)
+{
+ int **bitmask_ptr;
+
+ /*
+ * Set up a shared bitmask variable telling which Python version(s) are
+ * loaded into this process's address space. If there's more than one, we
+ * cannot call into libpython for fear of causing crashes. But postpone
+ * the actual failure for later, so that operations like pg_restore can
+ * load more than one plpython library so long as they don't try to do
+ * anything much with the language.
+ *
+ * While we only support Python 3 these days, somebody might create an
+ * out-of-tree version adding back support for Python 2. Conflicts with
+ * such an extension should be detected.
+ */
+ bitmask_ptr = (int **) find_rendezvous_variable("plpython_version_bitmask");
+ if (!(*bitmask_ptr)) /* am I the first? */
+ *bitmask_ptr = &plpython_version_bitmask;
+ /* Retain pointer to the agreed-on shared variable ... */
+ plpython_version_bitmask_ptr = *bitmask_ptr;
+ /* ... and announce my presence */
+ *plpython_version_bitmask_ptr |= (1 << PY_MAJOR_VERSION);
+
+ /*
+ * This should be safe even in the presence of conflicting plpythons, and
+ * it's necessary to do it before possibly throwing a conflict error, or
+ * the error message won't get localized.
+ */
+ pg_bindtextdomain(TEXTDOMAIN);
+}
+
+/*
+ * Perform one-time setup of PL/Python, after checking for a conflict
+ * with other versions of Python.
+ */
+static void
+PLy_initialize(void)
+{
+ static bool inited = false;
+
+ /*
+ * Check for multiple Python libraries before actively doing anything with
+ * libpython. This must be repeated on each entry to PL/Python, in case a
+ * conflicting library got loaded since we last looked.
+ *
+ * It is attractive to weaken this error from FATAL to ERROR, but there
+ * would be corner cases, so it seems best to be conservative.
+ */
+ if (*plpython_version_bitmask_ptr != (1 << PY_MAJOR_VERSION))
+ ereport(FATAL,
+ (errmsg("multiple Python libraries are present in session"),
+ errdetail("Only one Python major version can be used in one session.")));
+
+ /* The rest should only be done once per session */
+ if (inited)
+ return;
+
+ PyImport_AppendInittab("plpy", PyInit_plpy);
+ Py_Initialize();
+ PyImport_ImportModule("plpy");
+ PLy_init_interp();
+ PLy_init_plpy();
+ if (PyErr_Occurred())
+ PLy_elog(FATAL, "untrapped error in initialization");
+
+ init_procedure_caches();
+
+ explicit_subtransactions = NIL;
+
+ PLy_execution_contexts = NULL;
+
+ inited = true;
+}
+
+/*
+ * This should be called only once, from PLy_initialize. Initialize the Python
+ * interpreter and global data.
+ */
+static void
+PLy_init_interp(void)
+{
+ static PyObject *PLy_interp_safe_globals = NULL;
+ PyObject *mainmod;
+
+ mainmod = PyImport_AddModule("__main__");
+ if (mainmod == NULL || PyErr_Occurred())
+ PLy_elog(ERROR, "could not import \"__main__\" module");
+ Py_INCREF(mainmod);
+ PLy_interp_globals = PyModule_GetDict(mainmod);
+ PLy_interp_safe_globals = PyDict_New();
+ if (PLy_interp_safe_globals == NULL)
+ PLy_elog(ERROR, NULL);
+ PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals);
+ Py_DECREF(mainmod);
+ if (PLy_interp_globals == NULL || PyErr_Occurred())
+ PLy_elog(ERROR, "could not initialize globals");
+}
+
+Datum
+plpython3_validator(PG_FUNCTION_ARGS)
+{
+ Oid funcoid = PG_GETARG_OID(0);
+ HeapTuple tuple;
+ Form_pg_proc procStruct;
+ bool is_trigger;
+
+ if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
+ PG_RETURN_VOID();
+
+ if (!check_function_bodies)
+ PG_RETURN_VOID();
+
+ /* Do this only after making sure we need to do something */
+ PLy_initialize();
+
+ /* 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);
+ procStruct = (Form_pg_proc) GETSTRUCT(tuple);
+
+ is_trigger = PLy_procedure_is_trigger(procStruct);
+
+ ReleaseSysCache(tuple);
+
+ /* We can't validate triggers against any particular table ... */
+ PLy_procedure_get(funcoid, InvalidOid, is_trigger);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+plpython3_call_handler(PG_FUNCTION_ARGS)
+{
+ bool nonatomic;
+ Datum retval;
+ PLyExecutionContext *exec_ctx;
+ ErrorContextCallback plerrcontext;
+
+ PLy_initialize();
+
+ nonatomic = fcinfo->context &&
+ IsA(fcinfo->context, CallContext) &&
+ !castNode(CallContext, fcinfo->context)->atomic;
+
+ /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
+ if (SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0) != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed");
+
+ /*
+ * Push execution context onto stack. It is important that this get
+ * popped again, so avoid putting anything that could throw error between
+ * here and the PG_TRY.
+ */
+ exec_ctx = PLy_push_execution_context(!nonatomic);
+
+ PG_TRY();
+ {
+ Oid funcoid = fcinfo->flinfo->fn_oid;
+ PLyProcedure *proc;
+
+ /*
+ * Setup error traceback support for ereport(). Note that the PG_TRY
+ * structure pops this for us again at exit, so we needn't do that
+ * explicitly, nor do we risk the callback getting called after we've
+ * destroyed the exec_ctx.
+ */
+ plerrcontext.callback = plpython_error_callback;
+ plerrcontext.arg = exec_ctx;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ if (CALLED_AS_TRIGGER(fcinfo))
+ {
+ Relation tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
+ HeapTuple trv;
+
+ proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), true);
+ exec_ctx->curr_proc = proc;
+ trv = PLy_exec_trigger(fcinfo, proc);
+ retval = PointerGetDatum(trv);
+ }
+ else
+ {
+ proc = PLy_procedure_get(funcoid, InvalidOid, false);
+ exec_ctx->curr_proc = proc;
+ retval = PLy_exec_function(fcinfo, proc);
+ }
+ }
+ PG_CATCH();
+ {
+ PLy_pop_execution_context();
+ PyErr_Clear();
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /* Destroy the execution context */
+ PLy_pop_execution_context();
+
+ return retval;
+}
+
+Datum
+plpython3_inline_handler(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(fake_fcinfo, 0);
+ InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
+ FmgrInfo flinfo;
+ PLyProcedure proc;
+ PLyExecutionContext *exec_ctx;
+ ErrorContextCallback plerrcontext;
+
+ PLy_initialize();
+
+ /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
+ if (SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC) != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed");
+
+ MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
+ MemSet(&flinfo, 0, sizeof(flinfo));
+ fake_fcinfo->flinfo = &flinfo;
+ flinfo.fn_oid = InvalidOid;
+ flinfo.fn_mcxt = CurrentMemoryContext;
+
+ MemSet(&proc, 0, sizeof(PLyProcedure));
+ proc.mcxt = AllocSetContextCreate(TopMemoryContext,
+ "__plpython_inline_block",
+ ALLOCSET_DEFAULT_SIZES);
+ proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
+ proc.langid = codeblock->langOid;
+
+ /*
+ * This is currently sufficient to get PLy_exec_function to work, but
+ * someday we might need to be honest and use PLy_output_setup_func.
+ */
+ proc.result.typoid = VOIDOID;
+
+ /*
+ * Push execution context onto stack. It is important that this get
+ * popped again, so avoid putting anything that could throw error between
+ * here and the PG_TRY.
+ */
+ exec_ctx = PLy_push_execution_context(codeblock->atomic);
+
+ PG_TRY();
+ {
+ /*
+ * Setup error traceback support for ereport().
+ * plpython_inline_error_callback doesn't currently need exec_ctx, but
+ * for consistency with plpython3_call_handler we do it the same way.
+ */
+ plerrcontext.callback = plpython_inline_error_callback;
+ plerrcontext.arg = exec_ctx;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ PLy_procedure_compile(&proc, codeblock->source_text);
+ exec_ctx->curr_proc = &proc;
+ PLy_exec_function(fake_fcinfo, &proc);
+ }
+ PG_CATCH();
+ {
+ PLy_pop_execution_context();
+ PLy_procedure_delete(&proc);
+ PyErr_Clear();
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /* Destroy the execution context */
+ PLy_pop_execution_context();
+
+ /* Now clean up the transient procedure we made */
+ PLy_procedure_delete(&proc);
+
+ PG_RETURN_VOID();
+}
+
+static bool
+PLy_procedure_is_trigger(Form_pg_proc procStruct)
+{
+ return (procStruct->prorettype == TRIGGEROID);
+}
+
+static void
+plpython_error_callback(void *arg)
+{
+ PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg;
+
+ if (exec_ctx->curr_proc)
+ {
+ if (exec_ctx->curr_proc->is_procedure)
+ errcontext("PL/Python procedure \"%s\"",
+ PLy_procedure_name(exec_ctx->curr_proc));
+ else
+ errcontext("PL/Python function \"%s\"",
+ PLy_procedure_name(exec_ctx->curr_proc));
+ }
+}
+
+static void
+plpython_inline_error_callback(void *arg)
+{
+ errcontext("PL/Python anonymous code block");
+}
+
+PLyExecutionContext *
+PLy_current_execution_context(void)
+{
+ if (PLy_execution_contexts == NULL)
+ elog(ERROR, "no Python function is currently executing");
+
+ return PLy_execution_contexts;
+}
+
+MemoryContext
+PLy_get_scratch_context(PLyExecutionContext *context)
+{
+ /*
+ * A scratch context might never be needed in a given plpython procedure,
+ * so allocate it on first request.
+ */
+ if (context->scratch_ctx == NULL)
+ context->scratch_ctx =
+ AllocSetContextCreate(TopTransactionContext,
+ "PL/Python scratch context",
+ ALLOCSET_DEFAULT_SIZES);
+ return context->scratch_ctx;
+}
+
+static PLyExecutionContext *
+PLy_push_execution_context(bool atomic_context)
+{
+ PLyExecutionContext *context;
+
+ /* Pick a memory context similar to what SPI uses. */
+ context = (PLyExecutionContext *)
+ MemoryContextAlloc(atomic_context ? TopTransactionContext : PortalContext,
+ sizeof(PLyExecutionContext));
+ context->curr_proc = NULL;
+ context->scratch_ctx = NULL;
+ context->next = PLy_execution_contexts;
+ PLy_execution_contexts = context;
+ return context;
+}
+
+static void
+PLy_pop_execution_context(void)
+{
+ PLyExecutionContext *context = PLy_execution_contexts;
+
+ if (context == NULL)
+ elog(ERROR, "no Python function is currently executing");
+
+ PLy_execution_contexts = context->next;
+
+ if (context->scratch_ctx)
+ MemoryContextDelete(context->scratch_ctx);
+ pfree(context);
+}