* pltcl.c     - PostgreSQL support for Tcl as
  *               procedural language (PL)
  *
- *   $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.117 2007/11/15 21:14:46 momjian Exp $
+ *   $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.117.2.1 2008/06/17 00:52:49 tgl Exp $
  *
  **********************************************************************/
 
    ((TCL_MAJOR_VERSION > maj) || \
     (TCL_MAJOR_VERSION == maj && TCL_MINOR_VERSION >= min))
 
+/* In Tcl >= 8.0, really not supposed to touch interp->result directly */
+#if !HAVE_TCL_VERSION(8,0)
+#define Tcl_GetStringResult(interp)  ((interp)->result)
+#endif
+
 #if defined(UNICODE_CONVERSION) && HAVE_TCL_VERSION(8,1)
 
 #include "mb/pg_wchar.h"
 
 static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS);
 
+static void throw_tcl_error(Tcl_Interp *interp, const char *proname);
+
 static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid);
 
 static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
     * Check for errors reported by Tcl.
     ************************************************************/
    if (tcl_rc != TCL_OK)
-   {
-       UTF_BEGIN;
-       ereport(ERROR,
-               (errmsg("%s", interp->result),
-                errcontext("%s\nin PL/Tcl function \"%s\"",
-                           UTF_U2E(Tcl_GetVar(interp, "errorInfo",
-                                              TCL_GLOBAL_ONLY)),
-                           prodesc->user_proname)));
-       UTF_END;
-   }
+       throw_tcl_error(interp, prodesc->user_proname);
 
    /************************************************************
     * Disconnect from SPI manager and then create the return
     * this must not be allocated in the SPI memory context
     * because SPI_finish would free it).  But don't try to call
     * the result_in_func if we've been told to return a NULL;
-    * the contents of interp->result may not be a valid value of
-    * the result type in that case.
+    * the Tcl result may not be a valid value of the result type
+    * in that case.
     ************************************************************/
    if (SPI_finish() != SPI_OK_FINISH)
        elog(ERROR, "SPI_finish() failed");
    {
        UTF_BEGIN;
        retval = InputFunctionCall(&prodesc->result_in_func,
-                                  UTF_U2E(interp->result),
+                                  UTF_U2E((char *) Tcl_GetStringResult(interp)),
                                   prodesc->result_typioparam,
                                   -1);
        UTF_END;
    Datum      *modvalues;
    char       *modnulls;
    int         ret_numvals;
+   CONST84 char *result;
    CONST84 char **ret_values;
 
    /* Connect to SPI manager */
     * Check for errors reported by Tcl.
     ************************************************************/
    if (tcl_rc != TCL_OK)
-   {
-       UTF_BEGIN;
-       ereport(ERROR,
-               (errmsg("%s", interp->result),
-                errcontext("%s\nin PL/Tcl function \"%s\"",
-                           UTF_U2E(Tcl_GetVar(interp, "errorInfo",
-                                              TCL_GLOBAL_ONLY)),
-                           prodesc->user_proname)));
-       UTF_END;
-   }
+       throw_tcl_error(interp, prodesc->user_proname);
 
    /************************************************************
     * The return value from the procedure might be one of
-    * the magic strings OK or SKIP or a list from array get
+    * the magic strings OK or SKIP or a list from array get.
+    * We can check for OK or SKIP without worrying about encoding.
     ************************************************************/
    if (SPI_finish() != SPI_OK_FINISH)
        elog(ERROR, "SPI_finish() failed");
 
-   if (strcmp(interp->result, "OK") == 0)
+   result = Tcl_GetStringResult(interp);
+
+   if (strcmp(result, "OK") == 0)
        return rettup;
-   if (strcmp(interp->result, "SKIP") == 0)
+   if (strcmp(result, "SKIP") == 0)
        return (HeapTuple) NULL;
 
    /************************************************************
     * Convert the result value from the Tcl interpreter
     * and setup structures for SPI_modifytuple();
     ************************************************************/
-   if (Tcl_SplitList(interp, interp->result,
+   if (Tcl_SplitList(interp, result,
                      &ret_numvals, &ret_values) != TCL_OK)
+   {
+       UTF_BEGIN;
        elog(ERROR, "could not split return value from trigger: %s",
-            interp->result);
+            UTF_U2E(Tcl_GetStringResult(interp)));
+       UTF_END;
+   }
 
    /* Use a TRY to ensure ret_values will get freed */
    PG_TRY();
 }
 
 
+/**********************************************************************
+ * throw_tcl_error - ereport an error returned from the Tcl interpreter
+ **********************************************************************/
+static void
+throw_tcl_error(Tcl_Interp *interp, const char *proname)
+{
+   /*
+    * Caution is needed here because Tcl_GetVar could overwrite the
+    * interpreter result (even though it's not really supposed to),
+    * and we can't control the order of evaluation of ereport arguments.
+    * Hence, make real sure we have our own copy of the result string
+    * before invoking Tcl_GetVar.
+    */
+   char       *emsg;
+   char       *econtext;
+
+   UTF_BEGIN;
+   emsg = pstrdup(UTF_U2E(Tcl_GetStringResult(interp)));
+   UTF_END;
+   UTF_BEGIN;
+   econtext = UTF_U2E((char *) Tcl_GetVar(interp, "errorInfo",
+                                          TCL_GLOBAL_ONLY));
+   ereport(ERROR,
+           (errmsg("%s", emsg),
+            errcontext("%s\nin PL/Tcl function \"%s\"",
+                       econtext, proname)));
+   UTF_END;
+}
+
+
 /**********************************************************************
  * compile_pltcl_function  - compile (or hopefully just look up) function
  *
            free(prodesc->user_proname);
            free(prodesc->internal_proname);
            free(prodesc);
+           UTF_BEGIN;
            elog(ERROR, "could not create internal procedure \"%s\": %s",
-                internal_proname, interp->result);
+                internal_proname, UTF_U2E(Tcl_GetStringResult(interp)));
+           UTF_END;
        }
 
        /************************************************************
 
    if (argc != 3)
    {
-       Tcl_SetResult(interp, "syntax error - 'elog level msg'",
-                     TCL_VOLATILE);
+       Tcl_SetResult(interp, "syntax error - 'elog level msg'", TCL_STATIC);
        return TCL_ERROR;
    }
 
        return TCL_ERROR;
    }
 
-   /************************************************************
-    * If elog() throws an error, catch it and return the error to the
-    * Tcl interpreter.  Note we are assuming that elog() can't have any
+   if (level == ERROR)
+   {
+       /*
+        * We just pass the error back to Tcl.  If it's not caught,
+        * it'll eventually get converted to a PG error when we reach
+        * the call handler.
+        */
+       Tcl_SetResult(interp, (char *) argv[2], TCL_VOLATILE);
+       return TCL_ERROR;
+   }
+
+   /*
+    * For non-error messages, just pass 'em to elog().  We do not expect
+    * that this will fail, but just on the off chance it does, report the
+    * error back to Tcl.  Note we are assuming that elog() can't have any
     * internal failures that are so bad as to require a transaction abort.
-    ************************************************************/
+    *
+    * This path is also used for FATAL errors, which aren't going to come
+    * back to us at all.
+    */
    oldcontext = CurrentMemoryContext;
    PG_TRY();
    {
        FlushErrorState();
 
        /* Pass the error message to Tcl */
-       Tcl_SetResult(interp, edata->message, TCL_VOLATILE);
+       UTF_BEGIN;
+       Tcl_SetResult(interp, UTF_E2U(edata->message), TCL_VOLATILE);
+       UTF_END;
        FreeErrorData(edata);
 
        return TCL_ERROR;
     ************************************************************/
    if (argc != 2)
    {
-       Tcl_SetResult(interp, "syntax error - 'quote string'", TCL_VOLATILE);
+       Tcl_SetResult(interp, "syntax error - 'quote string'", TCL_STATIC);
        return TCL_ERROR;
    }
 
     ************************************************************/
    if (argc != 2)
    {
-       Tcl_SetResult(interp, "syntax error - 'argisnull argno'", TCL_VOLATILE);
+       Tcl_SetResult(interp, "syntax error - 'argisnull argno'",
+                     TCL_STATIC);
        return TCL_ERROR;
    }
 
    if (fcinfo == NULL)
    {
        Tcl_SetResult(interp, "argisnull cannot be used in triggers",
-                     TCL_VOLATILE);
+                     TCL_STATIC);
        return TCL_ERROR;
    }
 
    argno--;
    if (argno < 0 || argno >= fcinfo->nargs)
    {
-       Tcl_SetResult(interp, "argno out of range", TCL_VOLATILE);
+       Tcl_SetResult(interp, "argno out of range", TCL_STATIC);
        return TCL_ERROR;
    }
 
     * Get the requested NULL state
     ************************************************************/
    if (PG_ARGISNULL(argno))
