Merge two copies of tuple-building code in pltcl.c.
authorTom Lane <[email protected]>
Fri, 6 Jan 2017 21:21:57 +0000 (16:21 -0500)
committerTom Lane <[email protected]>
Fri, 6 Jan 2017 21:22:08 +0000 (16:22 -0500)
Make pltcl_trigger_handler() construct modified tuples using
pltcl_build_tuple_result(), rather than its own copy of essentially
the same logic.  This results in slightly different message wording for
the error cases, and in one case a different SQLSTATE, but it seems
unlikely that any existing applications are depending on any of those
details.

While at it, fix a typo in commit 26abb50c4: pltcl_build_tuple_result was
applying encoding conversion in the wrong direction.  That would be a
back-patchable bug fix, except the code hasn't shipped yet.

Jim Nasby, reviewed by me

Discussion: https://postgr.es/m/d2c6425a-d9e0-f034-f774-4a872c234d89@BlueTreble.com

src/pl/tcl/pltcl.c

index 5cb4ee85e0045952be3511b838f735be2ba0300c..d813dcb3a61b590276b193c6bb3107516c3fca2c 100644 (file)
@@ -202,6 +202,9 @@ typedef struct pltcl_call_state
    /* Call info struct, or NULL in a trigger */
    FunctionCallInfo fcinfo;
 
+   /* Trigger data, if we're in a normal (not event) trigger; else NULL */
+   TriggerData *trigdata;
+
    /* Function we're executing (NULL if not yet identified) */
    pltcl_proc_desc *prodesc;
 
@@ -1000,8 +1003,8 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
    const char *result;
    int         result_Objc;
    Tcl_Obj   **result_Objv;
-   Datum      *values;
-   bool       *nulls;
+
+   call_state->trigdata = trigdata;
 
    /* Connect to SPI manager */
    if (SPI_connect() != SPI_OK_CONNECT)
@@ -1018,7 +1021,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
 
    interp = prodesc->interp_desc->interp;
 
-   tupdesc = trigdata->tg_relation->rd_att;
+   tupdesc = RelationGetDescr(trigdata->tg_relation);
 
    /************************************************************
     * Create the tcl command to call the internal
@@ -1219,69 +1222,9 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
                 errmsg("could not split return value from trigger: %s",
                        utf_u2e(Tcl_GetStringResult(interp)))));
 
-   if (result_Objc % 2 != 0)
-       ereport(ERROR,
-               (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
-        errmsg("trigger's return list must have even number of elements")));
-
-   values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
-   nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
-   memset(nulls, true, tupdesc->natts * sizeof(bool));
-
-   for (i = 0; i < result_Objc; i += 2)
-   {
-       char       *ret_name = utf_u2e(Tcl_GetString(result_Objv[i]));
-       char       *ret_value = utf_u2e(Tcl_GetString(result_Objv[i + 1]));
-       int         attnum;
-       Oid         typinput;
-       Oid         typioparam;
-       FmgrInfo    finfo;
-
-       /************************************************************
-        * Get the attribute number
-        *
-        * We silently ignore ".tupno", if it's present but doesn't match
-        * any actual output column.  This allows direct use of a row
-        * returned by pltcl_set_tuple_values().
-        ************************************************************/
-       attnum = SPI_fnumber(tupdesc, ret_name);
-       if (attnum == SPI_ERROR_NOATTRIBUTE)
-       {
-           if (strcmp(ret_name, ".tupno") == 0)
-               continue;
-           ereport(ERROR,
-                   (errcode(ERRCODE_UNDEFINED_COLUMN),
-                    errmsg("unrecognized attribute \"%s\"",
-                           ret_name)));
-       }
-       if (attnum <= 0)
-           ereport(ERROR,
-                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                    errmsg("cannot set system attribute \"%s\"",
-                           ret_name)));
-
-       /************************************************************
-        * Lookup the attribute type's input function
-        ************************************************************/
-       getTypeInputInfo(tupdesc->attrs[attnum - 1]->atttypid,
-                        &typinput, &typioparam);
-       fmgr_info(typinput, &finfo);
-
-       /************************************************************
-        * Set the attribute to NOT NULL and convert the contents
-        ************************************************************/
-       values[attnum - 1] = InputFunctionCall(&finfo,
-                                              ret_value,
-                                              typioparam,
-                                     tupdesc->attrs[attnum - 1]->atttypmod);
-       nulls[attnum - 1] = false;
-   }
-
-   /* Build the modified tuple to return */
-   rettup = heap_form_tuple(tupdesc, values, nulls);
-
-   pfree(values);
-   pfree(nulls);
+   /* Convert function result to tuple */
+   rettup = pltcl_build_tuple_result(interp, result_Objv, result_Objc,
+                                     call_state);
 
    return rettup;
 }
@@ -3014,6 +2957,8 @@ pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
  * pltcl_build_tuple_result() - Build a tuple of function's result rowtype
  *               from a Tcl list of column names and values
  *
+ * In a trigger function, we build a tuple of the trigger table's rowtype.
+ *
  * Note: this function leaks memory.  Even if we made it clean up its own
  * mess, there's no way to prevent the datatype input functions it calls
  * from leaking.  Run it in a short-lived context, unless we're about to
@@ -3023,24 +2968,44 @@ static HeapTuple
 pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc,
                         pltcl_call_state *call_state)
 {
+   TupleDesc   tupdesc;
+   AttInMetadata *attinmeta;
    char      **values;
    int         i;
 
+   if (call_state->ret_tupdesc)
+   {
+       tupdesc = call_state->ret_tupdesc;
+       attinmeta = call_state->attinmeta;
+   }
+   else if (call_state->trigdata)
+   {
+       tupdesc = RelationGetDescr(call_state->trigdata->tg_relation);
+       attinmeta = TupleDescGetAttInMetadata(tupdesc);
+   }
+   else
+   {
+       elog(ERROR, "PL/Tcl function does not return a tuple");
+       tupdesc = NULL;         /* keep compiler quiet */
+       attinmeta = NULL;
+   }
+
+   values = (char **) palloc0(tupdesc->natts * sizeof(char *));
+
    if (kvObjc % 2 != 0)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
        errmsg("column name/value list must have even number of elements")));
 
-   values = (char **) palloc0(call_state->ret_tupdesc->natts * sizeof(char *));
-
    for (i = 0; i < kvObjc; i += 2)
    {
-       char       *fieldName = utf_e2u(Tcl_GetString(kvObjv[i]));
-       int         attn = SPI_fnumber(call_state->ret_tupdesc, fieldName);
+       char       *fieldName = utf_u2e(Tcl_GetString(kvObjv[i]));
+       int         attn = SPI_fnumber(tupdesc, fieldName);
 
        /*
-        * As in pltcl_trigger_handler, silently ignore ".tupno" if it's in
-        * the list but doesn't match any column name.
+        * We silently ignore ".tupno", if it's present but doesn't match any
+        * actual output column.  This allows direct use of a row returned by
+        * pltcl_set_tuple_values().
         */
        if (attn == SPI_ERROR_NOATTRIBUTE)
        {
@@ -3058,10 +3023,10 @@ pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc,
                     errmsg("cannot set system attribute \"%s\"",
                            fieldName)));
 
-       values[attn - 1] = utf_e2u(Tcl_GetString(kvObjv[i + 1]));
+       values[attn - 1] = utf_u2e(Tcl_GetString(kvObjv[i + 1]));
    }
 
-   return BuildTupleFromCStrings(call_state->attinmeta, values);
+   return BuildTupleFromCStrings(attinmeta, values);
 }
 
 /**********************************************************************