Erstellen Sie eine PostgreSQL-ROLLE (Benutzer), falls diese nicht vorhanden ist

122

Wie schreibe ich ein SQL-Skript, um eine ROLLE in PostgreSQL 9.1 zu erstellen, ohne jedoch einen Fehler auszulösen, wenn dieser bereits vorhanden ist?

Das aktuelle Skript hat einfach:

CREATE ROLE my_user LOGIN PASSWORD 'my_password';

Dies schlägt fehl, wenn der Benutzer bereits vorhanden ist. Ich hätte gerne etwas wie:

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

... aber das funktioniert nicht - IF scheint in einfachem SQL nicht unterstützt zu werden.

Ich habe eine Batch-Datei, die eine PostgreSQL 9.1-Datenbank, eine Rolle und einige andere Dinge erstellt. Es ruft psql.exe auf und übergibt den Namen eines auszuführenden SQL-Skripts. Bisher sind alle diese Skripte einfaches SQL und ich möchte PL / pgSQL und dergleichen nach Möglichkeit vermeiden.

EMP
quelle

Antworten:

156

Vereinfachen Sie auf ähnliche Weise wie Sie es sich vorgestellt haben:

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

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

(Aufbauend auf der Antwort von @a_horse_with_no_name und verbessert durch den Kommentar von @ Gregory .)

Anders als zum Beispiel mit CREATE TABLEgibt es keine IF NOT EXISTSKlausel für CREATE ROLE(bis mindestens S. 12). Und Sie können keine dynamischen DDL-Anweisungen in einfachem SQL ausführen.

Ihre Anfrage, "PL / pgSQL zu vermeiden", ist nur mit einem anderen PL möglich. Die DOAnweisung verwendet plpgsql als Standardprozedursprache. Die Syntax erlaubt es, die explizite Deklaration wegzulassen:

DO [ LANGUAGE lang_name ] code
... Der Name der prozeduralen Sprache, in der der Code geschrieben ist. Wenn nicht angegeben, ist die Standardeinstellung .
lang_name
plpgsql

Erwin Brandstetter
quelle
1
@ Alberto: pg_user und pg_roles sind beide korrekt. Immer noch der Fall in der aktuellen Version 9.3 und es wird sich in Kürze nicht ändern.
Erwin Brandstetter
2
@ Ken: Wenn $Ihr Client eine besondere Bedeutung hat, müssen Sie diese gemäß den Syntaxregeln Ihres Clients maskieren . Versuchen Sie, $mit \$in der Linux-Shell zu entkommen . Oder starten Sie eine neue Frage - Kommentare sind nicht der richtige Ort. Sie können jederzeit einen Link zu diesem für den Kontext erstellen.
Erwin Brandstetter
1
Ich verwende 9.6, und wenn ein Benutzer mit NOLOGIN erstellt wurde, wird er nicht in der Tabelle pg_user, sondern in der Tabelle pg_roles angezeigt. Wäre pg_roles hier eine bessere Lösung?
Jess
2
@ErwinBrandstetter Dies funktioniert nicht für Rollen mit NOLOGIN. Sie werden in pg_roles angezeigt, jedoch nicht in pg_user.
Gregory Arenius
2
Diese Lösung leidet unter einer Rennbedingung. Eine sicherere Variante ist in dieser Antwort dokumentiert .
Blubb
60

Die akzeptierte Antwort leidet unter einer Race-Bedingung, wenn zwei solcher Skripte gleichzeitig auf demselben Postgres-Cluster (DB-Server) ausgeführt werden, wie dies in Umgebungen mit kontinuierlicher Integration üblich ist .

Im Allgemeinen ist es sicherer, zu versuchen, die Rolle zu erstellen und Probleme beim Erstellen ordnungsgemäß zu lösen:

DO $$
BEGIN
  CREATE ROLE my_role WITH NOLOGIN;
  EXCEPTION WHEN DUPLICATE_OBJECT THEN
  RAISE NOTICE 'not creating role my_role -- it already exists';
END
$$;
blubb
quelle
2
Ich mag diese Art und Weise, weil es eine Benachrichtigung gibt, die existiert.
Matias Barone
2
DUPLICATE_OBJECTist die genaue Bedingung in diesem Fall, wenn Sie nicht so gut wie alle Bedingungen mit fangen wollen OTHERS.
Danek Duvall
43

Oder wenn die Rolle nicht der Eigentümer von Datenbankobjekten ist, kann man Folgendes verwenden:

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

Aber nur wenn das Fallenlassen dieses Benutzers keinen Schaden anrichtet.

Borys
quelle
10

Bash- Alternative (für Bash-Scripting ):

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';"

(ist nicht die Antwort auf die Frage! es ist nur für diejenigen, die nützlich sein können)

Eduardo Cuomo
quelle
3
Es sollte FROM pg_roles WHERE rolnamestattFROM pg_user WHERE usename
Barth
8

Hier ist eine generische Lösung mit 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;

Verwendung:

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)
Wolkenarchitekt
quelle
8

