2011-08-18 85 views
1

在開始使用Sybase和SQL Server多年之後,我開始了一個PostgreSQL項目,並且對它的怪癖很陌生,但到目前爲止印象非常深刻。在PostgreSQL觸發器中分配變量以輸出參數

我寫了一個返回OUT參數的小函數。我傳入一個清除密碼,該函數返回加密的密碼和鹽。那就是:

CREATE OR REPLACE FUNCTION iSecGenPwdSalt(pwdin text, out salt text, out userpassword text) 
AS 
$BODY$ 
DECLARE 

    BEGIN 
     -- Generate a salt... 
     salt = gen_salt('bf', 10); 

     -- Now generate the password hash... 
     userpassword = crypt(pwdin, salt); 
    END; 
$BODY$ 
    LANGUAGE plpgsql VOLATILE 
    COST 100; 
ALTER FUNCTION iSecGenPwdSalt(text, out text , out text) OWNER TO postgres; 

我想打電話觸發器中此功能,並使用值來填充密碼和鹽列我有,但我不能完全理解的語法。我已經嘗試過多個主題的變體,但我仍然遇到問題。

select salt, userpassword from iSecGenPwdSalt('mymagicpassword');

它的結果漂亮,所以我想調用該函數並返回結果爲一對變量觸發,所以我可以分配值回報。

這裏是我的代碼:

SELECT usersalt=salt, userpwd=userpassword 
    FROM iSecGenPwdSalt(NEW.systemuserpassword); 

我得到的錯誤是這樣的:

ERROR: query has no destination for result data HINT: If you want to discard the results of a SELECT, use PERFORM instead. CONTEXT: PL/pgSQL function "isec_trg_systemuser" line 13 at SQL statement

所以,簡單地分配給一對變量,從OUT參數觸發得出一個結果是造成了我的悲傷。

回答

1

在你的具體情況,我認爲,你可以使用SELECT INTO語句多列:

CREATE OR REPLACE FUNCTION userAuth_insert_f() RETURNS TRIGGER AS $$ 
BEGIN 
    SELECT INTO NEW.usersalt, NEW.userpwd 
     salt, userpassword FROM iSecGenPwdSalt(NEW.systemuserpassword); 
    RETURN NEW; 
END; 
$$ LANGUAGE plpgsql; 

完整的示例(使用pgcrypto的contrib模塊):

DROP TABLE IF EXISTS userAuth; 
CREATE TABLE userAuth (
    id serial, 
    systemuserpassword text, 
    usersalt text, 
    userpwd text 
); 

CREATE OR REPLACE FUNCTION iSecGenPwdSalt 
    (pwdin text, OUT salt text, OUT userpassword text) 
AS $$ BEGIN 
    salt = gen_salt('bf', 10); 
    userpassword = crypt(pwdin, salt); 
END; 
$$ LANGUAGE plpgsql; 

DROP TRIGGER IF EXISTS userAuth_insert_t ON userAuth; 
CREATE TRIGGER userAuth_insert_t 
    BEFORE INSERT ON userAuth 
    FOR EACH ROW 
    EXECUTE PROCEDURE userAuth_insert_f(); 

調用:

=>INSERT INTO userAuth (systemuserpassword) VALUES ('mymagicpassword'); 
INSERT 0 1 
=>SELECT usersalt, userpwd FROM userauth; 
      usersalt   |       userpwd     
-------------------------------+--------------------------------------------------- 
$2a$10$7FqU5a/DrM.CWGlQPlGz6e | $2a$10$7FqU5a/DrM.CWGlQPlGz6eLjuN3UTwh/hlgxTnogS.. 
(1 row) 
+0

我認爲你是對的 - 我必須改變我解決問題的方式。也許我在思考Sybase/SQL Server選擇變量的思路。 SELECT INTO NEW.columnname似乎是一個很好的解決方法,也許是比我想實現的更好的方法。 – Noodle

1

這不是你問的問題的答案,但最近我不得不做一個類似的事情,而我用視圖和規則來代替觸發器。我希望這是有幫助的,儘管沒有真正回答你直接詢問的問題。

這裏是我的用戶表和視圖:

CREATE TABLE users_t (
    username VARCHAR(16) PRIMARY KEY, 
    email VARCHAR UNIQUE, 
    password CHAR(60), 
    salt  CHAR(29) 
); 

CREATE VIEW users AS SELECT username, email, '********'::varchar AS password FROM users_t; 

插入規則從生成鹽值的護理:

CREATE RULE users_insert_rule AS ON INSERT TO users DO INSTEAD 
INSERT INTO users_t (username, email, password, salt) 
    SELECT new.username, new.email, crypt(new.password, salt.salt), salt.salt 
    FROM (SELECT gen_salt('bf') as salt) salt 
RETURNING username, email, '********'::varchar; 

爲了完整性,我包括更新和刪除規則,甚至儘管它們並不特別相關:

CREATE RULE users_update_rule AS ON UPDATE TO users DO INSTEAD 
    UPDATE users_t 
    SET 
    username = new.username, 
    email = new.email, 
    password = crypt(new.password, salt) 
RETURNING username, email, '********'::varchar; 

CREATE RULE users_delete_rule AS ON DELETE TO users DO INSTEAD 
    DELETE FROM users_t WHERE username = old.username 
RETURNING username, email, '********'::varchar; 

然後我寫了一個認證函數w HICH負責使用鹽:

CREATE OR REPLACE FUNCTION authenticate(varchar, varchar) RETURNS setof users AS $$ 
SELECT * FROM users 
WHERE username = 
    (SELECT username FROM users_t 
    WHERE username = $1 AND crypt($2, salt) = password) 
$$ LANGUAGE sql; 

這樣你就可以「登錄」通過查詢SELECT * FROM authenticate('username', 'password');和您的用戶是通過插入users視圖中創建。您可以設置權限,使您擁有可以查看用戶視圖但不包含底層users_t表的非特權角色。通過這種方式,您可以輕鬆地與使用鹽密碼的用戶表進行交互,而無需處理應用程序中的鹽。

+0

非常感謝。我認爲你和Grzegorz Szpetkowski在這裏提出的解決方案非常實用,都指向SELECT INTO NEW.columnname方法,而不是中間變量。 – Noodle