在 psycopg2 中使用 with 语句创建事务

Creating transactions with with statements in psycopg2

我正在尝试使用 psycopg2 向 table 添加一些新列。 PostgreSQL 缺少 ALTER TABLE table ADD COLUMN IF NOT EXISTS,因此我将每一列添加到它自己的事务中。如果该列存在,将出现 python & postgres 错误,没关系,我希望我的程序继续并尝试添加下一列。目标是做到幂等,所以它可以连续 运行 多次。

目前看起来是这样的:

def main():
    # <snip>
    with psycopg2.connect("") as connection:
        create_columns(connection, args.table)

def create_columns(connection, table_name):
    def sql(sql):
        with connection.cursor() as cursor:
            cursor.execute(sql.format(table_name=table_name))

    sql("ALTER TABLE {table_name} ADD COLUMN my_new_col numeric(10,0);")
    sql("ALTER TABLE {table_name} ADD COLUMN another_new_col INTEGER NOT NULL;")

然而,如果my_new_col存在,则出现异常ProgrammingError('column "parent_osm_id" of relation "relations" already exists\n',),这是意料之中的,但当它尝试添加another_new_col时,出现异常[=15] =].

psycogpg2 document for the with statement 意味着 with connection.cursor() as cursor: 会将代码包装在事务中。这显然没有发生。实验表明我需要 2 个级别的 with 语句,包括 pscyopg2.connect 调用,然后我得到一个事务。

我如何传递 connection object 并在他们自己的事务中查询 运行 以允许这种 "graceful error handling"?我想以 "clean architecture" 风格将 postgres 连接代码分开。这可能吗?

The psycogpg2 document for the with statement implies that the with connection.cursor() as cursor: will wrap that code in a transaction.

这实际上不是真的它说:

with psycopg2.connect(DSN) as conn:
    with conn.cursor() as curs:
       curs.execute(SQL)

When a connection exits the with block, if no exception has been raised by the block, the transaction is committed. In case of exception the transaction is rolled back. In no case the connection is closed: a connection can be used in more than a with statement and each with block is effectively wrapped in a transaction.

所以这不是 with 处理的游标对象,而是连接对象

还值得注意的是,当我们离开with子句

时,游标占用的所有资源都将被释放

When a cursor exits the with block it is closed, releasing any resource eventually associated with it. The state of the transaction is not affected.

所以回到你的代码,你可能会重写它更像:

def main():
    # <snip>
    with psycopg2.connect("") as connection:
        create_columns(connection, args.table)

def create_columns(con, table_name):
    def sql(connection, sql):
        with connection:
            with connection.cursor() as cursor:
                cursor.execute(sql.format(table_name=table_name))

    sql(con, "ALTER TABLE {table_name} ADD COLUMN my_new_col numeric(10,0);")
    sql(con, "ALTER TABLE {table_name} ADD COLUMN another_new_col INTEGER NOT NULL;")

确保您执行的每个查询的连接都包裹在 with 中,因此如果失败,连接上下文管理器将恢复事务