Einige Antworten schlagen vor, Muster zu verwenden: Überprüfen Sie, ob keine Rolle vorhanden ist, und geben Sie dann einen CREATE ROLEBefehl aus. Dies hat einen Nachteil: Rennbedingung. Wenn jemand anderes eine neue Rolle zwischen Prüfung und Ausgabe des CREATE ROLEBefehls erstellt, dannCREATE ROLE schlägt dies offensichtlich mit einem schwerwiegenden Fehler fehl.

Um das obige Problem zu lösen, erwähnten weitere Antworten bereits die Verwendung von PL/pgSQL, CREATE ROLEbedingungslose Ausgabe und dann das Abfangen von Ausnahmen von diesem Aufruf. Bei diesen Lösungen gibt es nur ein Problem. Sie löschen stillschweigend alle Fehler, einschließlich solcher, die nicht durch die Tatsache generiert werden, dass die Rolle bereits vorhanden ist. CREATE ROLEkann auch andere Fehler auslösen und die Simulation IF NOT EXISTSsollte nur Fehler zum Schweigen bringen, wenn die Rolle bereits vorhanden ist.

CREATE ROLEduplicate_objectFehler auslösen , wenn die Rolle bereits vorhanden ist. Und der Ausnahmebehandler sollte nur diesen einen Fehler abfangen. Wie in anderen Antworten erwähnt, ist es eine gute Idee, schwerwiegende Fehler in einfache Hinweise umzuwandeln. Andere PostgreSQL- IF NOT EXISTSBefehle fügen , skippingihrer Nachricht hinzu. Aus Gründen der Konsistenz füge ich sie auch hier hinzu.

Hier ist der vollständige SQL-Code für die Simulation CREATE ROLE IF NOT EXISTSmit korrekter Ausnahme und SQLstate-Weitergabe:

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

Testausgabe (zweimal über DO und dann direkt aufgerufen):

$ 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
Pali
quelle
2
Danke dir. Keine Rennbedingungen, strenger Ausnahmefang, Postgres 'eigene Nachricht einpacken, anstatt die eigene neu zu schreiben.
Stefano Taschini
1
Tatsächlich! Dies ist derzeit die einzig richtige Antwort hier, die nicht unter Rennbedingungen leidet und die notwendige selektive Fehlerbehandlung verwendet. Es ist wirklich schade, dass diese Antwort erschien, nachdem die (nicht vollständig korrekte) Top-Antwort mehr als 100 Punkte gesammelt hatte.
Vog
1
Bitte schön! Meine Lösung verbreitet auch SQLSTATE. Wenn Sie also eine Anweisung aus einem anderen PL / SQL-Skript oder einer anderen Sprache mit SQL Connector aufrufen, erhalten Sie korrektes SQLSTATE.
Pali
6

Wenn Sie sich in 9.x befinden, können Sie dies in eine DO-Anweisung einschließen:

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$
;
ein Pferd ohne Name
quelle
Select sollte `SELECT count (*) in num_users FROM pg_roles WHERE rolname = 'data_rw' sein;` Sonst funktioniert es nicht
Miro
6

Mein Team hatte eine Situation mit mehreren Datenbanken auf einem Server. Je nachdem, mit welcher Datenbank Sie verbunden waren, wurde die betreffende ROLLE nicht zurückgegeben SELECT * FROM pg_catalog.pg_user, wie von @ erwin-brandstetter und @a_horse_with_no_name vorgeschlagen. Der bedingte Block wurde ausgeführt und wir haben getroffen role "my_user" already exists.

Leider sind wir uns der genauen Bedingungen nicht sicher, aber diese Lösung umgeht das Problem:

        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$

Es könnte wahrscheinlich spezifischer gemacht werden, andere Ausnahmen auszuschließen.

Chris Betti
quelle
3
Die Tabelle pg_user scheint nur Rollen mit LOGIN zu enthalten. Wenn eine Rolle NOLOGIN hat, wird sie nicht in pg_user angezeigt, zumindest nicht in PostgreSQL 10.
Gregory Arenius
2

Sie können dies in Ihrer Batch-Datei tun, indem Sie die Ausgabe von:

SELECT * FROM pg_user WHERE usename = 'my_user'

und dann psql.exeerneut ausgeführt, wenn die Rolle nicht vorhanden ist.

Sheva
quelle
2
Die Spalte "Benutzername" existiert nicht. Es sollte "Benutzername" sein.
Mouhammed Soueidane
3
"Benutzername" ist derjenige, der nicht existiert. :)
Garen
1
Weitere Informationen finden Sie im Dokument pg_user view. In den Versionen 7.4-9.6 gibt es keine Spalte "Benutzername", "Benutzername" ist die richtige.
Sheva
1

Die gleiche Lösung wie für Simulate CREATE DATABASE, WENN NICHT FÜR PostgreSQL EXISTIERT? sollte funktionieren - senden Sie eine CREATE USER …an \gexec.

Problemumgehung innerhalb von psql

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

Problemumgehung aus der Shell

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

Weitere Informationen finden Sie in der dort akzeptierten Antwort .

Alexander Skwar
quelle
Ihre Lösung hat immer noch eine Race-Bedingung, die ich in meiner Antwort stackoverflow.com/a/55954480/7878845 beschrieben habe. Wenn Sie Ihr Shell-Skript mehrmals parallel ausführen, erhalten Sie ERROR: Rolle "my_user" existiert bereits
Pali