Создание функций и триггеров в PostgreSQL с помощью SQLAlchemy - PullRequest
2 голосов
/ 03 апреля 2019

Я использую SQLAlchemy Engine для создания некоторых функций и триггеров, но я не хотел смешивать Python и SQL, поэтому я создал отдельный файл для своих операторов SQL, прочитал содержимое и передал его engine.execute().Он не выдает ошибок, однако функции не создаются в базе данных, но если я запускаю тот же файл SQL через pgAdmin, все работает нормально.

Мой файл SQL:

DO $$
BEGIN
  IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'plpython3u') THEN
    CREATE EXTENSION plpython3u;
  END IF;
END;
$$;

DO $$
BEGIN
  IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'my_func') THEN
    CREATE FUNCTION public.my_func() RETURNS TRIGGER LANGUAGE 'plpython3u' NOT LEAKPROOF AS $BODY$
    -- definition
    $BODY$;

    GRANT EXECUTE ON FUNCTION my_func() TO public;
  END IF;
END;
$$;

DO $$
BEGIN
  IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'my_func2') THEN
    CREATE FUNCTION public.my_func2() RETURNS TRIGGER LANGUAGE 'plpython3u' NOT LEAKPROOF AS $BODY$
    -- definition
    $BODY$;

    GRANT EXECUTE ON FUNCTION my_func2() TO public;
  END IF;
END;
$$;

ИЯ запускаю это следующим образом:

def execute_sql_file(engine, path):
    try:
        with open(path) as file:
            engine.execute(file.read())
    except ProgrammingError:
        raise MyCustomError
    except FileNotFoundError:
        raise MyCustomError

Если я запускаю это без привилегий суперпользователя, он выдает ProgrammingError, как и ожидалось.В моем понимании END; фиксирует транзакцию, поэтому, если этот код действительно выполняется, функции должны быть доступны для общественности, однако они даже не созданы.Любые идеи приветствуются, спасибо!

1 Ответ

2 голосов
/ 03 апреля 2019

Полагаю, вы могли смешать команду SQL BEGIN (расширение Postgresql) и блок PL / pgSQL .Команда SQL DO выполняет блок анонимного кода, как если бы это была анонимная функция без параметров и возвращающая void.Другими словами, в

DO $$
BEGIN
    ...
END;
$$;

пара BEGIN / END; обозначает блок кода, а не транзакцию.Стоит отметить, что начиная с Postgresql версии 11 можно управлять транзакциями в DO блоке , учитывая, что он не выполняется в блоке транзакций , но команды дляэто COMMIT и ROLLBACK, а не ключевое слово END.

Проблема в том, что ваши изменения не зафиксированы, хотя ваши команды явно выполняются - что подтверждается ошибкой, если она не запущенас подходящими привилегиями.Эта проблема вызвана , как работает функция автоматической фиксации SQLAlchemy .Короче говоря, он проверяет ваш оператор / команду и пытается определить, является ли это операцией изменения данных или оператором DDL.Это работает для основных операций, таких как INSERT, DELETE, UPDATE и т. П., Но не идеально.Фактически, невозможно всегда правильно определить, изменяет ли оператор данные;например SELECT my_mutating_procedure() это такое утверждение.Так что нужна помощь, если делать более сложные операции.Один из способов - указать механизму автоматической фиксации, что он должен выполнить фиксацию, заключив строку SQL в конструкцию text() и , используя execution_options():

engine.execute(text("SELECT my_mutating_procedure()").
               execution_options(autocommit=True))

Также возможно явно указать SQLAlchemy, что команда является литеральным оператором DDL, используя конструкцию DDL:

from sqlalchemy.schema import DDL

def execute_sql_file(engine, path):
    try:
        with open(path) as file:
            stmt = file.read()

        # Not strictly DDL, but a series of DO commands that execute DDL
        ddl_stmt = DDL(stmt)
        engine.execute(ddl_stmt)

    except ProgrammingError:
        raise MyCustomError

    except FileNotFoundError:
        raise MyCustomError

Что касается того, почему она работает с pgAdmin, вероятно, по умолчаниюфиксирует, если не было ошибок.

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