2016-03-16 123 views
1

我沒有看到這個bug在此實現:函數參數anyelement,PostgreSQL的bug?

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的bug或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! 

回答

1

你的問題是由於如何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或者在FROM列表中調用的設置返回函數(RETURNS SETOF ...而不是RETURNS boolean),則會觸發語法錯誤:SELECT * FROM foo()而不是SELECT foo()。對於可以內聯的簡單IMMUTABLE函數,似乎查詢計劃的處理方式不同。


除此之外,使用:

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

代替:

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

這是一般都比較好。最好是每次施放一次而不是每次計算的值。這也適用於類型名稱的已知別名。

1

這肯定涉及到SQL規劃器/優化。由於函數聲明爲IMMUTABLE,因此優化程序會嘗試預先評估查詢部分。出於某種原因,即使您使用text參數調用該函數,它也會評估$1::int>2的表達式。

如果您將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