MagicStack / MagicStack/asyncpg

No rows returned by fetch() when for DELETE rewritten to UPDATE using rule

未关闭
#1,173 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

主要语言
Python
星标
8.1k
派生
468
PR 合并指标
30 天内没有已合并 PR

描述

* **asyncpg version**: 0.29.0
* **PostgreSQL version**: 16.2 (`postgres:latest` Docker image)
* **Python version**: 3.12.3
* **Platform**: Tested on macOS and Linux
* **Do you use pgbouncer?**: No
* **Did you install asyncpg with pip?**: Yes
* **Can the issue be reproduced under both asyncio and [uvloop](https://github.com/magicstack/uvloop)?**: Yes

Unexpected behaviour: It seems `asyncpg` doesn't return the rows returned by a DELETE query rewritten to an UPDATE query by a rule. Perhaps because it's optimizing (the query status is `DELETE 0`, so perhaps it thinks it doesn't need to return any rows) or something like that? I didn't dive in any further to check if that is indeed what is happening.

Reproduction

```py
import asyncio
import asyncpg

async def main():
connection = await asyncpg.connect('postgresql://postgres:password@localhost/test')

async def fetch_print(query, *params):
result = await connection.fetch(query, *params)
i = 0
for row in result:
print(f' - {row}')
i += 1
if i == 0:
print(' (no rows returned)')
print('')

# Create table with rule for deletion
await connection.execute('''
CREATE TABLE items (
id serial PRIMARY KEY,
name text UNIQUE,
deleted boolean DEFAULT false
);
CREATE RULE softdelete AS
ON DELETE
TO items
DO INSTEAD
UPDATE items SET deleted = true WHERE id = OLD.id RETURNING OLD.*
;
INSERT INTO items (name) VALUES
('foo'),
('bar')
;
''')

print('Our table has a rule that updates the "deleted" column instead of deleting the row.\n')

# Show contents
print('We start with 2 rows which are not soft-deleted:')
await fetch_print('SELECT * FROM items')

# Try delete (the unexpected case)
print('Deleting with RETURNING should give us a row (it does in psql), but we do not get any in asyncpg:')
await fetch_print('''
DELETE FROM items WHERE name = $1 RETURNING id
''', 'foo')

# Confirm above query worked
print('But the row is now soft-deleted:')
await fetch_print('SELECT * FROM items')

# Workaround
print('If wrapped in a CTE it does work:')
await fetch_print('''
WITH x AS (
DELETE FROM items WHERE name = $1 RETURNING id
) SELECT * FROM x
''', 'bar')

# Confirm above query worked
print('And it is again properly softdeleted:')
await fetch_print('SELECT * FROM items')

print('We now delete the rule.\n')
await connection.execute('DROP RULE softdelete ON items')

# Confirm normal delete without rule returns rows
print('Normal deletion (without the rule) does return rows correctly:')
await fetch_print('''
DELETE FROM items RETURNING id
''')

# Confirm above query worked
print('And now both rows are indeed gone:')
await fetch_print('SELECT * FROM items')

# Clean up table after we are done
await connection.execute('DROP TABLE items')

await connection.close()

asyncio.run(main())
```

Output of reproduction

```
Our table has a rule that updates the "deleted" column instead of deleting the row.

We start with 2 rows which are not soft-deleted:
-
-

Deleting with RETURNING should give us a row (it does in psql), but we do not get any in asyncpg:
(no rows returned)

But the row is now soft-deleted:
-
-

If wrapped in a CTE it does work:
-

And it is again properly softdeleted:
-
-

We now delete the rule.

Normal deletion (without the rule) does return rows correctly:
-
-

And now both rows are indeed gone:
(no rows returned)

```

In contrast the `psql` command line tool *does* show me the resulting rows when the result code is `DELETED 0`.

Output of psql

```
test=# DELETE FROM items WHERE name = 'foo' RETURNING id;
id
----
1
(1 row)

DELETE 0
```

So it is a bit unexpected that `asyncpg` doesn't return any rows.

贡献指南

这个仓库没有索引到贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

首先,针对 PostgreSQL 16.2 运行 issue 中的 Python 复现代码,比较经过规则重写的带 RETURNING 的 DELETE、CTE 变通方案和普通 DELETE。跟踪直接重写查询的 asyncpg fetch 结果;完成标准是它返回 psql 显示的行,同时保持其他情况的现有行为。

由索引模型根据 Issue 内容生成。

评估

技术栈
postgresql, python
领域
databases
Issue 类型
缺陷
难度
3/5
预计耗时
1-2 天
活跃度
停滞
描述清晰度
基本清楚
新手友好度
38/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。