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などを避けたいと思います。
考えていたものと同様の方法で単純化します。
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 [ LANGUAGE
lang_name
] code
...lang_name
コードが記述されている手続き言語の名前。省略された場合、デフォルトはplpgsql
です。
または、ロールが使用可能なdbオブジェクトの所有者でない場合:
DROP ROLE IF EXISTS my_user;
CREATE ROLE my_user LOGIN PASSWORD 'my_password';
ただし、このユーザーを削除しても害はありません。
受け入れられた答えは、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
$$;
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';"
(質問に対する答えではありません!役に立つかもしれない人のためだけです)
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)
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$
他の例外を除外するために、おそらくより具体的にすることができます。
次の出力を解析することにより、バッチファイルで実行できます。
SELECT * FROM pg_user WHERE usename = 'my_user'
そして、ロールが存在しない場合は、psql.exe
をもう一度実行します。
パターンを使用することを提案する回答がいくつかあります。ロールが存在しないかどうかを確認し、存在しない場合は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
PostgreSQL用のCREATE DATABASE IF NOT EXISTS? と同じ解決策が機能するはずです-CREATE USER …
を\gexec
に送信してください。
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
詳細については、 受け入れられた答え を参照してください。