Re: [HACKERS] I propose killing PL/Tcl's "modules" infrastructure

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: [HACKERS] I propose killing PL/Tcl's "modules" infrastructure
Дата
Msg-id 21254.1488228146@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: [HACKERS] I propose killing PL/Tcl's "modules" infrastructure  (Andrew Dunstan <andrew.dunstan@2ndquadrant.com>)
Ответы Re: [HACKERS] I propose killing PL/Tcl's "modules" infrastructure  (Jim Nasby <Jim.Nasby@BlueTreble.com>)
Список pgsql-hackers
Andrew Dunstan <andrew.dunstan@2ndquadrant.com> writes:
> On 02/26/2017 02:54 PM, Tom Lane wrote:
>> * I'm not terribly comfortable about what the permissions levels of the
>> GUCs ought to be.

> plperl's on_plperl_init and on_plperlu_init settings are both SUSET.
> In practice with PLv8 this is usually set in the config file in my
> experience.

Ah, I'd forgotten about that precedent.  Being consistent with that seems
like a good thing --- and I agree with your point that this would likely
usually be set in postgresql.conf anyway, making the issue rather moot.

I noticed also that the precedent of plperl is that if the init code
fails, we give up on use of that interpreter, and try again to run
the init code if plperl is used again.  This is different from what
I had in my draft spec but it probably is a better definition; without
it, people might find themselves running in Tcl interpreters that do
not behave as intended.

In sum, then, PFA a patch that adds these GUCs.  They're still function
names but otherwise the details are closer to what plperl does.

            regards, tom lane

diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml
index 0a69380..ad216dd 100644
*** a/doc/src/sgml/pltcl.sgml
--- b/doc/src/sgml/pltcl.sgml
*************** if {[catch { spi_exec $sql_command }]} {
*** 902,907 ****
--- 902,981 ----
      </para>
     </sect1>

+    <sect1 id="pltcl-config">
+     <title>PL/Tcl Configuration</title>
+
+     <para>
+      This section lists configuration parameters that
+      affect <application>PL/Tcl</>.
+     </para>
+
+     <variablelist>
+
+      <varlistentry id="guc-pltcl-start-proc" xreflabel="pltcl.start_proc">
+       <term>
+        <varname>pltcl.start_proc</varname> (<type>string</type>)
+        <indexterm>
+         <primary><varname>pltcl.start_proc</> configuration parameter</primary>
+        </indexterm>
+       </term>
+       <listitem>
+        <para>
+         This parameter, if set to a nonempty string, specifies the name
+         (possibly schema-qualified) of a parameterless PL/Tcl function that
+         is to be executed whenever a new Tcl interpreter is created for
+         PL/Tcl.  Such a function can perform per-session initialization, such
+         as loading additional Tcl code.  A new Tcl interpreter is created
+         when a PL/Tcl function is first executed in a database session, or
+         when an additional interpreter has to be created because a PL/Tcl
+         function is called by a new SQL role.
+        </para>
+
+        <para>
+         The referenced function must be written in the <literal>pltcl</>
+         language, and must not be marked <literal>SECURITY DEFINER</>.
+         (These restrictions ensure that it runs in the interpreter it's
+         supposed to initialize.)  The current user must have permission to
+         call it, too.
+        </para>
+
+        <para>
+         If the function fails with an error it will abort the function call
+         that caused the new interpreter to be created and propagate out to
+         the calling query, causing the current transaction or subtransaction
+         to be aborted.  Any actions already done within Tcl won't be undone;
+         however, that interpreter won't be used again.  If the language is
+         used again the initialization will be attempted again within a fresh
+         Tcl interpreter.
+        </para>
+
+        <para>
+         Only superusers can change this setting.  Although this setting
+         can be changed within a session, such changes will not affect Tcl
+         interpreters that have already been created.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry id="guc-pltclu-start-proc" xreflabel="pltclu.start_proc">
+       <term>
+        <varname>pltclu.start_proc</varname> (<type>string</type>)
+        <indexterm>
+         <primary><varname>pltclu.start_proc</> configuration parameter</primary>
+        </indexterm>
+       </term>
+       <listitem>
+        <para>
+         This parameter is exactly like <varname>pltcl.start_proc</varname>,
+         except that it applies to PL/TclU.  The referenced function must
+         be written in the <literal>pltclu</> language.
+        </para>
+       </listitem>
+      </varlistentry>
+
+     </variablelist>
+    </sect1>
+
     <sect1 id="pltcl-procnames">
      <title>Tcl Procedure Names</title>

diff --git a/src/pl/tcl/Makefile b/src/pl/tcl/Makefile
index 453e7ad..1096c4f 100644
*** a/src/pl/tcl/Makefile
--- b/src/pl/tcl/Makefile
*************** DATA = pltcl.control pltcl--1.0.sql pltc
*** 28,34 ****
         pltclu.control pltclu--1.0.sql pltclu--unpackaged--1.0.sql

  REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=pltcl
! REGRESS = pltcl_setup pltcl_queries pltcl_unicode

  # Tcl on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
--- 28,34 ----
         pltclu.control pltclu--1.0.sql pltclu--unpackaged--1.0.sql

  REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=pltcl
! REGRESS = pltcl_setup pltcl_queries pltcl_start_proc pltcl_unicode

  # Tcl on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
diff --git a/src/pl/tcl/expected/pltcl_start_proc.out b/src/pl/tcl/expected/pltcl_start_proc.out
index ...0b8afb7 .
*** a/src/pl/tcl/expected/pltcl_start_proc.out
--- b/src/pl/tcl/expected/pltcl_start_proc.out
***************
*** 0 ****
--- 1,28 ----
+ --
+ -- Test start_proc execution
+ --
+ SET pltcl.start_proc = 'no_such_function';
+ select tcl_int4add(1, 2);
+ ERROR:  function no_such_function() does not exist
+ select tcl_int4add(1, 2);
+ ERROR:  function no_such_function() does not exist
+ create function tcl_initialize() returns void as
+ $$ elog NOTICE "in tcl_initialize" $$ language pltcl SECURITY DEFINER;
+ SET pltcl.start_proc = 'public.tcl_initialize';
+ select tcl_int4add(1, 2);  -- fail
+ ERROR:  function "public.tcl_initialize" must not be SECURITY DEFINER to use in pltcl.start_proc
+ create or replace function tcl_initialize() returns void as
+ $$ elog NOTICE "in tcl_initialize" $$ language pltcl;
+ select tcl_int4add(1, 2);
+ NOTICE:  in tcl_initialize
+  tcl_int4add
+ -------------
+            3
+ (1 row)
+
+ select tcl_int4add(1, 2);
+  tcl_int4add
+ -------------
+            3
+ (1 row)
+
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 11faa6d..a390b6e 100644
*** a/src/pl/tcl/pltcl.c
--- b/src/pl/tcl/pltcl.c
***************
*** 15,20 ****
--- 15,21 ----

  #include "access/htup_details.h"
  #include "access/xact.h"
+ #include "catalog/objectaccess.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
  #include "commands/event_trigger.h"
***************
*** 25,35 ****
--- 26,39 ----
  #include "mb/pg_wchar.h"
  #include "miscadmin.h"
  #include "nodes/makefuncs.h"
+ #include "parser/parse_func.h"
  #include "parser/parse_type.h"
+ #include "pgstat.h"
  #include "tcop/tcopprot.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
+ #include "utils/regproc.h"
  #include "utils/rel.h"
  #include "utils/syscache.h"
  #include "utils/typcache.h"
*************** typedef struct pltcl_call_state
*** 226,231 ****
--- 230,237 ----
  /**********************************************************************
   * Global data
   **********************************************************************/
+ static char *pltcl_start_proc = NULL;
+ static char *pltclu_start_proc = NULL;
  static bool pltcl_pm_init_done = false;
  static Tcl_Interp *pltcl_hold_interp = NULL;
  static HTAB *pltcl_interp_htab = NULL;
*************** static const TclExceptionNameMap excepti
*** 253,260 ****
   **********************************************************************/
  void        _PG_init(void);

! static void pltcl_init_interp(pltcl_interp_desc *interp_desc, bool pltrusted);
! static pltcl_interp_desc *pltcl_fetch_interp(bool pltrusted);

  static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted);

--- 259,268 ----
   **********************************************************************/
  void        _PG_init(void);

! static void pltcl_init_interp(pltcl_interp_desc *interp_desc,
!                   Oid prolang, bool pltrusted);
! static pltcl_interp_desc *pltcl_fetch_interp(Oid prolang, bool pltrusted);
! static void call_pltcl_start_proc(Oid prolang, bool pltrusted);

  static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted);

*************** _PG_init(void)
*** 441,446 ****
--- 449,472 ----
                                    &hash_ctl,
                                    HASH_ELEM | HASH_BLOBS);

+     /************************************************************
+      * Define PL/Tcl's custom GUCs
+      ************************************************************/
+     DefineCustomStringVariable("pltcl.start_proc",
+       gettext_noop("PL/Tcl function to call once when pltcl is first used."),
+                                NULL,
+                                &pltcl_start_proc,
+                                NULL,
+                                PGC_SUSET, 0,
+                                NULL, NULL, NULL);
+     DefineCustomStringVariable("pltclu.start_proc",
+     gettext_noop("PL/TclU function to call once when pltclu is first used."),
+                                NULL,
+                                &pltclu_start_proc,
+                                NULL,
+                                PGC_SUSET, 0,
+                                NULL, NULL, NULL);
+
      pltcl_pm_init_done = true;
  }

