Создать PostgreSQL ROLE (пользователь), если он не существует - PullRequest
84 голосов
/ 11 ноября 2011

Как мне написать сценарий SQL для создания ROLE в PostgreSQL 9.1, но без сообщения об ошибке, если она уже существует?

Текущий сценарий просто имеет:

CREATE ROLE my_user LOGIN PASSWORD 'my_password';

Thisне удается, если пользователь уже существует.Я бы хотел что-то вроде:

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 и тому подобного.

Ответы [ 10 ]

123 голосов
/ 12 ноября 2011

Упростите аналогично тому, что вы имели в виду:

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 и улучшилось после @ комментария Грегори .)

В отличие, например, от CREATE TABLE нет предложения IF NOT EXISTS для CREATE ROLE (пока).И вы не можете выполнять динамические операторы DDL в простом SQL.

Ваш запрос "избежать PL / pgSQL" невозможен, за исключением использования другого PL.Оператор DO использует plpgsql в качестве процедурного языка по умолчанию.Синтаксис позволяет опустить явное объявление:

DO [ LANGUAGE lang_name ] code
...
lang_name
Название процедурного языка, на котором написан код. Если не указано, по умолчанию используется значение plpgsql.

39 голосов
/ 13 декабря 2012

Или, если роль не является владельцем каких-либо объектов БД, можно использовать:

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

Но только если удаление этого пользователя не принесет никакого вреда.

16 голосов
/ 16 апреля 2018

Принятый ответ страдает от состояния гонки, если два таких сценария выполняются одновременно в одном кластере Postgres (сервер БД), как обычно в средах непрерывной интеграции .

Обычно безопаснее попытаться создать роль и изящно справиться с проблемами при ее создании:

DO $$
BEGIN
  CREATE ROLE my_role WITH NOLOGIN;
  EXCEPTION WHEN OTHERS THEN
  RAISE NOTICE 'not creating role my_role -- it already exists';
END
$$;
11 голосов
/ 05 апреля 2018

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

(не ответ на вопрос! Только для тех, кто может быть полезен)

8 голосов
/ 20 февраля 2014

Вот общее решение с использованием 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)
7 голосов
/ 16 августа 2016

Моя команда столкнулась с ситуацией с несколькими базами данных на одном сервере, в зависимости от того, к какой базе данных вы подключились, соответствующая роль не была возвращена SELECT * FROM pg_catalog.pg_user, как предложили @ erwin-brandstetter и @a_horse_with_no_name. Условный блок выполнен, и мы нажимаем 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 голосов
/ 11 ноября 2011

Поскольку вы находитесь на 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$
;
3 голосов
/ 11 ноября 2011

Вы можете сделать это в своем пакетном файле, проанализировав выходные данные:

SELECT * FROM pg_user WHERE usename = 'my_user'

и затем запустив psql.exe еще раз, если роль не существует.

1 голос
/ 02 мая 2019

В некоторых ответах предлагается использовать шаблон: проверьте, не существует ли роль, а если нет, введите команду CREATE ROLE. Это имеет один недостаток: состояние гонки. Если кто-то еще создает новую роль между проверкой и вводом команды CREATE ROLE, то CREATE ROLE явно завершается с фатальной ошибкой.

Чтобы решить вышеуказанную проблему, в других ответах уже упоминалось использование PL/pgSQL, безоговорочно выдавая CREATE ROLE и затем перехватывая исключения из этого вызова. Есть только одна проблема с этими решениями. Они молча отбрасывают любые ошибки, в том числе те, которые не вызваны тем, что роль уже существует. CREATE ROLE может выдавать и другие ошибки, а симуляция IF NOT EXISTS должна игнорировать только ошибку, когда роль уже существует.

CREATE ROLE throw duplicate_object Ошибка, когда роль уже существует. И обработчик исключений должен отлавливать только одну эту ошибку. Как уже упоминалось в других ответах, хорошей идеей является преобразование фатальной ошибки в простое уведомление. Другие команды PostgreSQL IF NOT EXISTS добавляют , skipping в свое сообщение, поэтому для согласованности я добавлю и его сюда.

Вот полный код SQL для моделирования CREATE ROLE IF NOT EXISTS с правильным исключением и распространением sqlstate:

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

Тестовый вывод (вызывается два раза через DO, а затем напрямую):

$ 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
0 голосов
/ 08 мая 2019

То же решение, что и для Имитация CREATE DATABASE, ЕСЛИ НЕ СУЩЕСТВУЕТ PostgreSQL? должно работать - отправьте 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

См. принятый ответ там для более подробной информации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...