web-dev-qa-db-fra.com

Comment utiliser les paramètres variables dans les fonctions de déclenchement?

Je voudrais enregistrer l'ID d'un utilisateur dans la session/transaction, en utilisant SET, afin que je puisse y accéder plus tard dans une fonction de déclenchement, en utilisant current_setting. Fondamentalement, j'essaie l'option n2 à partir d'un ticket très similaire publié précédemment , à la différence que j'utilise PG 10.1.

J'ai essayé 3 approches pour définir la variable:

  • SET local myvars.user_id = 4, Le définissant ainsi localement dans la transaction;
  • SET myvars.user_id = 4, Le définissant ainsi dans la session;
  • SELECT set_config('myvars.user_id', '4', false), qui en fonction du dernier argument, sera un raccourci pour les 2 options précédentes.

Aucun d'entre eux n'est utilisable dans le déclencheur, qui reçoit NULL lors de l'obtention de la variable via current_setting. Voici un script que j'ai conçu pour le dépanner (peut être facilement utilisé avec l'image docker postgres):

database=$POSTGRES_DB
user=$POSTGRES_USER
[ -z "$user" ] && user="postgres"

psql -v ON_ERROR_STOP=1 --username "$user" $database <<-EOSQL
    DROP TRIGGER IF EXISTS add_transition1 ON houses;
    CREATE TABLE IF NOT EXISTS houses (
        id SERIAL NOT NULL,
        name VARCHAR(80),
        created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
        PRIMARY KEY(id)
    );

    CREATE TABLE IF NOT EXISTS transitions1 (
        id SERIAL NOT NULL,
        house_id INTEGER,
        user_id INTEGER,
        created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
        PRIMARY KEY(id),
        FOREIGN KEY(house_id) REFERENCES houses (id) ON DELETE CASCADE

    );

    CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS \$\$
        DECLARE
            user_id integer;
        BEGIN
            user_id := current_setting('myvars.user_id')::integer || NULL;
            INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
            RETURN NULL;
        END;
    \$\$ LANGUAGE plpgsql;

    CREATE TRIGGER add_transition1 AFTER INSERT OR UPDATE ON houses FOR EACH ROW EXECUTE PROCEDURE add_transition1();

    BEGIN;
    %1% SELECT current_setting('myvars.user_id');
    %2% SELECT set_config('myvars.user_id', '55', false);
    %3% SELECT current_setting('myvars.user_id');
    INSERT INTO houses (name) VALUES ('HOUSE PARTY') RETURNING houses.id;
    SELECT * from houses;
    SELECT * from transitions1;
    COMMIT;
    DROP TRIGGER IF EXISTS add_transition1 ON houses;
    DROP FUNCTION IF EXISTS add_transition1;
    DROP TABLE transitions1;
        DROP TABLE houses;
EOSQL

La conclusion à laquelle je suis arrivé est que la fonction est déclenchée dans une transaction et une session (?) Différentes. Est-ce quelque chose que l'on peut configurer pour que tout se passe dans le même contexte?

13
ChuckE

Traitez tous les cas possibles pour le option personnalisée correctement:

  1. option pas encore définie

    Toutes les références à celui-ci déclenchent une exception , y compris current_setting() sauf si elles sont appelées avec le deuxième paramètre missing_ok. Le manuel:

    S'il n'y a aucun paramètre nommé setting_name, current_setting Génère une erreur sauf si missing_ok est fourni et est true.

  2. option définie sur un littéral entier valide

  3. option définie sur un littéral entier non valide

  4. réinitialisation de l'option (qui se résume à un cas spécial de 3.)

    Par exemple, si vous définissez une option personnalisée avec SET LOCAL Ou set_config('myvars.user_id3', '55', true), la valeur de l'option est réinitialisée à la fin de la transaction. Il est toujours existe, peut être référencé, mais il renvoie maintenant une chaîne vide ('') - qui ne peut pas être convertie en integer.

Mis à part les erreurs évidentes dans votre démo, vous devez vous préparer aux 4 cas. Alors:

CREATE OR REPLACE FUNCTION add_transition1()
  RETURNS trigger AS
$func$
DECLARE
   _user_id text := current_setting('myvars.user_id', true);  -- see 1.
BEGIN
   IF _user_id ~ '^\d+$' THEN  -- one or more digits?

      INSERT INTO transitions1 (user_id, house_id)
      VALUES (_user_id::int, NEW.id);  -- valid int, cast is safe

   ELSE

      INSERT INTO transitions1 (user_id, house_id)
      VALUES (NULL, NEW.id);           -- use NULL instead

      RAISE WARNING 'Invalid user_id % for house_id % was reset to NULL!'
                  , quote_literal(_user_id), NEW.id;  -- optional
   END IF;

   RETURN NULL;  -- OK for AFTER trigger
END
$func$  LANGUAGE plpgsql;

db <> violon ici

Remarques:

  • Évitez les noms de variables qui correspondent aux noms de colonne. Très sujet aux erreurs. Une convention de dénomination populaire consiste à ajouter des noms de variable avec un trait de soulignement: _user_id.

  • Attribuer au moment de la déclaration pour enregistrer une affectation. Notez le type de données text. Nous diffuserons plus tard, après avoir trié les entrées non valides.

  • Évitez de lever/piéger une exception si possible. Le manuel:

    Un bloc contenant une clause EXCEPTION est beaucoup plus cher à entrer et à quitter qu'un bloc sans un. Par conséquent, n'utilisez pas EXCEPTION sans besoin.

  • Testez les chaînes entières valides. Cette expression régulière simple n'autorise que les chiffres (pas de signe de tête, pas d'espace blanc): _user_id ~ '^\d+$'. Je réinitialise à NULL pour toute entrée invalide. Adaptez-vous à vos besoins.

  • J'ai ajouté un WARNING facultatif pour votre commodité de débogage.

  • Les cas 3. Et 4. Surviennent uniquement parce que les options personnalisées sont des littéraux de chaîne (type text), les types de données valides ne peuvent pas être appliqués automatiquement.

En relation:

Tout cela mis à part, il peut y avoir des solutions plus élégantes pour ce que vous essayez de faire sans options personnalisées, selon vos besoins exacts. Peut être ça:

6
Erwin Brandstetter

La raison pour laquelle vous essayez de concaténer NULL en user_id N'est pas claire, mais c'est évidemment la cause du problème. Débarrassez-vous de lui:

CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
    DECLARE
        user_id integer;
    BEGIN
        user_id := current_setting('myvars.user_id')::integer;
        INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql;

Notez que

SELECT 55 || NULL

donne toujours NULL.

7
klin

Vous pouvez intercepter l'exception lorsque la valeur n'existe pas - voici les modifications que j'ai apportées pour que cela fonctionne:

CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
    DECLARE
        user_id integer;
    BEGIN
        BEGIN
            user_id := current_setting('myvars.user_id')::integer;
        EXCEPTION WHEN OTHERS THEN
            user_id := 0;
        END;

        INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql;

 CREATE OR REPLACE FUNCTION insert_house() RETURNS void as $$
 DECLARE
    user_id integer;
 BEGIN 
   PERFORM set_config('myvars.user_id', '55', false);

   INSERT INTO houses (name) VALUES ('HOUSE PARTY');
 END; $$ LANGUAGE plpgsql;
4
garysieling