使用SQLAlchemy在PostgreSQL中创建函数和触发器

时间:2019-04-03 13:09:44

标签: python postgresql sqlalchemy

我使用SQLAlchemy Engine创建一些函数和触发器,但是我不想混合使用Python和SQL,因此我为SQL语句创建了一个单独的文件,我读取了内容并将其传递给engine.execute() 。它不会引发任何错误,但是不会在数据库中创建函数,但是如果我通过pgAdmin运行相同的SQL文件,一切都会很好。

我的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 个答案:

答案 0 :(得分:2)

我相信您可能混用了BEGIN SQL命令(Postgresql扩展名)和PL/pgSQL block。 SQL命令DO执行一个匿名代码块,就好像它是一个没有参数并返回void的匿名函数一样。换句话说是

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

BEGIN / END;对表示代码块,而不是事务。值得注意的是,从Postgresql版本11 it is possible to manage transactions in a DO block开始,假设它是not executed in a transaction block,但是用于该命令的是COMMITROLLBACK,而不是关键字{{1 }}。

然后的问题是,尽管明确地执行了命令,但未提交更改,这已得到错误的证明(如果未以适当的特权运行)。此问题是由how SQLAlchemy autocommit feature works引起的。简而言之,它将检查您的语句/命令,并尝试确定它是数据更改操作还是DDL语句。这适用于诸如ENDINSERTDELETE之类的基本操作,但并不完美。实际上,不可能始终正确地确定语句是否更改了数据。例如UPDATE就是这样的语句。因此,如果执行更复杂的操作,则需要一些帮助。一种方法是通过将SQL字符串包装在text()构造和using execution_options()中来指示自动提交机制应提交的内容:

SELECT my_mutating_procedure()

还可以使用DDL构造来明确指示SQLAlchemy该命令是文字DDL语句:

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

关于它为何与pgAdmin一起使用的原因,如果没有出现错误,则默认情况下它可能会提交。