Mevcut değilse PostgreSQL ROLE (kullanıcı) oluşturun


123

PostgreSQL 9.1'de bir ROLE oluşturmak için bir SQL komut dosyasını nasıl yazabilirim, ancak zaten mevcutsa bir hata oluşturmadan?

Mevcut komut dosyası basitçe şunları içerir:

CREATE ROLE my_user LOGIN PASSWORD 'my_password';

Kullanıcı zaten varsa bu başarısız olur. Şunun gibi bir şey istiyorum:

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

... ancak bu işe yaramıyor - IFdüz SQL'de desteklenmiyor gibi görünüyor.

Bir PostgreSQL 9.1 veritabanı, rol ve birkaç başka şey oluşturan bir toplu iş dosyam var. Çalıştırmak için bir SQL betiğinin adını ileten psql.exe'yi çağırır. Şimdiye kadar tüm bu komut dosyaları düz SQL ve mümkünse PL / pgSQL ve benzerlerinden kaçınmak istiyorum.

Yanıtlar:


157

Aklınızdan geçenlere benzer bir şekilde basitleştirin:

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

( @ A_horse_with_no_name'nin yanıtı üzerine inşa edildi ve @ Gregory'nin yorumu ile geliştirildi .)

Bunun tersine, örneğin, CREATE TABLEiçin hiçbir IF NOT EXISTSmadde yoktur CREATE ROLE(en az 12. sayfaya kadar). Ve olamaz düz SQL dinamik DDL ifadeleri yürütün.

"PL / pgSQL'den kaçınma" isteğiniz, başka bir PL kullanmak dışında imkansızdır. DODeyim kullanır varsayılan prosedürel dil olarak plpgsql. Sözdizimi, açık bildirimin çıkarılmasına izin verir:

DO [ LANGUAGE lang_name ] code
... Kodun yazıldığı yordamsal dilin adı. Atlanırsa, varsayılan değerdir .
lang_name
plpgsql


1
@Alberto: pg_user ve pg_roles her ikisi de doğru. Yine de mevcut 9.3 sürümündeki durum ve yakın zamanda herhangi bir zamanda değişmeyecek.
Erwin Brandstetter

2
@Ken: İstemcinizde $özel bir anlamı varsa , istemcinizin sözdizimi kurallarına göre ondan kaçmanız gerekir. Linux kabuğunda $ile kaçmayı deneyin \$. Veya yeni bir soruya başlayın - yorumlar yer değildir. Bağlam için her zaman buna bağlanabilirsiniz.
Erwin Brandstetter

1
9.6 kullanıyorum ve bir kullanıcı NOLOGIN ile oluşturulmuşsa, bunlar pg_user tablosunda görünmüyor, ancak pg_roles tablosunda görünüyor. Pg_roles burada daha iyi bir çözüm olabilir mi?
Jess

2
@ErwinBrandstetter Bu, NOLOGIN'e sahip roller için çalışmaz. Pg_roles içinde görünürler ancak pg_user'da görünmezler.
Gregory Arenius

2
Bu çözüm bir yarış durumundan muzdariptir. Bu cevapta daha güvenli bir varyant belgelenmiştir .
blubb

60

Sürekli entegrasyon ortamlarında yaygın olduğu gibi, bu tür iki komut dosyası aynı Postgres kümesinde (DB sunucusu) aynı anda çalıştırılırsa, kabul edilen yanıt bir yarış koşulundan muzdariptir .

Rolü oluşturmaya çalışmak ve onu oluştururken sorunlarla zarafetle başa çıkmak genellikle daha güvenlidir:

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

2
Var olduğunu onaylamasından dolayı bu yolu seviyorum.
Matias Barone

2
DUPLICATE_OBJECThemen hemen tüm koşulları yakalamak istemiyorsanız, bu durumda kesin koşuldur OTHERS.
Danek Duvall

43

Veya rol herhangi bir db nesnesinin sahibi değilse, kullanabileceğiniz:

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

Ancak sadece bu kullanıcıyı düşürmek herhangi bir zarar vermez.


10

Bash alternatifi ( Bash komut dosyası için ):

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

(sorunun cevabı değil! sadece faydalı olabilecekler içindir)


3
Bunun FROM pg_roles WHERE rolnameyerineFROM pg_user WHERE usename
Barth

8

Plpgsql kullanan genel bir çözüm:

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;

Kullanımı:

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

Bazı yanıtlar kalıbı kullanmayı önerdi: rolün var olup olmadığını kontrol edin ve yoksa CREATE ROLEkomut verin. Bunun bir dezavantajı var: yarış durumu. Başka biri kontrol etme ve verme CREATE ROLEkomutu arasında yeni bir rol yaratırsa, o zamanCREATE ROLE açıkça ölümcül bir hatayla başarısız olur.

