函数参数anyelement,PostgreSQL错误?

时间:2016-03-16 02:40:18

标签: sql postgresql types query-planner polymorphic-functions

我没有看到此实现中的错误:

CREATE FUNCTION foo(anyelement) RETURNS SETOF int  AS $f$
    SELECT id FROM unnest(array[1,2,3]) t(id) 
    WHERE CASE WHEN (pg_typeof($1)::text)='integer' THEN $1::int>2 ELSE true END
$f$ LANGUAGE SQL IMMUTABLE;

SELECT * FROM foo(123); -- OK!
SELECT * FROM foo('test'::text); -- BUG

这是某种PostgreSQL错误还是anyelement数据类型的非文档限制?

有趣:孤立时CASE子句正常工作:

 CREATE FUNCTION bar(anyelement) RETURNS boolean  AS $f$
   SELECT CASE WHEN (pg_typeof($1)::text)='integer' THEN $1::int>2;
 $f$ LANGUAGE SQL IMMUTABLE;

 SELECT bar('test'::text), bar(123), bar(1); -- works fine! 

2 个答案:

答案 0 :(得分:4)

您的问题是由于SQL语句的计划方式。 SQL对数据类型非常严格。 Postgres函数为多态伪类型ANYELEMENT提供了一些灵活性,但SQL语句仍然是静态地使用给定类型进行规划。

如果$1::int>2不是$1,那么表达式integer永远不会执行(你可以避免以这种方式除以零),这无法拯救你来自规划查询的早期阶段出现的 语法 错误。

您仍然可以使用您拥有的功能 。使用无类型字符串文字:

CREATE OR REPLACE FUNCTION foo(anyelement)
  RETURNS SETOF int AS
 $func$
   SELECT id FROM unnest(array[1,2,3]) id
   WHERE  CASE WHEN pg_typeof($1) = 'integer'::regtype
               THEN $1 > '2'  -- use a string literal!
               ELSE true END
$func$ LANGUAGE sql IMMUTABLE;

这至少适用于所有字符和数字数据类型。字符串文字被强制转换为提供的数据类型。但对于其他数据类型,它仍然会失败,其中' 2'无效。

非凡,您的第二个示例不会触发语法错误。它来自我对Postgres 9.5的测试,如果函数不是IMMUTABLE,则会触发语法错误,或者在RETURNS SETOF ...中调用的集合返回函数(RETURNS boolean而不是FROM) {1}}列出:SELECT * FROM foo()而不是SELECT foo()。对于可以内联的简单IMMUTABLE函数,看起来查询计划的处理方式不同。

除此之外,请使用:

pg_typeof($1) = 'integer'::regtype

而不是:

<击> (pg_typeof($1)::text)='integer'

这通常更好。每次使用一次而不是计算值时,总是更好。这适用于类型名称的已知别名。

答案 1 :(得分:3)

它与SQL计划程序/优化程序完全相关。由于函数声明为IMMUTABLE,优化器会尝试预先评估查询部分。出于某种原因,即使您使用$1::int>2参数调用函数,它也会评估表达式text

如果您将foo功能更改为VOLATILE,它将正常工作,因为查询优化器不会尝试优化/预先评估它。

但是为什么bar函数即使它是IMMUTABLE也能正常工作?我想优化器决定不预先评估它,因为它不在循环中使用表达式。我的意思是$1::int>2仅被评估一次,而在foo函数中,它被评估多次。

似乎SQL计划程序对SQLPLPGSQL语言的工作方式存在一些差异。 PLPGSQL中的相同功能可以正常工作。

CREATE FUNCTION foo2(anyelement) RETURNS SETOF int AS $f$
DECLARE 
    i INTEGER;
BEGIN
    FOR i IN SELECT id FROM unnest(array[1,2,3]) t(id) 
        WHERE 
            CASE WHEN pg_typeof($1) = 'integer'::regtype 
                THEN $1::int > 2
                ELSE true END
    LOOP
        RETURN NEXT i;
    END LOOP;
END;
$f$ LANGUAGE plpgsql IMMUTABLE;

SELECT * FROM foo2('test'::text); -- works fine