aboutsummaryrefslogtreecommitdiff
path: root/src/pl/tcl/pltcl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pl/tcl/pltcl.c')
-rw-r--r--src/pl/tcl/pltcl.c137
1 files changed, 111 insertions, 26 deletions
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 5b9c030c8d8..21b2b045933 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -124,19 +124,21 @@ typedef struct pltcl_interp_desc
* The pltcl_proc_desc struct itself, as well as all subsidiary data,
* is stored in the memory context identified by the fn_cxt field.
* We can reclaim all the data by deleting that context, and should do so
- * when the fn_refcount goes to zero. (But note that we do not bother
- * trying to clean up Tcl's copy of the procedure definition: it's Tcl's
- * problem to manage its memory when we replace a proc definition. We do
- * not clean up pltcl_proc_descs when a pg_proc row is deleted, only when
- * it is updated, and the same policy applies to Tcl's copy as well.)
+ * when the fn_refcount goes to zero. That will happen if we build a new
+ * pltcl_proc_desc following an update of the pg_proc row. If that happens
+ * while the old proc is being executed, we mustn't remove the struct until
+ * execution finishes. When building a new pltcl_proc_desc, we unlink
+ * Tcl's copy of the old procedure definition, similarly relying on Tcl's
+ * internal reference counting to prevent that structure from disappearing
+ * while it's in use.
*
* Note that the data in this struct is shared across all active calls;
* nothing except the fn_refcount should be changed by a call instance.
**********************************************************************/
typedef struct pltcl_proc_desc
{
- char *user_proname; /* user's name (from pg_proc.proname) */
- char *internal_proname; /* Tcl name (based on function OID) */
+ char *user_proname; /* user's name (from format_procedure) */
+ char *internal_proname; /* Tcl proc name (NULL if deleted) */
MemoryContext fn_cxt; /* memory context for this procedure */
unsigned long fn_refcount; /* number of active references */
TransactionId fn_xmin; /* xmin of pg_proc row */
@@ -1375,13 +1377,29 @@ throw_tcl_error(Tcl_Interp *interp, const char *proname)
*/
char *emsg;
char *econtext;
+ int emsglen;
emsg = pstrdup(utf_u2e(Tcl_GetStringResult(interp)));
econtext = utf_u2e(Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY));
+
+ /*
+ * Typically, the first line of errorInfo matches the primary error
+ * message (the interpreter result); don't print that twice if so.
+ */
+ emsglen = strlen(emsg);
+ if (strncmp(emsg, econtext, emsglen) == 0 &&
+ econtext[emsglen] == '\n')
+ econtext += emsglen + 1;
+
+ /* Tcl likes to prefix the next line with some spaces, too */
+ while (*econtext == ' ')
+ econtext++;
+
+ /* Note: proname will already contain quoting if any is needed */
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("%s", emsg),
- errcontext("%s\nin PL/Tcl function \"%s\"",
+ errcontext("%s\nin PL/Tcl function %s",
econtext, proname)));
}
@@ -1405,6 +1423,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
pltcl_proc_desc *old_prodesc;
volatile MemoryContext proc_cxt = NULL;
Tcl_DString proc_internal_def;
+ Tcl_DString proc_internal_name;
Tcl_DString proc_internal_body;
/* We'll need the pg_proc tuple in any case... */
@@ -1435,6 +1454,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
* function's pg_proc entry without changing its OID.
************************************************************/
if (prodesc != NULL &&
+ prodesc->internal_proname != NULL &&
prodesc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self))
{
@@ -1452,36 +1472,104 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
* Then we load the procedure into the Tcl interpreter.
************************************************************/
Tcl_DStringInit(&proc_internal_def);
+ Tcl_DStringInit(&proc_internal_name);
Tcl_DStringInit(&proc_internal_body);
PG_TRY();
{
bool is_trigger = OidIsValid(tgreloid);
- char internal_proname[128];
+ Tcl_CmdInfo cmdinfo;
+ const char *user_proname;
+ const char *internal_proname;
+ bool need_underscore;
HeapTuple typeTup;
Form_pg_type typeStruct;
char proc_internal_args[33 * FUNC_MAX_ARGS];
Datum prosrcdatum;
char *proc_source;
char buf[48];
+ pltcl_interp_desc *interp_desc;
Tcl_Interp *interp;
int i;
int tcl_rc;
MemoryContext oldcontext;
/************************************************************
- * Build our internal proc name from the function's Oid. Append
- * "_trigger" when appropriate to ensure the normal and trigger
- * cases are kept separate. Note name must be all-ASCII.
+ * Identify the interpreter to use for the function
+ ************************************************************/
+ interp_desc = pltcl_fetch_interp(procStruct->prolang, pltrusted);
+ interp = interp_desc->interp;
+
+ /************************************************************
+ * If redefining the function, try to remove the old internal
+ * procedure from Tcl's namespace. The point of this is partly to
+ * allow re-use of the same internal proc name, and partly to avoid
+ * leaking the Tcl procedure object if we end up not choosing the same
+ * name. We assume that Tcl is smart enough to not physically delete
+ * the procedure object if it's currently being executed.
+ ************************************************************/
+ if (prodesc != NULL &&
+ prodesc->internal_proname != NULL)
+ {
+ /* We simply ignore any error */
+ (void) Tcl_DeleteCommand(interp, prodesc->internal_proname);
+ /* Don't do this more than once */
+ prodesc->internal_proname = NULL;
+ }
+
+ /************************************************************
+ * Build the proc name we'll use in error messages.
+ ************************************************************/
+ user_proname = format_procedure(fn_oid);
+
+ /************************************************************
+ * Build the internal proc name from the user_proname and/or OID.
+ * The internal name must be all-ASCII since we don't want to deal
+ * with encoding conversions. We don't want to worry about Tcl
+ * quoting rules either, so use only the characters of the function
+ * name that are ASCII alphanumerics, plus underscores to separate
+ * function name and arguments. If what we end up with isn't
+ * unique (that is, it matches some existing Tcl command name),
+ * append the function OID (perhaps repeatedly) so that it is unique.
************************************************************/
+
+ /* For historical reasons, use a function-type-specific prefix */
if (is_event_trigger)
- snprintf(internal_proname, sizeof(internal_proname),
- "__PLTcl_proc_%u_evttrigger", fn_oid);
+ Tcl_DStringAppend(&proc_internal_name,
+ "__PLTcl_evttrigger_", -1);
else if (is_trigger)
- snprintf(internal_proname, sizeof(internal_proname),
- "__PLTcl_proc_%u_trigger", fn_oid);
+ Tcl_DStringAppend(&proc_internal_name,
+ "__PLTcl_trigger_", -1);
else
- snprintf(internal_proname, sizeof(internal_proname),
- "__PLTcl_proc_%u", fn_oid);
+ Tcl_DStringAppend(&proc_internal_name,
+ "__PLTcl_proc_", -1);
+ /* Now add what we can from the user_proname */
+ need_underscore = false;
+ for (const char *ptr = user_proname; *ptr; ptr++)
+ {
+ if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789_", *ptr) != NULL)
+ {
+ /* Done this way to avoid adding a trailing underscore */
+ if (need_underscore)
+ {
+ Tcl_DStringAppend(&proc_internal_name, "_", 1);
+ need_underscore = false;
+ }
+ Tcl_DStringAppend(&proc_internal_name, ptr, 1);
+ }
+ else if (strchr("(, ", *ptr) != NULL)
+ need_underscore = true;
+ }
+ /* If this name already exists, append fn_oid; repeat as needed */
+ while (Tcl_GetCommandInfo(interp,
+ Tcl_DStringValue(&proc_internal_name),
+ &cmdinfo))
+ {
+ snprintf(buf, sizeof(buf), "_%u", fn_oid);
+ Tcl_DStringAppend(&proc_internal_name, buf, -1);
+ }
+ internal_proname = Tcl_DStringValue(&proc_internal_name);
/************************************************************
* Allocate a context that will hold all PG data for the procedure.
@@ -1496,7 +1584,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
************************************************************/
oldcontext = MemoryContextSwitchTo(proc_cxt);
prodesc = (pltcl_proc_desc *) palloc0(sizeof(pltcl_proc_desc));
- prodesc->user_proname = pstrdup(NameStr(procStruct->proname));
+ prodesc->user_proname = pstrdup(user_proname);
MemoryContextSetIdentifier(proc_cxt, prodesc->user_proname);
prodesc->internal_proname = pstrdup(internal_proname);
prodesc->fn_cxt = proc_cxt;
@@ -1513,13 +1601,8 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
(procStruct->provolatile != PROVOLATILE_VOLATILE);
/* And whether it is trusted */
prodesc->lanpltrusted = pltrusted;
-
- /************************************************************
- * Identify the interpreter to use for the function
- ************************************************************/
- prodesc->interp_desc = pltcl_fetch_interp(procStruct->prolang,
- prodesc->lanpltrusted);
- interp = prodesc->interp_desc->interp;
+ /* Save the associated interpreter, too */
+ prodesc->interp_desc = interp_desc;
/************************************************************
* Get the required information for input conversion of the
@@ -1712,6 +1795,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
if (proc_cxt)
MemoryContextDelete(proc_cxt);
Tcl_DStringFree(&proc_internal_def);
+ Tcl_DStringFree(&proc_internal_name);
Tcl_DStringFree(&proc_internal_body);
PG_RE_THROW();
}
@@ -1740,6 +1824,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
}
Tcl_DStringFree(&proc_internal_def);
+ Tcl_DStringFree(&proc_internal_name);
Tcl_DStringFree(&proc_internal_body);
ReleaseSysCache(procTup);