1

С следующей сохраненной функцией Я хотел бы проверить данные пользователя:Как гарантировать, что сохраненная функция всегда возвращает TRUE или FALSE?

CREATE OR REPLACE FUNCTION check_user(
     in_social integer, 
     in_sid varchar(255), 
     in_auth varchar(32)) 
     RETURNS boolean AS 
$func$ 
     SELECT MD5('secret word' || in_social || in_sid) = in_auth; 
$func$ LANGUAGE sql IMMUTABLE; 

Я буду называть его в то время как цикл через массив JSON объекты в других хранимых функциях - и буду RAISE EXCEPTION, если она возвращает FALSE для любого объектов JSON (и, таким образом, откат всей транзакции).

Вместо демпинг здесь исходный код моей 2-ой сохраненной функции, я подготовил 3 простых функций тестирования ниже -

CREATE OR REPLACE FUNCTION test1() RETURNS void AS 
$func$ 
BEGIN 
     IF NOT check_user(42, 'user1', '56db1046fa7b664c9b3d05bf7413552a') THEN 
       RAISE NOTICE 'invalid user'; 
     ELSE 
       RAISE NOTICE 'valid user'; 
     END IF; 
END 
$func$ LANGUAGE plpgsql; 

1-ая функция работает, как ожидалось и печатает valid user.

CREATE OR REPLACE FUNCTION test2() RETURNS void AS 
$func$ 
BEGIN 
     IF NOT check_user(42, 'user2', '56db1046fa7b664c9b3d05bf7413552a') THEN 
       RAISE NOTICE 'invalid user'; 
     ELSE 
       RAISE NOTICE 'valid user'; 
     END IF; 
END 
$func$ LANGUAGE plpgsql; 

2-я функция работает должным образом и печатает invalid user.

CREATE OR REPLACE FUNCTION test3() RETURNS void AS 
$func$ 
BEGIN 
     IF NOT check_user(42, 'user1', NULL) THEN 
       RAISE NOTICE 'invalid user'; 
     ELSE 
       RAISE NOTICE 'valid user'; 
     END IF; 
END 
$func$ LANGUAGE plpgsql; 

3-я функция не работает, как ожидалось и печатает valid user.

Это происходит потому, что check_user() возвращает NULL вместо булевского значения.

COALESCE можно обернуть вокруг check_user() позвонить в IF -statement ... но может быть, есть лучший способ решить эту проблему?

ответ

1

Проверить is [not] distinct from

select md5('secret word' || in_social || in_sid) is not distinct from in_auth; 

Но обратите внимание, что если обе стороны оценивают в null сравнение будет возвращать true:

Для непустых входов, отличается от такой же, как оператор> <. Однако, если оба входа равны нулю, он возвращает false, и если только один вход является нулевым, он возвращает true. Аналогично, IS NOT DISTINCT FROM идентичен = для непустых входов, но возвращает true, когда оба входа равны нулю, и false, когда только один вход является нулевым. Таким образом, эти конструкции эффективно действуют так, как будто значение null является нормальным значением данных, а не «неизвестным».

Даже проще, чтобы просто объявить функцию strict и сравнить возвращаемое значение с is not true:

if check_user(42, 'user1', null) is not true then raise notice 'invalid user'; 

STRICT указывает, что функция всегда возвращает нуль всякий раз, когда какой-либо из его аргументов равны нулю. Если этот параметр указан, функция не выполняется при наличии нулевых аргументов; вместо этого нулевой результат принимается автоматически.

Это дополнительное преимущество, позволяющее избежать каких-либо затрат на выполнение функции.

+0

Во втором утверждении (для функции 'STRICT') обязательно использовать' is not true' часть? Не могу ли я просто использовать 'if NOT check_user (42, 'user1', null), а затем вызывать уведомление« недопустимый пользователь »; end if; '? Спасибо –

+1

@AlexanderFarber: 'выражение не истинно' вернет true для' false' и 'null', а' not expression' будет возвращать 'true' только для' false', а не для 'null', что является вашей исходной проблемой. –

1

Я бы добавил coalesce внутри функции check_user(), поэтому он всегда возвращает true или false.

SELECT MD5('secret word' || in_social || in_sid) = coalesce(in_auth,''); 

Или даже вариант ниже в случае, если другие значения также могут быть NULL:

SELECT MD5('secret word' || coalesce(in_social,-1)::varchar || coalesce(in_sid,'')) = coalesce(in_auth,''); 

быть там «-1» значение, которое никогда не будет назначен in_social

0

Вот что я, вероятно, буду использовать, потому что функция связана с безопасностью, и поэтому я не хочу помнить какие-либо случаи с краем (например, обязательное использование IF check_user(...) IS NOT TRUE при объявлении его STRICT) при его использовании в будущем -

CREATE OR REPLACE FUNCTION check_user(
     in_social integer, 
     in_sid varchar(255), 
     in_auth varchar(32)) 
     RETURNS boolean AS 
$func$ 
     SELECT CASE 
       WHEN in_social IS NULL THEN FALSE 
       WHEN in_sid IS NULL THEN FALSE 
       WHEN in_auth IS NULL THEN FALSE 
       ELSE (MD5('secret word' || in_social || in_sid) = in_auth) 
     END; 

$func$ LANGUAGE sql IMMUTABLE;