36.5. 表达式

所有在PL/pgSQL 语句里使用的表达式都是用服务器的普通SQL执行器进行处理的。 实际上,类似下面的查询

SELECT expression

是使用 SPI 管理器执行的。 在计算之前,出现PL/pgSQL变量标识符的地方先被参数代替, 然后变量的实际值放在参数数组里传递给执行器。 这样就允许SELECT的执行计划只需要准备一次,并且在随后的计算中复用。

PostgreSQL 的主分析器做的类型检查对常量数值的代换有一些副作用。 详细说来就是下面这两个函数做的事情有些区别:

CREATE FUNCTION logfunc1 (logtxt text) RETURNS timestamp AS $$
    BEGIN
        INSERT INTO logtable VALUES (logtxt, 'now');
        RETURN 'now';
    END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION logfunc2 (logtxt text) RETURNS timestamp AS $$
    DECLARE
        curtime timestamp;
    BEGIN
        curtime := 'now';
        INSERT INTO logtable VALUES (logtxt, curtime);
        RETURN curtime;
    END;
$$ LANGUAGE plpgsql;

logfunc1() 的实例里, PostgreSQL 的主分析器在为 INSERT 准备执行计划的时候知道字串 'now' 应该解释成 timestamp 类型,因为 logtable 的目标字段就是该类型。所以,它会在这个时候从这个字串中计算一个常量, 然后在该服务器的整个生存期中的所有 logfunc1 调用中使用这个常量。不消说,这可不是程序员想要的。

logfunc2里, PostgreSQL 的主分析器并不知道 now 应该转换成什么类型, 因此它返回一个包含字符串 now 的类型为 text 的数据值。 在随后给局部变量curtime赋值时, PL/pgSQL解释器通过调用 text_outtimestamp_in 把这个字符串转换成 timestamp 类型的变量。 因此,计算出的时戳就会按照程序员希望的那样在每次执行的时候都更新。

记录变量的易变性天性在这种结合上提出了一个问题。 在一个记录变量在语句或者表达式中使用时, 该字段的数据类型在同一个表达式的不同调用期间不能修改, 因为该表达式准备使用的是运行第一次到达该表达式时出现的数据类型。 在写处理超过一个表的事件的触发器过程的时候一定要把这个记住。(必要时可以用EXECUTE绕开这个问题。)