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

Generated by: LCOV version 1.16