2016-03-16 9 views
1

Я не вижу ошибку в этой реализации:Параметр функции anyelement, ошибка PostgreSQL?

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! 

ответ

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 или для функций set-return (RETURNS SETOF ... вместо RETURNS boolean), которые вызываются в списке FROM: SELECT * FROM foo() вместо SELECT foo(). Казалось бы, планирование запросов обрабатывается по-разному для простых функций IMMUTABLE, которые могут быть встроены.


Кроме, использование:

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

вместо:

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

Это обычно лучше. Всегда лучше использовать константу как только вместо вычисленного значения каждый раз. И это работает для известных псевдонимов названия типа.

1

Это определенно связано с SQL планировщик/оптимизатор. Поскольку функция объявлена ​​как IMMUTABLE, оптимизатор пытается предварительно оценить детали запроса.По какой-то причине он вычисляет выражение $1::int>2, даже если вы вызываете функцию с параметром text.

Если вы изменили свою функцию foo на VOLATILE, она будет работать нормально, потому что оптимизатор запросов не будет пытаться оптимизировать/предварительно оценить ее.

Но почему bar функция работает нормально, даже если это IMMUTABLE? Я предполагаю, что оптимизатор решает не предугадывать его, поскольку он не использует выражения в циклах. Я имею в виду, что $1::int>2 оценивается только один раз, тогда как функция foo оценивается несколько раз.


Похоже, есть некоторые отличия, как SQL планировщик работ для SQL и PLPGSQL языка. Та же функция в 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