pltcl_start_proc-1.patch
text/x-diff
Filename: pltcl_start_proc-1.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| doc/src/sgml/pltcl.sgml | 0 | 0 |
| src/pl/tcl/expected/pltcl_start_proc.out | 0 | 0 |
| src/pl/tcl/Makefile | 0 | 0 |
| src/pl/tcl/pltcl.c | 0 | 0 |
| src/pl/tcl/sql/pltcl_start_proc.sql | 0 | 0 |
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);