From: Tom Lane Date: Sun, 6 Nov 2016 19:43:13 +0000 (-0500) Subject: Rationalize and document pltcl's handling of magic ".tupno" array element. X-Git-Tag: REL9_6_2~144 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=3af8467e9a5950f3ee908607867a008e0442f108;p=postgresql Rationalize and document pltcl's handling of magic ".tupno" array element. For a very long time, pltcl's spi_exec and spi_execp commands have had a behavior of storing the current row number as an element of output arrays, but this was never documented. Fix that. For an equally long time, pltcl_trigger_handler had a behavior of silently ignoring ".tupno" as an output column name, evidently so that the result of spi_exec could be used directly as a trigger result tuple. Not sure how useful that really is, but in any case it's bad that it would break attempts to use ".tupno" as an actual column name. We can fix it by not checking for ".tupno" until after we check for a column name match. This comports with the effective behavior of spi_exec[p] that ".tupno" is only magic when you don't have an actual column named that. In passing, wordsmith the description of returning modified tuples from a pltcl trigger. Noted while working on Jim Nasby's patch to support composite results from pltcl. The inability to return trigger tuples using ".tupno" as a column name is a bug, so back-patch to all supported branches. --- diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml index 805cc89dc9..52fc44940c 100644 --- a/doc/src/sgml/pltcl.sgml +++ b/doc/src/sgml/pltcl.sgml @@ -296,20 +296,22 @@ $$ LANGUAGE pltcl; If the command is a SELECT statement, the values of the result columns are placed into Tcl variables named after the columns. If the -array option is given, the column values are - instead stored into the named associative array, with the - column names used as array indexes. + instead stored into elements of the named associative array, with the + column names used as array indexes. In addition, the current row + number within the result (counting from zero) is stored into the array + element named .tupno, unless that name is + in use as a column name in the result. If the command is a SELECT statement and no loop-body script is given, then only the first row of results are stored into - Tcl variables; remaining rows, if any, are ignored. No storing occurs - if the - query returns no rows. (This case can be detected by checking the - result of spi_exec.) For example: + Tcl variables or array elements; remaining rows, if any, are ignored. + No storing occurs if the query returns no rows. (This case can be + detected by checking the result of spi_exec.) + For example: spi_exec "SELECT count(*) AS cnt FROM pg_proc" - will set the Tcl variable $cnt to the number of rows in the pg_proc system catalog. @@ -317,15 +319,15 @@ spi_exec "SELECT count(*) AS cnt FROM pg_proc" If the optional loop-body argument is given, it is a piece of Tcl script that is executed once for each row in the query result. (loop-body is ignored if the given - command is not a SELECT.) The values of the current row's columns - are stored into Tcl variables before each iteration. For example: - + command is not a SELECT.) + The values of the current row's columns + are stored into Tcl variables or array elements before each iteration. + For example: spi_exec -array C "SELECT * FROM pg_class" { elog DEBUG "have table $C(relname)" } - will print a log message for every row of pg_class. This feature works similarly to other Tcl looping constructs; in particular continue and break work in the @@ -667,21 +669,35 @@ SELECT 'doesn''t' AS ret The return value from a trigger procedure can be one of the strings - OK or SKIP, or a list as returned by the - array get Tcl command. If the return value is OK, - the operation (INSERT/UPDATE/DELETE) that fired the trigger will proceed + OK or SKIP, or a list of column name/value pairs. + If the return value is OK, + the operation (INSERT/UPDATE/DELETE) + that fired the trigger will proceed normally. SKIP tells the trigger manager to silently suppress the operation for this row. If a list is returned, it tells PL/Tcl to - return a modified row to the trigger manager. This is only meaningful + return a modified row to the trigger manager; the contents of the + modified row are specified by the column names and values in the list. + Any columns not mentioned in the list are set to null. + Returning a modified row is only meaningful for row-level BEFORE INSERT or UPDATE - triggers for which the modified row will be inserted instead of the one + triggers, for which the modified row will be inserted instead of the one given in $NEW; or for row-level INSTEAD OF INSERT or UPDATE triggers where the returned row - is used to support INSERT RETURNING and - UPDATE RETURNING commands. The return value is ignored for - other types of triggers. + is used as the source data for INSERT RETURNING or + UPDATE RETURNING clauses. + In row-level BEFORE DELETE or INSTEAD + OF DELETE triggers, returning a modified row has the same + effect as returning OK, that is the operation proceeds. + The trigger return value is ignored for all other types of triggers. + + + The result list can be made from an array representation of the + modified tuple with the array get Tcl command. + + + Here's a little example trigger procedure that forces an integer value in a table to keep track of the number of updates that are performed on the diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index 2a335aa219..34e62c3d99 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -1093,21 +1093,23 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted) Oid typioparam; FmgrInfo finfo; - /************************************************************ - * Ignore ".tupno" pseudo elements (see pltcl_set_tuple_values) - ************************************************************/ - if (strcmp(ret_name, ".tupno") == 0) - continue; - /************************************************************ * 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), @@ -2666,8 +2668,7 @@ pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname, const char *nullname = NULL; /************************************************************ - * Prepare pointers for Tcl_SetVar2() below and in array - * mode set the .tupno element + * Prepare pointers for Tcl_SetVar2() below ************************************************************/ if (arrayname == NULL) { @@ -2678,6 +2679,12 @@ pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname, { arrptr = &arrayname; nameptr = &attname; + + /* + * When outputting to an array, fill the ".tupno" element with the + * current tuple number. This will be overridden below if ".tupno" is + * in use as an actual field name in the rowtype. + */ Tcl_SetVar2Ex(interp, arrayname, ".tupno", Tcl_NewWideIntObj(tupno), 0); }