*************** _PG_init(void)
*** 448,454 ****
   * pltcl_init_interp() - initialize a new Tcl interpreter
   **********************************************************************/
  static void
! pltcl_init_interp(pltcl_interp_desc *interp_desc, bool pltrusted)
  {
      Tcl_Interp *interp;
      char        interpname[32];
--- 474,480 ----
   * pltcl_init_interp() - initialize a new Tcl interpreter
   **********************************************************************/
  static void
! pltcl_init_interp(pltcl_interp_desc *interp_desc, Oid prolang, bool pltrusted)
  {
      Tcl_Interp *interp;
      char        interpname[32];
*************** pltcl_init_interp(pltcl_interp_desc *int
*** 462,468 ****
      if ((interp = Tcl_CreateSlave(pltcl_hold_interp, interpname,
                                    pltrusted ? 1 : 0)) == NULL)
          elog(ERROR, "could not create slave Tcl interpreter");
-     interp_desc->interp = interp;

      /************************************************************
       * Initialize the query hash table associated with interpreter
--- 488,493 ----
*************** pltcl_init_interp(pltcl_interp_desc *int
*** 490,505 ****
                           pltcl_SPI_execute_plan, NULL, NULL);
      Tcl_CreateObjCommand(interp, "spi_lastoid",
                           pltcl_SPI_lastoid, NULL, NULL);
  }

  /**********************************************************************
   * pltcl_fetch_interp() - fetch the Tcl interpreter to use for a function
   *
   * This also takes care of any on-first-use initialization required.
-  * Note: we assume caller has already connected to SPI.
   **********************************************************************/
  static pltcl_interp_desc *
! pltcl_fetch_interp(bool pltrusted)
  {
      Oid            user_id;
      pltcl_interp_desc *interp_desc;
--- 515,549 ----
                           pltcl_SPI_execute_plan, NULL, NULL);
      Tcl_CreateObjCommand(interp, "spi_lastoid",
                           pltcl_SPI_lastoid, NULL, NULL);
+
+     /************************************************************
+      * Call the appropriate start_proc, if there is one.
+      *
+      * We must set interp_desc->interp before the call, else the start_proc
+      * won't find the interpreter it's supposed to use.  But, if the
+      * start_proc fails, we want to abandon use of the interpreter.
+      ************************************************************/
+     PG_TRY();
+     {
+         interp_desc->interp = interp;
+         call_pltcl_start_proc(prolang, pltrusted);
+     }
+     PG_CATCH();
+     {
+         interp_desc->interp = NULL;
+         Tcl_DeleteInterp(interp);
+         PG_RE_THROW();
+     }
+     PG_END_TRY();
  }

  /**********************************************************************
   * pltcl_fetch_interp() - fetch the Tcl interpreter to use for a function
   *
   * This also takes care of any on-first-use initialization required.
   **********************************************************************/
  static pltcl_interp_desc *
! pltcl_fetch_interp(Oid prolang, bool pltrusted)
  {
      Oid            user_id;
      pltcl_interp_desc *interp_desc;
*************** pltcl_fetch_interp(bool pltrusted)
*** 515,527 ****
                                HASH_ENTER,
                                &found);
      if (!found)
!         pltcl_init_interp(interp_desc, pltrusted);

      return interp_desc;
  }


  /**********************************************************************
   * pltcl_call_handler        - This is the only visible function
   *                  of the PL interpreter. The PostgreSQL
   *                  function manager and trigger manager
--- 559,654 ----
                                HASH_ENTER,
                                &found);
      if (!found)
!         interp_desc->interp = NULL;
!
!     /* If we haven't yet successfully made an interpreter, try to do that */
!     if (!interp_desc->interp)
!         pltcl_init_interp(interp_desc, prolang, pltrusted);

      return interp_desc;
  }


  /**********************************************************************
+  * call_pltcl_start_proc()     - Call user-defined initialization proc, if any
+  **********************************************************************/
+ static void
+ call_pltcl_start_proc(Oid prolang, bool pltrusted)
+ {
+     char       *start_proc;
+     List       *namelist;
+     Oid            fargtypes[1];    /* dummy */
+     Oid            procOid;
+     HeapTuple    procTup;
+     Form_pg_proc procStruct;
+     AclResult    aclresult;
+     FmgrInfo    finfo;
+     FunctionCallInfoData fcinfo;
+     PgStat_FunctionCallUsage fcusage;
+
+     /* select appropriate GUC */
+     start_proc = pltrusted ? pltcl_start_proc : pltclu_start_proc;
+
+     /* Nothing to do if it's empty or unset */
+     if (start_proc == NULL || start_proc[0] == '\0')
+         return;
+
+     /* Parse possibly-qualified identifier and look up the function */
+     namelist = stringToQualifiedNameList(start_proc);
+     procOid = LookupFuncName(namelist, 0, fargtypes, false);
+
+     /* Current user must have permission to call function */
+     aclresult = pg_proc_aclcheck(procOid, GetUserId(), ACL_EXECUTE);
+     if (aclresult != ACLCHECK_OK)
+         aclcheck_error(aclresult, ACL_KIND_PROC, start_proc);
+
+     /* Get the function's pg_proc entry */
+     procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procOid));
+     if (!HeapTupleIsValid(procTup))
+         elog(ERROR, "cache lookup failed for function %u", procOid);
+     procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+     /* It must be same language as the function we're currently calling */
+     if (procStruct->prolang != prolang)
+         ereport(ERROR,
+                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+               errmsg("function \"%s\" is in the wrong language to use in %s",
+                      start_proc,
+                      pltrusted ? "pltcl.start_proc" : "pltclu.start_proc")));
+
+     /*
+      * It must not be SECURITY DEFINER, either.  This together with the
+      * language match check ensures that the function will execute in the same
+      * Tcl interpreter we just finished initializing.
+      */
+     if (procStruct->prosecdef)
+         ereport(ERROR,
+                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+           errmsg("function \"%s\" must not be SECURITY DEFINER to use in %s",
+                  start_proc,
+                  pltrusted ? "pltcl.start_proc" : "pltclu.start_proc")));
+
+     /* A-OK */
+     ReleaseSysCache(procTup);
+
+     /*
+      * Call the function using the normal SQL function call mechanism.  We
+      * could perhaps cheat and jump directly to pltcl_handler(), but it seems
+      * better to do it this way so that the call is exposed to, eg, call
+      * statistics collection.
+      */
+     InvokeFunctionExecuteHook(procOid);
+     fmgr_info(procOid, &finfo);
+     InitFunctionCallInfoData(fcinfo, &finfo,
+                              0,
+                              InvalidOid, NULL, NULL);
+     pgstat_init_function_usage(&fcinfo, &fcusage);
+     (void) FunctionCallInvoke(&fcinfo);
+     pgstat_end_function_usage(&fcusage, true);
+ }
+
+
+ /**********************************************************************
   * pltcl_call_handler        - This is the only visible function
   *                  of the PL interpreter. The PostgreSQL
   *                  function manager and trigger manager
*************** compile_pltcl_function(Oid fn_oid, Oid t
*** 1319,1325 ****
          /************************************************************
           * Identify the interpreter to use for the function
           ************************************************************/
