37.5. 表达式 #
PL/pgSQL 语句中使用的所有表达式都由服务器常规的 SQL 执行器处理。看起来只包含常量的表达式实际上可能需要运行时求值(例如 timestamp 类型的
'now'),因此 PL/pgSQL
解析器无法识别 NULL 关键字之外的真实常量值。所有表达式都通过使用 SPI 管理器执行查询
SELECT expression
在内部求值。求值时,PL/pgSQL 变量标识符的出现被替换为参数,变量的实际值通过参数数组传递给执行器。这使
SELECT 的查询计划只需预备一次,之后即可重用于后续求值。
PostgreSQL 主解析器所做的求值对常量值的解释有一些副作用。具体来说,下面这两个函数的行为是有差异的:
CREATE FUNCTION logfunc1(text) RETURNS timestamp AS '
DECLARE
logtxt ALIAS FOR $1;
BEGIN
INSERT INTO logtable VALUES (logtxt, ''now'');
RETURN ''now'';
END;
' LANGUAGE plpgsql;和
CREATE FUNCTION logfunc2(text) RETURNS timestamp AS '
DECLARE
logtxt ALIAS FOR $1;
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_out 和 timestamp_in
函数把这个字符串转换为 timestamp 类型。这样,计算出的时间戳就会如程序员所期望的那样在每次执行时更新。
记录变量的可变性质在这方面带来一个问题。当记录变量的字段被用于表达式或语句中时,各字段的数据类型在同一表达式的多次调用之间不能改变,因为表达式将使用首次到达该表达式时出现的数据类型来规划。在编写处理多个表的事件的触发器过程时要记住这一点。(必要时可以用
EXECUTE 绕过这个问题。)