Re: When use triggers?

Поиск
Список
Период
Сортировка
От Berend Tober
Тема Re: When use triggers?
Дата
Msg-id 859061db-18b4-e3a8-69d3-f5e24f036ba4@computer.org
обсуждение исходный текст
Ответ на Re: When use triggers?  (Michael Stephenson <domehead100@gmail.com>)
Список pgsql-general
Michael Stephenson wrote:
On Wed, May 16, 2018 at 6:36 PM, Adrian Klaver
<adrian.klaver@aklaver.com> wrote:
On 05/16/2018 03:19 PM, hmidi slim wrote:
HI,

I'm working on a microservice application and I avoid using triggers
because they will not be easy to maintain and need an experimented person in
database administration to manage them. ...
Two benefits triggers and their associated functions offer, with the
understanding these are general statements:

1) Are in the database so tasks that always need happen do not need to be
replicated across the various interfaces that touch the database.

2) Also since they run on the server the data does not necessarily cross a
network, so less network bandwidth and better performance.

An example of a use case is table auditing. If you need to track what is
being INSERTed/UPDATEd/DELETEd in a table stick an audit trigger/function on
the table and push the information to an audit table.

As to managing, they are like any other code. I keep my schema code in
scripts under version control and deploy them from there. I use
Sqitch(http://sqitch.org/) which allows me to deploy and revert changes. I
use a dev database to test and troubleshoot triggers and functions.

The only appropriate use for triggers that I've ever found was for
auditing changes to tables. ...


A great use case for triggers and stored functions is data validation or cleaning. Sure, this could be done in the end-user application ... or, that is, in *all* end-user applications if you don't mind duplicating algorithmic implementations and you trust the applications to do it properly and the same way every time and non-maliciously.

Triggering a before-insert function allows for a single implementation to be used consistently for all inserts.

Also, while appropriately limiting access privileges for end-users, triggers can invoke functions that do stuff at a higher privilege level safely, such as, for example, creating data base roles for new users.

Here's a complicated but cool example:

First, there is a publicly-exposed but non-data-leaking view that allows unprivileged users to initiate inserts for account creation (and by unprivileged I mean really unprivileged, that is, end users that don't even have an associated database login role when they do the insert):


CREATE OR REPLACE VIEW public.fairian AS
 SELECT NULL::name AS fairian_name,
    NULL::name AS passwd,
    NULL::name AS email_address;

GRANT USAGE ON SCHEMA public TO public;
GRANT SELECT, INSERT ON TABLE public.fairian TO public;


A rule redirects inserts on the public dummy view to a protected intermediate view (i.e., a view contained in non-publicly-visible schema) on which new and unprivileged users do not have read, write, or update privileges, but since relations that are used due to rules get checked against the privileges of the rule owner, not the user invoking the rule, this actually works:

REVOKE ALL ON schema protected  FROM public;

CREATE OR REPLACE RULE fairian_iir AS
    ON INSERT TO public.fairian DO INSTEAD 
    INSERT INTO protected.fairian (fairian_name, passwd, email_address)
    VALUES (new.fairian_name, new.passwd, new.email_address);


The redirected insert triggers a function that drills the insert down one level further to the actual table that is visible only to the data base owner:


CREATE TRIGGER fairian_iit
  INSTEAD OF INSERT
  ON protected.fairian
  FOR EACH ROW
  EXECUTE PROCEDURE protected.fairian_iit();

CREATE OR REPLACE FUNCTION protected.fairian_iit()
  RETURNS trigger AS
$BODY$
BEGIN
    -- Note, the password is not actually stored but there is some
    -- validation ... look at the private trigger function

    INSERT INTO private.fairian (fairian_name, passwd, email_address)
        VALUES (new.fairian_name, new.passwd, new.email_address);

  RETURN NEW;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE SECURITY DEFINER;


When the insert is finally pushed all the way down to the actual table in the (non-publicly visible) private schema, there's a trigger function which prevents account name collisions by serializing inserts, that ensures the credentials meet certain requirements such as containing no white space and starting with a character, and (although omitted below) sanitizes against SQL injection:


REVOKE ALL ON schema private  FROM public;

CREATE TRIGGER fairian_bit
  BEFORE INSERT
  ON private.fairian
  FOR EACH ROW
  EXECUTE PROCEDURE private.fairian_bit();


CREATE OR REPLACE FUNCTION private.fairian_bit()
  RETURNS trigger AS
$BODY$
    DECLARE
      _fairwinds private.fairwinds;
    BEGIN
      -- Serializes account creation
      select * into _fairwinds from private.fairwinds for update;

    -- Deny white space characters
   
    if position(' ' in new.fairian_name) > 0 then
        raise exception 'Fairian name may not contain spaces.';
    end if;
    if position(' ' in new.passwd) > 0 then
        raise exception 'Password may not contain spaces.';
    end if;
    if position(' ' in new.email_address) > 0 then
        raise exception 'E-mail address may not contain spaces.';
    end if;

    if not (select new.fairian_name similar to '[a-zA-Z]%') then
        RAISE EXCEPTION 'Fairian name must begin with a letter.';
    end if;

    --
    -- Anti-SQL-injection cleaning code omitted here
    -- 
      
    EXECUTE 'CREATE ROLE ' || new.fairian_name || ' WITH
        LOGIN
        INHERIT
        NOSUPERUSER
        NOCREATEDB
        NOCREATEROLE
        ENCRYPTED
        PASSWORD ' || quote_literal(new.passwd) || ' IN GROUP fairwinds';

    EXECUTE 'ALTER USER ' || new.fairian_name || ' SET search_path = privileged,public,pg_temp;';

    -- Do not store the plaintext password
   
    NEW.passwd = NULL;
        RETURN NEW;
    END;
    $BODY$
  LANGUAGE plpgsql VOLATILE SECURITY DEFINER;


Whew!


В списке pgsql-general по дате отправления:

Предыдущее
От: Ken Tanzer
Дата:
Сообщение: Re: When use triggers?
Следующее
От: Justin Pryzby
Дата:
Сообщение: postgres=# VACUUM FULL pg_statistic => ERROR: missing chunk number 0for toast value .. in pg_toast_2619