web-dev-qa-db-fra.com

Créer un ROLE PostgreSQL (utilisateur) s'il n'existe pas

Comment écrire un script SQL pour créer un ROLE dans PostgreSQL 9.1, mais sans générer d'erreur s'il existe déjà?

Le script actuel a simplement:

CREATE ROLE my_user LOGIN PASSWORD 'my_password';

Cela échoue si l'utilisateur existe déjà. Je voudrais quelque chose comme:

IF NOT EXISTS (SELECT * FROM pg_user WHERE username = 'my_user')
BEGIN
    CREATE ROLE my_user LOGIN PASSWORD 'my_password';
END;

... mais cela ne fonctionne pas - IF ne semble pas être pris en charge en langage SQL simple.

J'ai un fichier de commandes qui crée une base de données PostgreSQL 9.1, un rôle et quelques autres choses. Il appelle psql.exe, en passant le nom d'un script SQL à exécuter. Jusqu'à présent, tous ces scripts sont en langage SQL simple et j'aimerais éviter PL/pgSQL et autres, si possible.

86
EMP

Simplifiez-vous de la même manière que ce que vous aviez en tête:

DO
$do$
BEGIN
   IF NOT EXISTS (
      SELECT                       -- SELECT list can stay empty for this
      FROM   pg_catalog.pg_roles
      WHERE  rolname = 'my_user') THEN

      CREATE ROLE my_user LOGIN PASSWORD 'my_password';
   END IF;
END
$do$;

(Construit sur la réponse de @ a_horse_with_no_name et amélioré après le commentaire de @ Gregory .)

Contrairement à, par exemple, avec CREATE TABLE il n'y a pas de IF NOT EXISTS clause pour CREATE ROLE (encore). Et vous ne pouvez pas exécuter des instructions DDL dynamiques en langage SQL simple.

Votre demande "d'éviter PL/pgSQL" est impossible sauf en utilisant un autre PL. La déclaration DO utilise plpgsql comme langage procédural par défaut. La syntaxe permet d'omettre la déclaration explicite:

DO [ LANGUAGElang_name] code
...
lang_name
Nom du langage procédural dans lequel le code est écrit. S'il est omis, la valeur par défaut est plpgsql.

128
Erwin Brandstetter

Ou si le rôle n’est propriétaire d’objets de base de données, on peut utiliser:

DROP ROLE IF EXISTS my_user;
CREATE ROLE my_user LOGIN PASSWORD 'my_password';

Mais seulement si laisser tomber cet utilisateur ne fera aucun mal.

38
Borys

La réponse acceptée souffre d'une situation critique si deux scripts de ce type sont exécutés simultanément sur le même cluster Postgres (serveur de base de données), comme il est courant dans les environnements à intégration continue.

Il est généralement plus sûr d'essayer de créer le rôle et de résoudre les problèmes avec élégance lors de sa création:

DO $$
BEGIN
  CREATE ROLE my_role WITH NOLOGIN;
  EXCEPTION WHEN OTHERS THEN
  RAISE NOTICE 'not creating role my_role -- it already exists';
END
$$;
18
blubb

Bash alternative (pour script Bash):

psql -h localhost -U postgres -tc "SELECT 1 FROM pg_user WHERE usename = 'my_user'" | grep -q 1 || psql -h localhost -U postgres -c "CREATE ROLE my_user LOGIN PASSWORD 'my_password';"

(N'est-ce pas la réponse à la question! c'est seulement pour ceux qui peuvent être utiles)

11
Eduardo Cuomo

Voici une solution générique utilisant plpgsql:

CREATE OR REPLACE FUNCTION create_role_if_not_exists(rolename NAME) RETURNS TEXT AS
$$
BEGIN
    IF NOT EXISTS (SELECT * FROM pg_roles WHERE rolname = rolename) THEN
        EXECUTE format('CREATE ROLE %I', rolename);
        RETURN 'CREATE ROLE';
    ELSE
        RETURN format('ROLE ''%I'' ALREADY EXISTS', rolename);
    END IF;
END;
$$
LANGUAGE plpgsql;

Usage:

posgres=# SELECT create_role_if_not_exists('ri');
 create_role_if_not_exists 
---------------------------
 CREATE ROLE
(1 row)
posgres=# SELECT create_role_if_not_exists('ri');
 create_role_if_not_exists 
---------------------------
 ROLE 'ri' ALREADY EXISTS
