Выберите и используйте один столбец - PullRequest
0 голосов
/ 23 января 2011

Пожалуйста, рассмотрите следующую (рабочую) хранимую процедуру.Эта функция получает целое число в качестве первого параметра, указывающего, какой privilegeid должен быть проверен у текущего пользователя.Привилегии хранятся в таблице privileges и состоят из идентификатора и имени (varchar).

Каждая привилегия принадлежит одному или нескольким roles, хранящимся в users_roles.Каждому пользователю назначается одна или несколько ролей.Эта функция извлекает все роли, назначенные current_user, и проверяет их, как сказано, по заданному привилигеиду.

CREATE OR REPLACE FUNCTION "public"."has_privilege" (in int4) RETURNS bool AS
$BODY$
DECLARE
    asked_privilegeid ALIAS FOR $1;
    userid int;
    role_row users_roles%rowtype;
    privilege_row privileges%rowtype;
BEGIN
    EXECUTE 'SELECT userid FROM users WHERE username=$1' INTO userid USING current_user;

    FOR role_row IN SELECT * FROM users_roles
    WHERE userid=userid 
    LOOP
    IF role_row.roleid = 1 THEN
        return TRUE;
    END IF;

    FOR privilege_row IN SELECT * FROM privileges WHERE roleid=role_row.roleid LOOP
        IF privilege_row.privilegeid = asked_privilegeid THEN
            return TRUE;
        END IF;
    END LOOP;
    END LOOP;

    return FALSE;
END
$BODY$
LANGUAGE 'plpgsql'

Однако этот код неэффективен, так как он может учитывать, что извлекает все значения строк для users_roles и privileges.Я попытался написать процедуру следующим образом, но она не работает:

CREATE OR REPLACE FUNCTION "public"."has_privilege" (in int4) RETURNS bool AS
$BODY$
DECLARE
    asked_privilegeid ALIAS FOR $1;
    privilegeid int;
    userid int;
    roleid int;

    //role_row users_roles%rowtype;
    //privilege_row privileges%rowtype;
BEGIN
    EXECUTE 'SELECT userid FROM users WHERE username=$1' INTO userid USING current_user;

    FOR roleid IN SELECT roleid FROM users_roles
    WHERE userid=userid 
    LOOP
    IF roleid = 1 THEN
        return TRUE;
    END IF;

    FOR privilegeid IN SELECT privilegeid FROM privileges WHERE roleid=roleid LOOP
        IF privilegeid = asked_privilegeid THEN
            return TRUE;
        END IF;
    END LOOP;
    END LOOP;

    return FALSE;
END
$BODY$
LANGUAGE 'plpgsql'

Что я делаю не так?Заранее спасибо!

Редактировать : отступы не были выполнены, как ожидалось.Вот ссылки для вставки: http://pastebin.com/w18WaCW0 http://pastebin.com/W8ewXxEe

Ответы [ 3 ]

2 голосов
/ 23 января 2011
DECLARE
    asked_privilegeid ALIAS FOR $1;
    _userid int; -- chage to avoid variable name same as column name
    role_row users_roles%rowtype;
    privilege_row privileges%rowtype;
BEGIN
    _userid := (select userid from users where username = session_user); -- if u use current_user u will have problem when function is defined as security definer

    FOR role_row IN SELECT * FROM users_roles WHERE userid = _userid -- your code is error becus userid is same as your variable name
    LOOP
    IF role_row.roleid = 1 THEN
        return TRUE;
    END IF;

    FOR privilege_row IN SELECT * FROM privileges WHERE roleid=role_row.roleid LOOP
        IF privilege_row.privilegeid = asked_privilegeid THEN
            return TRUE;
        END IF;
    END LOOP;
    END LOOP;

    return FALSE;
END

когда вы объявляете переменную в postgre, рекомендуется использовать имя переменной подчеркивания b4 '_userid'.чтобы отличить его от имени столбца

2 голосов
/ 23 января 2011

Проблема со строкой

WHERE userid=userid 

Неоднозначно, какой столбец таблицы и какая ваша переменная. Та же проблема здесь

FROM privileges WHERE roleid=roleid 

Не используйте имена переменных, которые также являются именами столбцов, на которые вы будете ссылаться

Вы также можете переписать тело PROC как прямой оператор SQL, который, вероятно, будет работать быстрее

CREATE OR REPLACE FUNCTION "public"."has_privilege" (in int4) RETURNS bool AS
$BODY$
DECLARE
    asked_privilegeid ALIAS FOR $1;
BEGIN

RETURN EXISTS (
    SELECT *
    FROM users u
    INNER JOIN users_roles r on r.userid=u.userid
    LEFT JOIN privileges p on p.roleid=r.roleid
       AND p.privilegeid = asked_privilegeid
       AND r.roleid <> 1 //  don't need to process this join if we already have our answer
    WHERE u.username = $1
      AND (r.roleid=1 OR p.privilegeid is not null))

END
$BODY$
LANGUAGE 'plpgsql'
1 голос
/ 23 января 2011

Я думаю, что это можно решить с помощью всего одного оператора SELECT:

SELECT count(*)
FROM privileges p
  JOIN roles r ON r.privilegeid = p.privilegeid 
  JOIN user_roles ur ON ur.roleid = r.roleid
  JOIN users u ON u.userid = ur.userid AND u.username = session_user
WHERE p.privilegeid = asked_privilegeid

(не проверено)

Если счетчик равен нулю, привилегия не назначается, в противном случае это.

...