Soruna yukarıda çözmek için, daha başka cevaplar zaten kullanımını söz PL/pgSQLveren, CREATE ROLEkoşulsuz ve daha sonra bu çağrısından durumları yakalamak. Bu çözümlerle ilgili yalnızca bir sorun var. Rolün zaten var olduğu gerçeğiyle oluşmayanlar da dahil olmak üzere tüm hataları sessizce atarlar. CREATE ROLEbaşka hatalar da atabilir ve simülasyon IF NOT EXISTSyalnızca rol zaten mevcut olduğunda hatayı susturmalıdır.

CREATE ROLEatmak duplicate_objectrolü zaten var olduğunda hata. Ve istisna işleyicisi yalnızca bu tek hatayı yakalamalıdır. Diğer cevaplarda belirtildiği gibi, ölümcül hatayı basit bildirime dönüştürmek iyi bir fikirdir. Diğer PostgreSQL IF NOT EXISTSkomutları , skippingmesajlarına ekler , bu nedenle tutarlılık için buraya da ekliyorum.

CREATE ROLE IF NOT EXISTSDoğru istisna ve sqlstate yayılımı ile simülasyonu için tam SQL kodu :

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

Test çıkışı (DO aracılığıyla iki kez ve ardından doğrudan çağrılır):

$ 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

2
Teşekkür ederim. Yarış koşulları yok, sıkı istisna yakalama, kendi mesajınızı yeniden yazmak yerine Postgres'in kendi mesajını sarma.
Stefano Taschini

1
Aslında! Bu şu anda buradaki tek doğru cevaptır, yarış koşullarından etkilenmez ve gerekli seçici hata işlemeyi kullanır. Bu cevabın (tam olarak doğru değil) en iyi cevap 100 puandan fazla toplandıktan sonra ortaya çıkması gerçekten üzücü.
vog

1
Rica ederim! Çözümüm ayrıca SQLSTATE'i yayar, bu nedenle başka bir PL / SQL betiğinden veya SQL bağlayıcılı başka bir dilden ifade çağırıyorsanız, doğru SQLSTATE'i alırsınız.
Pali

6

9.x üzerindeyken, bunu bir DO ifadesine sarabilirsiniz:

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

`Pg_roles GELEN NUM_USERS içine SEÇ sayımı (*) olmalıdır seçin NEREDE rolname = 'data_rw';` Aksi takdirde olmaz iş
Miro

6

Ekibim, bağlandığınız veritabanına bağlı olarak tek bir sunucuda birden fazla veritabanının olduğu bir duruma SELECT * FROM pg_catalog.pg_userdüşüyordu, söz konusu ROLE @ erwin-brandstetter ve @a_horse_with_no_name tarafından önerildiği gibi tarafından geri döndürülmedi . Koşullu blok uygulandı ve vurduk role "my_user" already exists.

Maalesef kesin koşullardan emin değiliz, ancak bu çözüm sorunun etrafında çalışıyor:

        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$

Diğer istisnaları dışlamak için muhtemelen daha spesifik hale getirilebilir.


3
Pg_user tablosu sadece LOGIN olan rolleri içeriyor gibi görünüyor. Bir rolde NOLOGIN varsa, pg_user'da, en azından PostgreSQL 10'da
görünmez

2

Bunu toplu iş dosyanızda aşağıdakilerin çıktısını ayrıştırarak yapabilirsiniz:

SELECT * FROM pg_user WHERE usename = 'my_user'

ve sonra psql.exerol yoksa bir kez daha çalışır .


2
"kullanıcı adı" sütunu mevcut değil. "Kullanıcı adı" olmalıdır.
Mouhammed Soueidane

3
"usename", var olmayan bir addır. :)
Garen

1
Lütfen pg_user görünüm belgesine bakın . 7.4-9.6 sürümlerinde "kullanıcı adı" sütunu yoktur, "kullanıcı adı" doğru olanıdır.
Sheva

1

İle aynı çözüm Simulate VERİTABANI OLUŞTURMA ile PostgreSQL için MEVCUT DEĞİLSE? çalışması gerekir - bir göndermek CREATE USER …için \gexec.

Psql içinden geçici çözüm

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

Kabuktan geçici çözüm

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

Daha fazla ayrıntı için kabul edilen yanıta bakın.


Çözümünüzde, cevabımda açıkladığım bir yarış durumu hala var stackoverflow.com/a/55954480/7878845 Kabuk betiğinizi daha fazla kez paralel çalıştırırsanız, HATA mesajı alırsınız: "my_user" rolü zaten var
Pali
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.