(1 row)
8
Ingo Fischer

Comme vous êtes sur 9.x, vous pouvez en faire une déclaration:

do 
$body$
declare 
  num_users integer;
begin
   SELECT count(*) 
     into num_users
   FROM pg_user
   WHERE usename = 'my_user';

   IF num_users = 0 THEN
      CREATE ROLE my_user LOGIN PASSWORD 'my_password';
   END IF;
end
$body$
;
7

Mon équipe était confrontée à plusieurs bases de données sur un serveur. Selon la base de données à laquelle vous vous êtes connecté, le ROLE en question n'a pas été renvoyé par SELECT * FROM pg_catalog.pg_user, tel que proposé par @ erwin-brandstetter et @a_horse_with_no_name. Le bloc conditionnel exécuté, et nous avons frappé role "my_user" already exists.

Malheureusement, nous ne sommes pas sûrs des conditions exactes, mais cette solution contourne le problème:

        DO  
        $body$
        BEGIN
            CREATE ROLE my_user LOGIN PASSWORD 'my_password';
        EXCEPTION WHEN others THEN
            RAISE NOTICE 'my_user role exists, not re-creating';
        END
        $body$

Il pourrait probablement être rendu plus spécifique pour exclure d'autres exceptions.

7
Chris Betti

Vous pouvez le faire dans votre fichier de commandes en analysant la sortie de:

SELECT * FROM pg_user WHERE usename = 'my_user'

puis en cours d'exécution psql.exe encore une fois si le rôle n'existe pas.

3
Sheva

Certaines réponses suggèrent d’utiliser pattern: vérifie si le rôle n’existe pas et si non, lancez CREATE ROLE commande. Cela a un inconvénient: la condition de concurrence. Si quelqu'un d'autre crée un nouveau rôle entre check et émission CREATE ROLE commande puis CREATE ROLE échoue évidemment avec une erreur fatale.

Pour résoudre le problème ci-dessus, plusieurs autres réponses ont déjà mentionné l'utilisation de PL/pgSQL, émettant CREATE ROLE _ inconditionnellement, puis intercepter des exceptions de cet appel. Il n'y a qu'un problème avec ces solutions. Ils suppriment silencieusement toutes les erreurs, y compris celles qui ne sont pas générées par le fait que ce rôle existe déjà. CREATE ROLE peut également générer d’autres erreurs et simulations IF NOT EXISTS devrait ne taire que les erreurs lorsque le rôle existe déjà.

CREATE ROLE jeter duplicate_object _ erreur lorsque le rôle existe déjà. Et le gestionnaire d'exceptions ne devrait intercepter que cette seule erreur. Comme d'autres réponses l'ont mentionné, il est judicieux de convertir une erreur fatale en un simple avis. Autre PostgreSQL IF NOT EXISTS commandes ajoute , skipping _ dans leur message, donc pour plus de cohérence, je l’ajoute ici aussi.

Voici le code SQL complet pour la simulation de CREATE ROLE IF NOT EXISTS avec exception correcte et propagation de SQLSTATE:

DO $$
BEGIN
CREATE ROLE test;
EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
END
$$;

Test de la sortie (appelé deux fois via DO puis directement):

$ Sudo -u postgres psql
psql (9.6.12)
Type "help" for help.

postgres=# \set ON_ERROR_STOP on
postgres=# \set VERBOSITY verbose
postgres=# 
postgres=# DO $$
postgres$# BEGIN
postgres$# CREATE ROLE test;
postgres$# EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
DO
postgres=# 
postgres=# DO $$
postgres$# BEGIN
postgres$# CREATE ROLE test;
postgres$# EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
NOTICE:  42710: role "test" already exists, skipping
LOCATION:  exec_stmt_raise, pl_exec.c:3165
DO
postgres=# 
postgres=# CREATE ROLE test;
ERROR:  42710: role "test" already exists
LOCATION:  CreateRole, user.c:337
1
Pali

La même solution que pour Simuler CREATE DATABASE SI PAS EXISTS pour PostgreSQL? devrait fonctionner - envoyer un CREATE USER … à \gexec.

Solution de contournement depuis psql

SELECT 'CREATE USER my_user'
WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user')\gexec

Solution de contournement de Shell

echo "SELECT 'CREATE USER my_user' WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user')\gexec" | psql

Voir réponse acceptée ici pour plus de détails.

0
Alexander Skwar