!         prodesc->interp_desc = pltcl_fetch_interp(prodesc->lanpltrusted);
          interp = prodesc->interp_desc->interp;

          /************************************************************
--- 1446,1453 ----
          /************************************************************
           * Identify the interpreter to use for the function
           ************************************************************/
!         prodesc->interp_desc = pltcl_fetch_interp(procStruct->prolang,
!                                                   prodesc->lanpltrusted);
          interp = prodesc->interp_desc->interp;

          /************************************************************
diff --git a/src/pl/tcl/sql/pltcl_start_proc.sql b/src/pl/tcl/sql/pltcl_start_proc.sql
index ...7a8e68e .
*** a/src/pl/tcl/sql/pltcl_start_proc.sql
--- b/src/pl/tcl/sql/pltcl_start_proc.sql
***************
*** 0 ****
--- 1,21 ----
+ --
+ -- Test start_proc execution
+ --
+
+ SET pltcl.start_proc = 'no_such_function';
+
+ select tcl_int4add(1, 2);
+ select tcl_int4add(1, 2);
+
+ create function tcl_initialize() returns void as
+ $$ elog NOTICE "in tcl_initialize" $$ language pltcl SECURITY DEFINER;
+
+ SET pltcl.start_proc = 'public.tcl_initialize';
+
+ select tcl_int4add(1, 2);  -- fail
+
+ create or replace function tcl_initialize() returns void as
+ $$ elog NOTICE "in tcl_initialize" $$ language pltcl;
+
+ select tcl_int4add(1, 2);
+ select tcl_int4add(1, 2);

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Bruce Momjian
Дата:
Сообщение: Re: [HACKERS] REINDEX CONCURRENTLY 2.0
Следующее
От: Thomas Munro
Дата:
Сообщение: Re: [HACKERS] make async slave to wait for lsn to be replayed