-       Tcl_SetResult(interp, "1", TCL_VOLATILE);
+       Tcl_SetResult(interp, "1", TCL_STATIC);
    else
-       Tcl_SetResult(interp, "0", TCL_VOLATILE);
+       Tcl_SetResult(interp, "0", TCL_STATIC);
 
    return TCL_OK;
 }
     ************************************************************/
    if (argc != 1)
    {
-       Tcl_SetResult(interp, "syntax error - 'return_null'", TCL_VOLATILE);
+       Tcl_SetResult(interp, "syntax error - 'return_null'", TCL_STATIC);
        return TCL_ERROR;
    }
 
    if (fcinfo == NULL)
    {
        Tcl_SetResult(interp, "return_null cannot be used in triggers",
-                     TCL_VOLATILE);
+                     TCL_STATIC);
        return TCL_ERROR;
    }
 
    SPI_restore_connection();
 
    /* Pass the error message to Tcl */
-   Tcl_SetResult(interp, edata->message, TCL_VOLATILE);
+   UTF_BEGIN;
+   Tcl_SetResult(interp, UTF_E2U(edata->message), TCL_VOLATILE);
+   UTF_END;
    FreeErrorData(edata);
 }
 
     ************************************************************/
    if (argc < 2)
    {
-       Tcl_SetResult(interp, usage, TCL_VOLATILE);
+       Tcl_SetResult(interp, usage, TCL_STATIC);
        return TCL_ERROR;
    }
 
        {
            if (++i >= argc)
            {
-               Tcl_SetResult(interp, usage, TCL_VOLATILE);
+               Tcl_SetResult(interp, usage, TCL_STATIC);
                return TCL_ERROR;
            }
            arrayname = argv[i++];
        {
            if (++i >= argc)
            {
-               Tcl_SetResult(interp, usage, TCL_VOLATILE);
+               Tcl_SetResult(interp, usage, TCL_STATIC);
                return TCL_ERROR;
            }
            if (Tcl_GetInt(interp, argv[i++], &count) != TCL_OK)
    query_idx = i;
    if (query_idx >= argc || query_idx + 2 < argc)
    {
-       Tcl_SetResult(interp, usage, TCL_VOLATILE);
+       Tcl_SetResult(interp, usage, TCL_STATIC);
        return TCL_ERROR;
    }
    if (query_idx + 1 < argc)
        case SPI_OK_UTILITY:
            if (tuptable == NULL)
            {
-               Tcl_SetResult(interp, "0", TCL_VOLATILE);
+               Tcl_SetResult(interp, "0", TCL_STATIC);
                break;
            }
            /* FALL THRU for utility returning tuples */
    if (argc != 3)
    {
        Tcl_SetResult(interp, "syntax error - 'SPI_prepare query argtypes'",
-                     TCL_VOLATILE);
+                     TCL_STATIC);
        return TCL_ERROR;
    }
 
 
    ckfree((char *) args);
 
+   /* qname is ASCII, so no need for encoding conversion */
    Tcl_SetResult(interp, qdesc->qname, TCL_VOLATILE);
    return TCL_OK;
 }
        {
            if (++i >= argc)
            {
-               Tcl_SetResult(interp, usage, TCL_VOLATILE);
+               Tcl_SetResult(interp, usage, TCL_STATIC);
                return TCL_ERROR;
            }
            arrayname = argv[i++];
        {
            if (++i >= argc)
            {
-               Tcl_SetResult(interp, usage, TCL_VOLATILE);
+               Tcl_SetResult(interp, usage, TCL_STATIC);
                return TCL_ERROR;
            }
            nulls = argv[i++];
        {
            if (++i >= argc)
            {
-               Tcl_SetResult(interp, usage, TCL_VOLATILE);
+               Tcl_SetResult(interp, usage, TCL_STATIC);
                return TCL_ERROR;
            }
            if (Tcl_GetInt(interp, argv[i++], &count) != TCL_OK)
     ************************************************************/
    if (i >= argc)
    {
-       Tcl_SetResult(interp, usage, TCL_VOLATILE);
+       Tcl_SetResult(interp, usage, TCL_STATIC);
        return TCL_ERROR;
    }
 
        {
            Tcl_SetResult(interp,
                       "length of nulls string doesn't match # of arguments",
-                         TCL_VOLATILE);
+                         TCL_STATIC);
            return TCL_ERROR;
        }
    }
    {
        if (i >= argc)
        {
-           Tcl_SetResult(interp, "missing argument list", TCL_VOLATILE);
+           Tcl_SetResult(interp, "missing argument list", TCL_STATIC);
            return TCL_ERROR;
        }
 
        {
            Tcl_SetResult(interp,
               "argument list length doesn't match # of arguments for query",
-                         TCL_VOLATILE);
+                         TCL_STATIC);
            ckfree((char *) callargs);
            return TCL_ERROR;
        }
 
    if (i != argc)
    {
-       Tcl_SetResult(interp, usage, TCL_VOLATILE);
+       Tcl_SetResult(interp, usage, TCL_STATIC);
        return TCL_ERROR;
    }