我使用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;
提交了事务,因此该代码真正运行了,这些功能应该对公众可用,但是甚至没有创建。任何想法都欢迎,谢谢!
答案 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,但是用于该命令的是COMMIT
和ROLLBACK
,而不是关键字{{1 }}。
然后的问题是,尽管明确地执行了命令,但未提交更改,这已得到错误的证明(如果未以适当的特权运行)。此问题是由how SQLAlchemy autocommit feature works引起的。简而言之,它将检查您的语句/命令,并尝试确定它是数据更改操作还是DDL语句。这适用于诸如END
,INSERT
,DELETE
之类的基本操作,但并不完美。实际上,不可能始终正确地确定语句是否更改了数据。例如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一起使用的原因,如果没有出现错误,则默认情况下它可能会提交。