如果您希望避免修改代码并让函数NULL
在错误时返回,您可以通过将它们包装在使用BEGIN ... EXCEPTION
块来捕获错误的 PL/PgSQL 函数中来做到这一点。
为此,首先我得到错误的 SQLSTATE:
regress=# \set VERBOSITY verbose
regress=# SELECT pgp_sym_decrypt('fred','key');
ERROR: 39000: Wrong key or corrupt data
LOCATION: decrypt_internal, pgp-pgsql.c:607
我可以直接在错误处理程序中使用它,但我更喜欢使用符号名称,所以我在附录 A - 错误代码中查找与 39000 关联的错误名称,发现它是通用函数调用错误external_routine_invocation_exception
。不像我们希望的那样具体,但它会做到的。
现在需要一个包装函数。必须定义这样的东西,为pgp_sym_decrypt
您希望支持的每个重载签名使用一个函数。对于(bytea,text)
返回的表单,text
例如:
CREATE OR REPLACE FUNCTION pgp_sym_decrypt_null_on_err(data bytea, psw text) RETURNS text AS $$
BEGIN
RETURN pgp_sym_decrypt(data, psw);
EXCEPTION
WHEN external_routine_invocation_exception THEN
RAISE DEBUG USING
MESSAGE = format('Decryption failed: SQLSTATE %s, Msg: %s',
SQLSTATE,SQLERRM),
HINT = 'pgp_sym_encrypt(...) failed; check your key',
ERRCODE = 'external_routine_invocation_exception';
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
我已选择在DEBUG
级别消息中保留原始错误。这是原始和包装器的比较,具有完整的消息详细程度和调试级别输出。
启用调试输出以显示RAISE
. 请注意,它还显示pgp_decrypt_sym
调用的*原始查询文本,包括参数。
regress=# SET client_min_messages = DEBUG;
如果启用了详细日志记录,新包装的函数仍会报告错误,但会返回NULL
:
regress=# SELECT pgp_sym_decrypt_null_on_err('redsdfsfdsfd','bobsdf');
LOG: 00000: statement: SELECT pgp_sym_decrypt_null_on_err('redsdfsfdsfd','bobsdf');
LOCATION: exec_simple_query, postgres.c:860
DEBUG: 39000: Decryption failed: SQLSTATE 39000, Msg: Wrong key or corrupt data
HINT: pgp_sym_encrypt(...) failed; check your key
LOCATION: exec_stmt_raise, pl_exec.c:2806
pgp_sym_decrypt_null_on_err
-----------------------------
(1 row)
与原来的相比,它失败了:
regress=# SELECT pgp_sym_decrypt('redsdfsfdsfd','bobsdf');
LOG: 00000: statement: SELECT pgp_sym_decrypt('redsdfsfdsfd','bobsdf');
LOCATION: exec_simple_query, postgres.c:860
ERROR: 39000: Wrong key or corrupt data
LOCATION: decrypt_internal, pgp-pgsql.c:607
请注意,这两种形式都显示了函数在失败时调用的参数。如果您使用了绑定参数(“准备好的语句”),则不会显示这些参数,但如果您使用数据库内加密,您仍应将日志视为安全关键。
就个人而言,我认为最好在应用程序中进行加密,这样数据库就永远无法访问密钥。