LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_procedure.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 177 189 93.7 %
Date: 2025-09-10 22:18:18 Functions: 8 8 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * Python procedure manipulation for plpython
       3             :  *
       4             :  * src/pl/plpython/plpy_procedure.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 "funcapi.h"
      13             : #include "plpy_elog.h"
      14             : #include "plpy_main.h"
      15             : #include "plpy_procedure.h"
      16             : #include "plpy_util.h"
      17             : #include "utils/builtins.h"
      18             : #include "utils/hsearch.h"
      19             : #include "utils/memutils.h"
      20             : #include "utils/syscache.h"
      21             : 
      22             : static HTAB *PLy_procedure_cache = NULL;
      23             : 
      24             : static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, PLyTrigType is_trigger);
      25             : static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
      26             : static char *PLy_procedure_munge_source(const char *name, const char *src);
      27             : 
      28             : 
      29             : void
      30          46 : init_procedure_caches(void)
      31             : {
      32             :     HASHCTL     hash_ctl;
      33             : 
      34          46 :     hash_ctl.keysize = sizeof(PLyProcedureKey);
      35          46 :     hash_ctl.entrysize = sizeof(PLyProcedureEntry);
      36          46 :     PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
      37             :                                       HASH_ELEM | HASH_BLOBS);
      38          46 : }
      39             : 
      40             : /*
      41             :  * PLy_procedure_name: get the name of the specified procedure.
      42             :  *
      43             :  * NB: this returns the SQL name, not the internal Python procedure name
      44             :  */
      45             : char *
      46        1062 : PLy_procedure_name(PLyProcedure *proc)
      47             : {
      48        1062 :     if (proc == NULL)
      49           0 :         return "<unknown procedure>";
      50        1062 :     return proc->proname;
      51             : }
      52             : 
      53             : /*
      54             :  * PLy_procedure_get: returns a cached PLyProcedure, or creates, stores and
      55             :  * returns a new PLyProcedure.
      56             :  *
      57             :  * fn_oid is the OID of the function requested
      58             :  * fn_rel is InvalidOid or the relation this function triggers on
      59             :  * is_trigger denotes whether the function is a trigger function
      60             :  *
      61             :  * The reason that both fn_rel and is_trigger need to be passed is that when
      62             :  * trigger functions get validated we don't know which relation(s) they'll
      63             :  * be used with, so no sensible fn_rel can be passed.
      64             :  */
      65             : PLyProcedure *
      66        1902 : PLy_procedure_get(Oid fn_oid, Oid fn_rel, PLyTrigType is_trigger)
      67             : {
      68             :     bool        use_cache;
      69             :     HeapTuple   procTup;
      70             :     PLyProcedureKey key;
      71        1902 :     PLyProcedureEntry *volatile entry = NULL;
      72        1902 :     PLyProcedure *volatile proc = NULL;
      73        1902 :     bool        found = false;
      74             : 
      75        1902 :     if (is_trigger == PLPY_TRIGGER && fn_rel == InvalidOid)
      76          48 :         use_cache = false;
      77             :     else
      78        1854 :         use_cache = true;
      79             : 
      80        1902 :     procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
      81        1902 :     if (!HeapTupleIsValid(procTup))
      82           0 :         elog(ERROR, "cache lookup failed for function %u", fn_oid);
      83             : 
      84             :     /*
      85             :      * Look for the function in the cache, unless we don't have the necessary
      86             :      * information (e.g. during validation). In that case we just don't cache
      87             :      * anything.
      88             :      */
      89        1902 :     if (use_cache)
      90             :     {
      91        1854 :         key.fn_oid = fn_oid;
      92        1854 :         key.fn_rel = fn_rel;
      93        1854 :         entry = hash_search(PLy_procedure_cache, &key, HASH_ENTER, &found);
      94        1854 :         proc = entry->proc;
      95             :     }
      96             : 
      97        1902 :     PG_TRY();
      98             :     {
      99        1902 :         if (!found)
     100             :         {
     101             :             /* Haven't found it, create a new procedure */
     102         548 :             proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
     103         540 :             if (use_cache)
     104         492 :                 entry->proc = proc;
     105             :         }
     106        1354 :         else if (!PLy_procedure_valid(proc, procTup))
     107             :         {
     108             :             /* Found it, but it's invalid, free and reuse the cache entry */
     109          10 :             entry->proc = NULL;
     110          10 :             if (proc)
     111          10 :                 PLy_procedure_delete(proc);
     112          10 :             proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
     113          10 :             entry->proc = proc;
     114             :         }
     115             :         /* Found it and it's valid, it's fine to use it */
     116             :     }
     117           8 :     PG_CATCH();
     118             :     {
     119             :         /* Do not leave an uninitialized entry in the cache */
     120           8 :         if (use_cache)
     121           8 :             hash_search(PLy_procedure_cache, &key, HASH_REMOVE, NULL);
     122           8 :         PG_RE_THROW();
     123             :     }
     124        1894 :     PG_END_TRY();
     125             : 
     126        1894 :     ReleaseSysCache(procTup);
     127             : 
     128        1894 :     return proc;
     129             : }
     130             : 
     131             : /*
     132             :  * Create a new PLyProcedure structure
     133             :  */
     134             : static PLyProcedure *
     135         558 : PLy_procedure_create(HeapTuple procTup, Oid fn_oid, PLyTrigType is_trigger)
     136             : {
     137             :     char        procName[NAMEDATALEN + 256];
     138             :     Form_pg_proc procStruct;
     139             :     PLyProcedure *volatile proc;
     140             :     MemoryContext cxt;
     141             :     MemoryContext oldcxt;
     142             :     int         rv;
     143             :     char       *ptr;
     144             : 
     145         558 :     procStruct = (Form_pg_proc) GETSTRUCT(procTup);
     146        1116 :     rv = snprintf(procName, sizeof(procName),
     147             :                   "__plpython_procedure_%s_%u",
     148         558 :                   NameStr(procStruct->proname),
     149             :                   fn_oid);
     150         558 :     if (rv >= sizeof(procName) || rv < 0)
     151           0 :         elog(ERROR, "procedure name would overrun buffer");
     152             : 
     153             :     /* Replace any not-legal-in-Python-names characters with '_' */
     154       26134 :     for (ptr = procName; *ptr; ptr++)
     155             :     {
     156       25576 :         if (!((*ptr >= 'A' && *ptr <= 'Z') ||
     157       25574 :               (*ptr >= 'a' && *ptr <= 'z') ||
     158        6854 :               (*ptr >= '0' && *ptr <= '9')))
     159        3860 :             *ptr = '_';
     160             :     }
     161             : 
     162             :     /* Create long-lived context that all procedure info will live in */
     163         558 :     cxt = AllocSetContextCreate(TopMemoryContext,
     164             :                                 "PL/Python function",
     165             :                                 ALLOCSET_DEFAULT_SIZES);
     166             : 
     167         558 :     oldcxt = MemoryContextSwitchTo(cxt);
     168             : 
     169         558 :     proc = (PLyProcedure *) palloc0(sizeof(PLyProcedure));
     170         558 :     proc->mcxt = cxt;
     171             : 
     172         558 :     PG_TRY();
     173             :     {
     174             :         Datum       protrftypes_datum;
     175             :         Datum       prosrcdatum;
     176             :         bool        isnull;
     177             :         char       *procSource;
     178             :         int         i;
     179             : 
     180         558 :         proc->proname = pstrdup(NameStr(procStruct->proname));
     181         558 :         MemoryContextSetIdentifier(cxt, proc->proname);
     182         558 :         proc->pyname = pstrdup(procName);
     183         558 :         proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
     184         558 :         proc->fn_tid = procTup->t_self;
     185         558 :         proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
     186         558 :         proc->is_setof = procStruct->proretset;
     187         558 :         proc->is_procedure = (procStruct->prokind == PROKIND_PROCEDURE);
     188         558 :         proc->is_trigger = is_trigger;
     189         558 :         proc->src = NULL;
     190         558 :         proc->argnames = NULL;
     191         558 :         proc->args = NULL;
     192         558 :         proc->nargs = 0;
     193         558 :         proc->langid = procStruct->prolang;
     194         558 :         protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
     195             :                                             Anum_pg_proc_protrftypes,
     196             :                                             &isnull);
     197         558 :         proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
     198         558 :         proc->code = NULL;
     199         558 :         proc->statics = NULL;
     200         558 :         proc->globals = NULL;
     201         558 :         proc->calldepth = 0;
     202         558 :         proc->argstack = NULL;
     203             : 
     204             :         /*
     205             :          * get information required for output conversion of the return value,
     206             :          * but only if this isn't a trigger.
     207             :          */
     208         558 :         if (is_trigger == PLPY_NOT_TRIGGER)
     209             :         {
     210         456 :             Oid         rettype = procStruct->prorettype;
     211             :             HeapTuple   rvTypeTup;
     212             :             Form_pg_type rvTypeStruct;
     213             : 
     214         456 :             rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
     215         456 :             if (!HeapTupleIsValid(rvTypeTup))
     216           0 :                 elog(ERROR, "cache lookup failed for type %u", rettype);
     217         456 :             rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
     218             : 
     219             :             /* Disallow pseudotype result, except for void or record */
     220         456 :             if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
     221             :             {
     222         104 :                 if (rettype == VOIDOID ||
     223             :                     rettype == RECORDOID)
     224             :                      /* okay */ ;
     225           2 :                 else if (rettype == TRIGGEROID || rettype == EVENT_TRIGGEROID)
     226           2 :                     ereport(ERROR,
     227             :                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     228             :                              errmsg("trigger functions can only be called as triggers")));
     229             :                 else
     230           0 :                     ereport(ERROR,
     231             :                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     232             :                              errmsg("PL/Python functions cannot return type %s",
     233             :                                     format_type_be(rettype))));
     234             :             }
     235             : 
     236             :             /* set up output function for procedure result */
     237         454 :             PLy_output_setup_func(&proc->result, proc->mcxt,
     238             :                                   rettype, -1, proc);
     239             : 
     240         454 :             ReleaseSysCache(rvTypeTup);
     241             :         }
     242             :         else
     243             :         {
     244             :             /*
     245             :              * In a trigger function, we use proc->result and proc->result_in
     246             :              * for converting tuples, but we don't yet have enough info to set
     247             :              * them up.  PLy_exec_trigger will deal with it.
     248             :              */
     249         102 :             proc->result.typoid = InvalidOid;
     250         102 :             proc->result_in.typoid = InvalidOid;
     251             :         }
     252             : 
     253             :         /*
     254             :          * Now get information required for input conversion of the
     255             :          * procedure's arguments.  Note that we ignore output arguments here.
     256             :          * If the function returns record, those I/O functions will be set up
     257             :          * when the function is first called.
     258             :          */
     259         556 :         if (procStruct->pronargs)
     260             :         {
     261             :             Oid        *types;
     262             :             char      **names,
     263             :                        *modes;
     264             :             int         pos,
     265             :                         total;
     266             : 
     267             :             /* extract argument type info from the pg_proc tuple */
     268         214 :             total = get_func_arg_info(procTup, &types, &names, &modes);
     269             : 
     270             :             /* count number of in+inout args into proc->nargs */
     271         214 :             if (modes == NULL)
     272         192 :                 proc->nargs = total;
     273             :             else
     274             :             {
     275             :                 /* proc->nargs was initialized to 0 above */
     276          82 :                 for (i = 0; i < total; i++)
     277             :                 {
     278          60 :                     if (modes[i] != PROARGMODE_OUT &&
     279          36 :                         modes[i] != PROARGMODE_TABLE)
     280          36 :                         (proc->nargs)++;
     281             :                 }
     282             :             }
     283             : 
     284             :             /* Allocate arrays for per-input-argument data */
     285         214 :             proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs);
     286         214 :             proc->args = (PLyDatumToOb *) palloc0(sizeof(PLyDatumToOb) * proc->nargs);
     287             : 
     288         554 :             for (i = pos = 0; i < total; i++)
     289             :             {
     290             :                 HeapTuple   argTypeTup;
     291             :                 Form_pg_type argTypeStruct;
     292             : 
     293         340 :                 if (modes &&
     294          60 :                     (modes[i] == PROARGMODE_OUT ||
     295          36 :                      modes[i] == PROARGMODE_TABLE))
     296          24 :                     continue;   /* skip OUT arguments */
     297             : 
     298             :                 Assert(types[i] == procStruct->proargtypes.values[pos]);
     299             : 
     300         316 :                 argTypeTup = SearchSysCache1(TYPEOID,
     301         316 :                                              ObjectIdGetDatum(types[i]));
     302         316 :                 if (!HeapTupleIsValid(argTypeTup))
     303           0 :                     elog(ERROR, "cache lookup failed for type %u", types[i]);
     304         316 :                 argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
     305             : 
     306             :                 /* disallow pseudotype arguments */
     307         316 :                 if (argTypeStruct->typtype == TYPTYPE_PSEUDO)
     308           0 :                     ereport(ERROR,
     309             :                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     310             :                              errmsg("PL/Python functions cannot accept type %s",
     311             :                                     format_type_be(types[i]))));
     312             : 
     313             :                 /* set up I/O function info */
     314         316 :                 PLy_input_setup_func(&proc->args[pos], proc->mcxt,
     315         316 :                                      types[i], -1,  /* typmod not known */
     316             :                                      proc);
     317             : 
     318             :                 /* get argument name */
     319         316 :                 proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
     320             : 
     321         316 :                 ReleaseSysCache(argTypeTup);
     322             : 
     323         316 :                 pos++;
     324             :             }
     325             :         }
     326             : 
     327             :         /*
     328             :          * get the text of the function.
     329             :          */
     330         556 :         prosrcdatum = SysCacheGetAttrNotNull(PROCOID, procTup,
     331             :                                              Anum_pg_proc_prosrc);
     332         556 :         procSource = TextDatumGetCString(prosrcdatum);
     333             : 
     334         556 :         PLy_procedure_compile(proc, procSource);
     335             : 
     336         550 :         pfree(procSource);
     337             :     }
     338           8 :     PG_CATCH();
     339             :     {
     340           8 :         MemoryContextSwitchTo(oldcxt);
     341           8 :         PLy_procedure_delete(proc);
     342           8 :         PG_RE_THROW();
     343             :     }
     344         550 :     PG_END_TRY();
     345             : 
     346         550 :     MemoryContextSwitchTo(oldcxt);
     347         550 :     return proc;
     348             : }
     349             : 
     350             : /*
     351             :  * Insert the procedure into the Python interpreter
     352             :  */
     353             : void
     354         598 : PLy_procedure_compile(PLyProcedure *proc, const char *src)
     355             : {
     356         598 :     PyObject   *crv = NULL;
     357             :     char       *msrc;
     358             :     PyObject   *code0;
     359             : 
     360         598 :     proc->globals = PyDict_Copy(PLy_interp_globals);
     361             : 
     362             :     /*
     363             :      * SD is private preserved data between calls. GD is global data shared by
     364             :      * all functions
     365             :      */
     366         598 :     proc->statics = PyDict_New();
     367         598 :     if (!proc->statics)
     368           0 :         PLy_elog(ERROR, NULL);
     369         598 :     PyDict_SetItemString(proc->globals, "SD", proc->statics);
     370             : 
     371             :     /*
     372             :      * insert the function code into the interpreter
     373             :      */
     374         598 :     msrc = PLy_procedure_munge_source(proc->pyname, src);
     375             :     /* Save the mangled source for later inclusion in tracebacks */
     376         598 :     proc->src = MemoryContextStrdup(proc->mcxt, msrc);
     377         598 :     code0 = Py_CompileString(msrc, "<string>", Py_file_input);
     378         598 :     if (code0)
     379         592 :         crv = PyEval_EvalCode(code0, proc->globals, NULL);
     380         598 :     pfree(msrc);
     381             : 
     382         598 :     if (crv != NULL)
     383             :     {
     384             :         int         clen;
     385             :         char        call[NAMEDATALEN + 256];
     386             : 
     387         592 :         Py_DECREF(crv);
     388             : 
     389             :         /*
     390             :          * compile a call to the function
     391             :          */
     392         592 :         clen = snprintf(call, sizeof(call), "%s()", proc->pyname);
     393         592 :         if (clen < 0 || clen >= sizeof(call))
     394           0 :             elog(ERROR, "string would overflow buffer");
     395         592 :         proc->code = Py_CompileString(call, "<string>", Py_eval_input);
     396         592 :         if (proc->code != NULL)
     397         592 :             return;
     398             :     }
     399             : 
     400           6 :     if (proc->proname)
     401           6 :         PLy_elog(ERROR, "could not compile PL/Python function \"%s\"",
     402             :                  proc->proname);
     403             :     else
     404           0 :         PLy_elog(ERROR, "could not compile anonymous PL/Python code block");
     405             : }
     406             : 
     407             : void
     408          60 : PLy_procedure_delete(PLyProcedure *proc)
     409             : {
     410          60 :     Py_XDECREF(proc->code);
     411          60 :     Py_XDECREF(proc->statics);
     412          60 :     Py_XDECREF(proc->globals);
     413          60 :     MemoryContextDelete(proc->mcxt);
     414          60 : }
     415             : 
     416             : /*
     417             :  * Decide whether a cached PLyProcedure struct is still valid
     418             :  */
     419             : static bool
     420        1354 : PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
     421             : {
     422        1354 :     if (proc == NULL)
     423           0 :         return false;
     424             : 
     425             :     /* If the pg_proc tuple has changed, it's not valid */
     426        2698 :     if (!(proc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
     427        1344 :           ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
     428          10 :         return false;
     429             : 
     430        1344 :     return true;
     431             : }
     432             : 
     433             : static char *
     434         598 : PLy_procedure_munge_source(const char *name, const char *src)
     435             : {
     436             :     char       *mrc,
     437             :                *mp;
     438             :     const char *sp;
     439             :     size_t      mlen;
     440             :     int         plen;
     441             : 
     442             :     /*
     443             :      * room for function source and the def statement
     444             :      */
     445         598 :     mlen = (strlen(src) * 2) + strlen(name) + 16;
     446             : 
     447         598 :     mrc = palloc(mlen);
     448         598 :     plen = snprintf(mrc, mlen, "def %s():\n\t", name);
     449             :     Assert(plen >= 0 && plen < mlen);
     450             : 
     451         598 :     sp = src;
     452         598 :     mp = mrc + plen;
     453             : 
     454       64362 :     while (*sp != '\0')
     455             :     {
     456       63764 :         if (*sp == '\r' && *(sp + 1) == '\n')
     457           6 :             sp++;
     458             : 
     459       63764 :         if (*sp == '\n' || *sp == '\r')
     460             :         {
     461        2692 :             *mp++ = '\n';
     462        2692 :             *mp++ = '\t';
     463        2692 :             sp++;
     464             :         }
     465             :         else
     466       61072 :             *mp++ = *sp++;
     467             :     }
     468         598 :     *mp++ = '\n';
     469         598 :     *mp++ = '\n';
     470         598 :     *mp = '\0';
     471             : 
     472         598 :     if (mp > (mrc + mlen))
     473           0 :         elog(FATAL, "buffer overrun in PLy_procedure_munge_source");
     474             : 
     475         598 :     return mrc;
     476             : }

Generated by: LCOV version 1.16