LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_main.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 148 157 94.3 %
Date: 2024-11-21 08:14:44 Functions: 17 17 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * PL/Python main entry points
       3             :  *
       4             :  * src/pl/plpython/plpy_main.c
       5             :  */
       6             : 
       7             : #include "postgres.h"
       8             : 
       9             : #include "access/htup_details.h"
      10             : #include "catalog/pg_proc.h"
      11             : #include "catalog/pg_type.h"
      12             : #include "commands/trigger.h"
      13             : #include "executor/spi.h"
      14             : #include "miscadmin.h"
      15             : #include "plpy_elog.h"
      16             : #include "plpy_exec.h"
      17             : #include "plpy_main.h"
      18             : #include "plpy_plpymodule.h"
      19             : #include "plpy_procedure.h"
      20             : #include "plpy_subxactobject.h"
      21             : #include "plpython.h"
      22             : #include "utils/guc.h"
      23             : #include "utils/memutils.h"
      24             : #include "utils/rel.h"
      25             : #include "utils/syscache.h"
      26             : 
      27             : /*
      28             :  * exported functions
      29             :  */
      30             : 
      31          46 : PG_MODULE_MAGIC;
      32             : 
      33          52 : PG_FUNCTION_INFO_V1(plpython3_validator);
      34          52 : PG_FUNCTION_INFO_V1(plpython3_call_handler);
      35          16 : PG_FUNCTION_INFO_V1(plpython3_inline_handler);
      36             : 
      37             : 
      38             : static bool PLy_procedure_is_trigger(Form_pg_proc procStruct);
      39             : static void plpython_error_callback(void *arg);
      40             : static void plpython_inline_error_callback(void *arg);
      41             : static void PLy_init_interp(void);
      42             : 
      43             : static PLyExecutionContext *PLy_push_execution_context(bool atomic_context);
      44             : static void PLy_pop_execution_context(void);
      45             : 
      46             : /* static state for Python library conflict detection */
      47             : static int *plpython_version_bitmask_ptr = NULL;
      48             : static int  plpython_version_bitmask = 0;
      49             : 
      50             : /* initialize global variables */
      51             : PyObject   *PLy_interp_globals = NULL;
      52             : 
      53             : /* this doesn't need to be global; use PLy_current_execution_context() */
      54             : static PLyExecutionContext *PLy_execution_contexts = NULL;
      55             : 
      56             : 
      57             : void
      58          46 : _PG_init(void)
      59             : {
      60             :     int       **bitmask_ptr;
      61             : 
      62             :     /*
      63             :      * Set up a shared bitmask variable telling which Python version(s) are
      64             :      * loaded into this process's address space.  If there's more than one, we
      65             :      * cannot call into libpython for fear of causing crashes.  But postpone
      66             :      * the actual failure for later, so that operations like pg_restore can
      67             :      * load more than one plpython library so long as they don't try to do
      68             :      * anything much with the language.
      69             :      *
      70             :      * While we only support Python 3 these days, somebody might create an
      71             :      * out-of-tree version adding back support for Python 2. Conflicts with
      72             :      * such an extension should be detected.
      73             :      */
      74          46 :     bitmask_ptr = (int **) find_rendezvous_variable("plpython_version_bitmask");
      75          46 :     if (!(*bitmask_ptr))        /* am I the first? */
      76          46 :         *bitmask_ptr = &plpython_version_bitmask;
      77             :     /* Retain pointer to the agreed-on shared variable ... */
      78          46 :     plpython_version_bitmask_ptr = *bitmask_ptr;
      79             :     /* ... and announce my presence */
      80          46 :     *plpython_version_bitmask_ptr |= (1 << PY_MAJOR_VERSION);
      81             : 
      82             :     /*
      83             :      * This should be safe even in the presence of conflicting plpythons, and
      84             :      * it's necessary to do it before possibly throwing a conflict error, or
      85             :      * the error message won't get localized.
      86             :      */
      87          46 :     pg_bindtextdomain(TEXTDOMAIN);
      88          46 : }
      89             : 
      90             : /*
      91             :  * Perform one-time setup of PL/Python, after checking for a conflict
      92             :  * with other versions of Python.
      93             :  */
      94             : static void
      95        1904 : PLy_initialize(void)
      96             : {
      97             :     static bool inited = false;
      98             : 
      99             :     /*
     100             :      * Check for multiple Python libraries before actively doing anything with
     101             :      * libpython.  This must be repeated on each entry to PL/Python, in case a
     102             :      * conflicting library got loaded since we last looked.
     103             :      *
     104             :      * It is attractive to weaken this error from FATAL to ERROR, but there
     105             :      * would be corner cases, so it seems best to be conservative.
     106             :      */
     107        1904 :     if (*plpython_version_bitmask_ptr != (1 << PY_MAJOR_VERSION))
     108           0 :         ereport(FATAL,
     109             :                 (errmsg("multiple Python libraries are present in session"),
     110             :                  errdetail("Only one Python major version can be used in one session.")));
     111             : 
     112             :     /* The rest should only be done once per session */
     113        1904 :     if (inited)
     114        1858 :         return;
     115             : 
     116          46 :     PyImport_AppendInittab("plpy", PyInit_plpy);
     117          46 :     Py_Initialize();
     118          46 :     PyImport_ImportModule("plpy");
     119          46 :     PLy_init_interp();
     120          46 :     PLy_init_plpy();
     121          46 :     if (PyErr_Occurred())
     122           0 :         PLy_elog(FATAL, "untrapped error in initialization");
     123             : 
     124          46 :     init_procedure_caches();
     125             : 
     126          46 :     explicit_subtransactions = NIL;
     127             : 
     128          46 :     PLy_execution_contexts = NULL;
     129             : 
     130          46 :     inited = true;
     131             : }
     132             : 
     133             : /*
     134             :  * This should be called only once, from PLy_initialize. Initialize the Python
     135             :  * interpreter and global data.
     136             :  */
     137             : static void
     138          46 : PLy_init_interp(void)
     139             : {
     140             :     static PyObject *PLy_interp_safe_globals = NULL;
     141             :     PyObject   *mainmod;
     142             : 
     143          46 :     mainmod = PyImport_AddModule("__main__");
     144          46 :     if (mainmod == NULL || PyErr_Occurred())
     145           0 :         PLy_elog(ERROR, "could not import \"__main__\" module");
     146          46 :     Py_INCREF(mainmod);
     147          46 :     PLy_interp_globals = PyModule_GetDict(mainmod);
     148          46 :     PLy_interp_safe_globals = PyDict_New();
     149          46 :     if (PLy_interp_safe_globals == NULL)
     150           0 :         PLy_elog(ERROR, NULL);
     151          46 :     PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals);
     152          46 :     Py_DECREF(mainmod);
     153          46 :     if (PLy_interp_globals == NULL || PyErr_Occurred())
     154           0 :         PLy_elog(ERROR, "could not initialize globals");
     155          46 : }
     156             : 
     157             : Datum
     158         498 : plpython3_validator(PG_FUNCTION_ARGS)
     159             : {
     160         498 :     Oid         funcoid = PG_GETARG_OID(0);
     161             :     HeapTuple   tuple;
     162             :     Form_pg_proc procStruct;
     163             :     bool        is_trigger;
     164             : 
     165         498 :     if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
     166           0 :         PG_RETURN_VOID();
     167             : 
     168         498 :     if (!check_function_bodies)
     169           2 :         PG_RETURN_VOID();
     170             : 
     171             :     /* Do this only after making sure we need to do something */
     172         496 :     PLy_initialize();
     173             : 
     174             :     /* Get the new function's pg_proc entry */
     175         496 :     tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
     176         496 :     if (!HeapTupleIsValid(tuple))
     177           0 :         elog(ERROR, "cache lookup failed for function %u", funcoid);
     178         496 :     procStruct = (Form_pg_proc) GETSTRUCT(tuple);
     179             : 
     180         496 :     is_trigger = PLy_procedure_is_trigger(procStruct);
     181             : 
     182         496 :     ReleaseSysCache(tuple);
     183             : 
     184             :     /* We can't validate triggers against any particular table ... */
     185         496 :     PLy_procedure_get(funcoid, InvalidOid, is_trigger);
     186             : 
     187         494 :     PG_RETURN_VOID();
     188             : }
     189             : 
     190             : Datum
     191        1366 : plpython3_call_handler(PG_FUNCTION_ARGS)
     192             : {
     193             :     bool        nonatomic;
     194             :     Datum       retval;
     195             :     PLyExecutionContext *exec_ctx;
     196             :     ErrorContextCallback plerrcontext;
     197             : 
     198        1366 :     PLy_initialize();
     199             : 
     200        2846 :     nonatomic = fcinfo->context &&
     201        1382 :         IsA(fcinfo->context, CallContext) &&
     202          16 :         !castNode(CallContext, fcinfo->context)->atomic;
     203             : 
     204             :     /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
     205        1366 :     SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0);
     206             : 
     207             :     /*
     208             :      * Push execution context onto stack.  It is important that this get
     209             :      * popped again, so avoid putting anything that could throw error between
     210             :      * here and the PG_TRY.
     211             :      */
     212        1366 :     exec_ctx = PLy_push_execution_context(!nonatomic);
     213             : 
     214        1366 :     PG_TRY();
     215             :     {
     216        1366 :         Oid         funcoid = fcinfo->flinfo->fn_oid;
     217             :         PLyProcedure *proc;
     218             : 
     219             :         /*
     220             :          * Setup error traceback support for ereport().  Note that the PG_TRY
     221             :          * structure pops this for us again at exit, so we needn't do that
     222             :          * explicitly, nor do we risk the callback getting called after we've
     223             :          * destroyed the exec_ctx.
     224             :          */
     225        1366 :         plerrcontext.callback = plpython_error_callback;
     226        1366 :         plerrcontext.arg = exec_ctx;
     227        1366 :         plerrcontext.previous = error_context_stack;
     228        1366 :         error_context_stack = &plerrcontext;
     229             : 
     230        1366 :         if (CALLED_AS_TRIGGER(fcinfo))
     231          80 :         {
     232          98 :             Relation    tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
     233             :             HeapTuple   trv;
     234             : 
     235          98 :             proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), true);
     236          98 :             exec_ctx->curr_proc = proc;
     237          98 :             trv = PLy_exec_trigger(fcinfo, proc);
     238          80 :             retval = PointerGetDatum(trv);
     239             :         }
     240             :         else
     241             :         {
     242        1268 :             proc = PLy_procedure_get(funcoid, InvalidOid, false);
     243        1262 :             exec_ctx->curr_proc = proc;
     244        1262 :             retval = PLy_exec_function(fcinfo, proc);
     245             :         }
     246             :     }
     247         186 :     PG_CATCH();
     248             :     {
     249         186 :         PLy_pop_execution_context();
     250         186 :         PyErr_Clear();
     251         186 :         PG_RE_THROW();
     252             :     }
     253        1180 :     PG_END_TRY();
     254             : 
     255             :     /* Destroy the execution context */
     256        1180 :     PLy_pop_execution_context();
     257             : 
     258        1180 :     return retval;
     259             : }
     260             : 
     261             : Datum
     262          42 : plpython3_inline_handler(PG_FUNCTION_ARGS)
     263             : {
     264          42 :     LOCAL_FCINFO(fake_fcinfo, 0);
     265          42 :     InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
     266             :     FmgrInfo    flinfo;
     267             :     PLyProcedure proc;
     268             :     PLyExecutionContext *exec_ctx;
     269             :     ErrorContextCallback plerrcontext;
     270             : 
     271          42 :     PLy_initialize();
     272             : 
     273             :     /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
     274          42 :     SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC);
     275             : 
     276         210 :     MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
     277         294 :     MemSet(&flinfo, 0, sizeof(flinfo));
     278          42 :     fake_fcinfo->flinfo = &flinfo;
     279          42 :     flinfo.fn_oid = InvalidOid;
     280          42 :     flinfo.fn_mcxt = CurrentMemoryContext;
     281             : 
     282        1722 :     MemSet(&proc, 0, sizeof(PLyProcedure));
     283          42 :     proc.mcxt = AllocSetContextCreate(TopMemoryContext,
     284             :                                       "__plpython_inline_block",
     285             :                                       ALLOCSET_DEFAULT_SIZES);
     286          42 :     proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
     287          42 :     proc.langid = codeblock->langOid;
     288             : 
     289             :     /*
     290             :      * This is currently sufficient to get PLy_exec_function to work, but
     291             :      * someday we might need to be honest and use PLy_output_setup_func.
     292             :      */
     293          42 :     proc.result.typoid = VOIDOID;
     294             : 
     295             :     /*
     296             :      * Push execution context onto stack.  It is important that this get
     297             :      * popped again, so avoid putting anything that could throw error between
     298             :      * here and the PG_TRY.
     299             :      */
     300          42 :     exec_ctx = PLy_push_execution_context(codeblock->atomic);
     301             : 
     302          42 :     PG_TRY();
     303             :     {
     304             :         /*
     305             :          * Setup error traceback support for ereport().
     306             :          * plpython_inline_error_callback doesn't currently need exec_ctx, but
     307             :          * for consistency with plpython3_call_handler we do it the same way.
     308             :          */
     309          42 :         plerrcontext.callback = plpython_inline_error_callback;
     310          42 :         plerrcontext.arg = exec_ctx;
     311          42 :         plerrcontext.previous = error_context_stack;
     312          42 :         error_context_stack = &plerrcontext;
     313             : 
     314          42 :         PLy_procedure_compile(&proc, codeblock->source_text);
     315          42 :         exec_ctx->curr_proc = &proc;
     316          42 :         PLy_exec_function(fake_fcinfo, &proc);
     317             :     }
     318          22 :     PG_CATCH();
     319             :     {
     320          22 :         PLy_pop_execution_context();
     321          22 :         PLy_procedure_delete(&proc);
     322          22 :         PyErr_Clear();
     323          22 :         PG_RE_THROW();
     324             :     }
     325          20 :     PG_END_TRY();
     326             : 
     327             :     /* Destroy the execution context */
     328          20 :     PLy_pop_execution_context();
     329             : 
     330             :     /* Now clean up the transient procedure we made */
     331          20 :     PLy_procedure_delete(&proc);
     332             : 
     333          20 :     PG_RETURN_VOID();
     334             : }
     335             : 
     336             : static bool
     337         496 : PLy_procedure_is_trigger(Form_pg_proc procStruct)
     338             : {
     339         496 :     return (procStruct->prorettype == TRIGGEROID);
     340             : }
     341             : 
     342             : static void
     343         912 : plpython_error_callback(void *arg)
     344             : {
     345         912 :     PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg;
     346             : 
     347         912 :     if (exec_ctx->curr_proc)
     348             :     {
     349         906 :         if (exec_ctx->curr_proc->is_procedure)
     350           8 :             errcontext("PL/Python procedure \"%s\"",
     351             :                        PLy_procedure_name(exec_ctx->curr_proc));
     352             :         else
     353         898 :             errcontext("PL/Python function \"%s\"",
     354             :                        PLy_procedure_name(exec_ctx->curr_proc));
     355             :     }
     356         912 : }
     357             : 
     358             : static void
     359          56 : plpython_inline_error_callback(void *arg)
     360             : {
     361          56 :     errcontext("PL/Python anonymous code block");
     362          56 : }
     363             : 
     364             : PLyExecutionContext *
     365        2842 : PLy_current_execution_context(void)
     366             : {
     367        2842 :     if (PLy_execution_contexts == NULL)
     368           0 :         elog(ERROR, "no Python function is currently executing");
     369             : 
     370        2842 :     return PLy_execution_contexts;
     371             : }
     372             : 
     373             : MemoryContext
     374        1572 : PLy_get_scratch_context(PLyExecutionContext *context)
     375             : {
     376             :     /*
     377             :      * A scratch context might never be needed in a given plpython procedure,
     378             :      * so allocate it on first request.
     379             :      */
     380        1572 :     if (context->scratch_ctx == NULL)
     381         876 :         context->scratch_ctx =
     382         876 :             AllocSetContextCreate(TopTransactionContext,
     383             :                                   "PL/Python scratch context",
     384             :                                   ALLOCSET_DEFAULT_SIZES);
     385        1572 :     return context->scratch_ctx;
     386             : }
     387             : 
     388             : static PLyExecutionContext *
     389        1408 : PLy_push_execution_context(bool atomic_context)
     390             : {
     391             :     PLyExecutionContext *context;
     392             : 
     393             :     /* Pick a memory context similar to what SPI uses. */
     394             :     context = (PLyExecutionContext *)
     395        1408 :         MemoryContextAlloc(atomic_context ? TopTransactionContext : PortalContext,
     396             :                            sizeof(PLyExecutionContext));
     397        1408 :     context->curr_proc = NULL;
     398        1408 :     context->scratch_ctx = NULL;
     399        1408 :     context->next = PLy_execution_contexts;
     400        1408 :     PLy_execution_contexts = context;
     401        1408 :     return context;
     402             : }
     403             : 
     404             : static void
     405        1408 : PLy_pop_execution_context(void)
     406             : {
     407        1408 :     PLyExecutionContext *context = PLy_execution_contexts;
     408             : 
     409        1408 :     if (context == NULL)
     410           0 :         elog(ERROR, "no Python function is currently executing");
     411             : 
     412        1408 :     PLy_execution_contexts = context->next;
     413             : 
     414        1408 :     if (context->scratch_ctx)
     415         842 :         MemoryContextDelete(context->scratch_ctx);
     416        1408 :     pfree(context);
     417        1408 : }

Generated by: LCOV version 1.14