PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Passando o ID do usuário para gatilhos do PostgreSQL


As opções incluem:

  • Quando você abre uma conexão, CREATE TEMPORARY TABLE current_app_user(username text); INSERT INTO current_app_user(username) VALUES ('the_user'); . Em seguida, em seu gatilho, SELECT username FROM current_app_user para obter o nome de usuário atual, possivelmente como uma subconsulta.

  • Em postgresql.conf crie uma entrada para um GUC personalizado como my_app.username = 'unknown'; . Sempre que você criar uma conexão execute SET my_app.username = 'the_user'; . Em seguida, nos gatilhos, use o current_setting('my_app.username') função para obter o valor. Efetivamente, você está abusando do maquinário GUC para fornecer variáveis ​​de sessão. Leia a documentação apropriada para a versão do seu servidor, pois os GUCs personalizados foram alterados na versão 9.2 .

  • Ajuste seu aplicativo para que ele tenha funções de banco de dados para cada usuário do aplicativo. SET ROLE para esse usuário antes de fazer o trabalho. Isso não apenas permite que você use o current_user integrado função tipo variável para SELECT current_user; , também permite impor a segurança no banco de dados . Veja esta pergunta. Você pode fazer login diretamente como usuário em vez de usar SET ROLE , mas isso tende a dificultar o pool de conexões.

Em ambos os três casos em que você está agrupando conexões, você deve ter cuidado para DISCARD ALL; quando você retorna uma conexão com o pool. (Embora não esteja documentado, DISCARD ALL faz um RESET ROLE ).

Configuração comum para demonstrações:

CREATE TABLE tg_demo(blah text);
INSERT INTO tg_demo(blah) VALUES ('spam'),('eggs');

-- Placeholder; will be replaced by demo functions
CREATE OR REPLACE FUNCTION get_app_user() RETURNS text AS $$
SELECT 'unknown';
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION tg_demo_trigger() RETURNS trigger AS $$
BEGIN
    RAISE NOTICE 'Current user is: %',get_app_user();
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tg_demo_tg
AFTER INSERT OR UPDATE OR DELETE ON tg_demo 
FOR EACH ROW EXECUTE PROCEDURE tg_demo_trigger();

Usando um GUC:

  • Nas CUSTOMIZED OPTIONS seção de postgresql.conf , adicione uma linha como myapp.username = 'unknown_user' . Nas versões do PostgreSQL anteriores à 9.2, você também deve definir custom_variable_classes = 'myapp' .
  • Reinicie o PostgreSQL. Agora você poderá SHOW myapp.username e obtenha o valor unknown_user .

Agora você pode usar SET myapp.username = 'the_user'; quando você estabelece uma conexão, ou alternativamente SET LOCAL myapp.username = 'the_user'; depois de BEGIN ndo uma transação se você quiser que ela seja local de transação, o que é conveniente para conexões em pool.

O get_app_user definição da função:
CREATE OR REPLACE FUNCTION get_app_user() RETURNS text AS $$
    SELECT current_setting('myapp.username');
$$ LANGUAGE sql;

Demonstração usando SET LOCAL para o nome de usuário atual local da transação:
regress=> BEGIN;
BEGIN
regress=> SET LOCAL myapp.username = 'test_user';
SET
regress=> INSERT INTO tg_demo(blah) VALUES ('42');
NOTICE:  Current user is: test_user
INSERT 0 1
regress=> COMMIT;
COMMIT
regress=> SHOW myapp.username;
 myapp.username 
----------------
 unknown_user
(1 row)

Se você usar SET em vez de SET LOCAL a configuração não será revertida no momento da confirmação/reversão, portanto, é persistente durante a sessão. Ele ainda é redefinido por DISCARD ALL :
regress=> SET myapp.username = 'test';
SET
regress=> SHOW myapp.username;
 myapp.username 
----------------
 test
(1 row)

regress=> DISCARD ALL;
DISCARD ALL
regress=> SHOW myapp.username;
 myapp.username 
----------------
 unknown_user
(1 row)

Além disso, observe que você não pode usar SET ou SET LOCAL com parâmetros de ligação do lado do servidor. Se você quiser usar parâmetros de ligação ("declarações preparadas"), considere usar o formulário de função set_config(...) . Veja as funções de administração do sistema

Usando uma tabela temporária


Essa abordagem requer o uso de um gatilho (ou função auxiliar chamada por um gatilho, de preferência) que tenta ler um valor de uma tabela temporária que toda sessão deveria ter. Se a tabela temporária não puder ser localizada, um valor padrão será fornecido. Isso provavelmente será um pouco lento . Teste com atenção.

O get_app_user() definição:
CREATE OR REPLACE FUNCTION get_app_user() RETURNS text AS $$
DECLARE
    cur_user text;
BEGIN
    BEGIN
        cur_user := (SELECT username FROM current_app_user);
    EXCEPTION WHEN undefined_table THEN
        cur_user := 'unknown_user';
    END;
    RETURN cur_user;
END;
$$ LANGUAGE plpgsql VOLATILE;

Demonstração:
regress=> CREATE TEMPORARY TABLE current_app_user(username text);
CREATE TABLE
regress=> INSERT INTO current_app_user(username) VALUES ('testuser');
INSERT 0 1
regress=> INSERT INTO tg_demo(blah) VALUES ('42');
NOTICE:  Current user is: testuser
INSERT 0 1
regress=> DISCARD ALL;
DISCARD ALL
regress=> INSERT INTO tg_demo(blah) VALUES ('42');
NOTICE:  Current user is: unknown_user
INSERT 0 1

Variáveis ​​de sessão segura


Há também uma proposta para adicionar "variáveis ​​de sessão seguras" ao PostgreSQL. Estas são um pouco como variáveis ​​de pacote. A partir do PostgreSQL 12, o recurso não foi incluído, mas fique de olho e fale na lista de hackers se isso for algo que você precisa.

Avançado:seu próprio ramal com área de memória compartilhada


Para usos avançados, você pode até ter sua própria extensão C para registrar uma área de memória compartilhada e se comunicar entre back-ends usando chamadas de função C que lêem/gravam valores em um segmento DSA. Veja os exemplos de programação PostgreSQL para detalhes. Você precisará de conhecimento em C, tempo e paciência.