postgresql/src/pl/plpython/plpy_main.c

463 lines
12 KiB
C

/*
* 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
*/
#if PY_MAJOR_VERSION >= 3
/* Use separate names to reduce confusion */
#define plpython_validator plpython3_validator
#define plpython_call_handler plpython3_call_handler
#define plpython_inline_handler plpython3_inline_handler
#endif
extern void _PG_init(void);
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(plpython_validator);
PG_FUNCTION_INFO_V1(plpython_call_handler);
PG_FUNCTION_INFO_V1(plpython_inline_handler);
#if PY_MAJOR_VERSION < 3
/* Define aliases plpython2_call_handler etc */
PG_FUNCTION_INFO_V1(plpython2_validator);
PG_FUNCTION_INFO_V1(plpython2_call_handler);
PG_FUNCTION_INFO_V1(plpython2_inline_handler);
#endif
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.
*/
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;
#if PY_MAJOR_VERSION >= 3
PyImport_AppendInittab("plpy", PyInit_plpy);
#endif
Py_Initialize();
#if PY_MAJOR_VERSION >= 3
PyImport_ImportModule("plpy");
#endif
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
plpython_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();
}
#if PY_MAJOR_VERSION < 3
Datum
plpython2_validator(PG_FUNCTION_ARGS)
{
/* call plpython validator with our fcinfo so it gets our oid */
return plpython_validator(fcinfo);
}
#endif /* PY_MAJOR_VERSION < 3 */
Datum
plpython_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;
}
#if PY_MAJOR_VERSION < 3
Datum
plpython2_call_handler(PG_FUNCTION_ARGS)
{
return plpython_call_handler(fcinfo);
}
#endif /* PY_MAJOR_VERSION < 3 */
Datum
plpython_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 plpython_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();
}
#if PY_MAJOR_VERSION < 3
Datum
plpython2_inline_handler(PG_FUNCTION_ARGS)
{
return plpython_inline_handler(fcinfo);
}
#endif /* PY_MAJOR_VERSION < 3 */
static bool
PLy_procedure_is_trigger(Form_pg_proc procStruct)
{
return (procStruct->prorettype == TRIGGEROID ||
(procStruct->prorettype == OPAQUEOID &&
procStruct->pronargs == 0));
}
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);
}