web-dev-qa-db-ja.com

PostgreSQL ROLE(ユーザー)が存在しない場合は作成します

PostgreSQL 9.1でROLEを作成するためのSQLスクリプトを記述するにはどうすればよいですか?

現在のスクリプトには次のものがあります。

CREATE ROLE my_user LOGIN PASSWORD 'my_password';

ユーザーが既に存在する場合、これは失敗します。次のようなものが欲しい:

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

...しかし、それは機能しません-IFはプレーンSQLではサポートされていないようです。

PostgreSQL 9.1データベース、ロール、その他いくつかを作成するバッチファイルがあります。 psql.exeを呼び出し、実行するSQLスクリプトの名前を渡します。これまでのところ、これらのスクリプトはすべてプレーンSQLであり、可能な場合はPL/pgSQLなどを避けたいと思います。

86
EMP

考えていたものと同様の方法で単純化します。

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$;

@ a_horse_with_no_name の回答に基づいて構築し、 @ Gregoryのコメント の後に改善しました。)

たとえば、 CREATE TABLE の場合とは異なり、 IF NOT EXISTS にはCREATE ROLE句はありません(まだ)。そして、あなたはcannotプレーンなSQLで動的なDDLステートメントを実行します。

「PL/pgSQLを回避する」というリクエストは、別のPLを使用しない限り不可能です。 DOステートメント は、デフォルトの手続き言語としてplpgsqlを使用します。構文では、明示的な宣言を省略できます。

DO [ LANGUAGElang_name] code
...
lang_name
コードが記述されている手続き言語の名前。省略された場合、デフォルトはplpgsqlです。

128

または、ロールが使用可能なdbオブジェクトの所有者でない場合:

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

ただし、このユーザーを削除しても害はありません。

38
Borys

受け入れられた答えは、2つのそのようなスクリプトが同時に実行される場合、競合状態に苦しみます同じPostgresクラスター上(DBサーバー)、一般的です連続統合環境

一般に、ロールを作成し、作成するときに問題を適切に処理しようとする方が安全です。

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代替(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';"

(質問に対する答えではありません!役に立つかもしれない人のためだけです)

11
Eduardo Cuomo

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;

使用法:

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

9.xを使用している場合、それをDOステートメントにラップできます。

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$
;

私のチームは、1つのサーバーに複数のデータベースが存在する状況に陥っていました。接続するデータベースによっては、@ erwin-brandstetterおよび@a_horse_with_no_nameが提案したように、問題のROLEがSELECT * FROM pg_catalog.pg_userから返されませんでした。条件付きブロックが実行され、role "my_user" already existsがヒットしました。

残念ながら、正確な条件はわかりませんが、この解決策は問題を回避します。

        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$

他の例外を除外するために、おそらくより具体的にすることができます。

7
Chris Betti

次の出力を解析することにより、バッチファイルで実行できます。

SELECT * FROM pg_user WHERE usename = 'my_user'

そして、ロールが存在しない場合は、psql.exeをもう一度実行します。

3
Sheva

パターンを使用することを提案する回答がいくつかあります。ロールが存在しないかどうかを確認し、存在しない場合はCREATE ROLEコマンドを発行します。これには1つの欠点があります。競合状態です。他の誰かがチェックとCREATE ROLEコマンドの発行の間に新しいロールを作成すると、CREATE ROLEは明らかに致命的なエラーで失敗します。

上記の問題を解決するために、他の回答では既にPL/pgSQLの使用法について言及し、CREATE ROLEを無条件に発行し、その呼び出しから例外をキャッチしました。これらのソリューションには1つの問題があります。ロールがすでに存在するという事実によって生成されないエラーを含め、エラーを静かにドロップします。 CREATE ROLEは他のエラーとシミュレーションをスローすることもありますIF NOT EXISTSは、ロールが既に存在する場合にのみエラーを消します。

ロールが既に存在する場合、CREATE ROLE throw duplicate_objectエラー。例外ハンドラーは、この1つのエラーのみをキャッチする必要があります。他の回答が述べたように、致命的なエラーを単純な通知に変換することは良い考えです。他のPostgreSQL IF NOT EXISTSコマンドは, skippingをメッセージに追加するため、一貫性を保つためにここでも追加します。

正しい例外とsqlstateの伝播を伴うCREATE ROLE IF NOT EXISTSのシミュレーション用の完全なSQLコードを次に示します。

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

テスト出力(DOを介して2回呼び出され、次に直接呼び出されます):

$ 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

PostgreSQL用のCREATE DATABASE IF NOT EXISTS? と同じ解決策が機能するはずです-CREATE USER …\gexecに送信してください。

Psql内からの回避策

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

シェルからの回避策

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

詳細については、 受け入れられた答え を参照してください。

0
Alexander Skwar