23.4. 表达式 #
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 绕开这个问题。)