8000 Add a result iterator to all cursor classes by embecka · Pull Request #1081 · psycopg/psycopg · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add a result iterator to all cursor classes #1081

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

embecka
Copy link
Contributor
@embecka embecka commented May 9, 2025

Closes #1080.

@embecka embecka force-pushed the master branch 3 times, most recently from 90b906f to 5b7b9aa Compare May 9, 2025 10:35
@dvarrazzo
Copy link
Member
dvarrazzo commented May 10, 2025

@dlax I would like to hear your opinion about a topic related to this improvement.

As you surely remember, initially, in psycopg 3.1 the pipeline mode was accumulating all the results in the cursor, but we changed this behaviour in #604 (we warned against using the feature in 3.1.10 and changed the behaviour in 3.2). However the lack of reliability might have been related to other errors that have been since fixed (like #685, fixed in 3.1.18).

I see some utility in receiving more than one result, especially considering that - I seem to remember - in pipeline mode you can't use more than one query in the same statement. People could use the pipeline mode to request a batch of objects from different tables and then receive all the result to aggregate on the client:

with cur.pipeline():
    cur.execute("select * from parent where id = %s", [rec_id])
    cur.execute("select * from child where parent_id = %s", [rec_id])

    parent = cur.fetchone()
    cur.nextset()
    children = cur.fetchall()

So I was thinking to enable this behaviour on request: what do you think about a parameter on the pipeline() method, such as all_results=True, which would make the results aggregate in the cursor, like in psycopg 3.1 instead of clobbering the current one (which should remain the default behaviour to avoid changing the cursor behaviour between pipeline and non-pipeline mode)?

I haven't reviewed completely the conversations in the tickets linked. Do you have in mind any reasons why this couldn't obviously work?

Comment on lines +926 to +927
await cur.execute(";".join(["select 1"] * count))
assert await alist(cur.results()) == [cur] * count
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would test if the result changed too.

Suggested change
await cur.execute(";".join(["select 1"] * count))
assert await alist(cur.results()) == [cur] * count
await cur.execute(";".join(["select %s" % i for i in range(count)])
ress = await alist(res.fetchall() for res in cur.results())
assert ress == [[(i,)] for i in count]

Comment on lines +934 to +935
await cur.executemany("select 1", [()] * count, returning=returning)
assert await alist(cur.results()) == [cur] * returning * count
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. We could use rowcount to check that even in the returning=False case we iterate on the results. Untested:

Suggested change
await cur.executemany("select 1", [()] * count, returning=returning)
assert await alist(cur.results()) == [cur] * returning * count
await cur.executemany(
"select * from generate_series(0, %s)",
[(i,) for i in range(count)],
returning=returning,
)
if returning:
ress = await alist(res.fetchall() for res in cur.results())
assert ress == [[(j,) for j in range(i)] for i in range(2+1)]
else:
ress = await alist(res.rowcount for res in cur.results())
assert ress == list(range(count))



@pytest.mark.parametrize("count", [1])
def test_results_after_execute(conn, count):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this method makes sense on server cursors. I would raise a NotSupportedError.


@pytest.mark.parametrize("count", [1])
async def test_results_after_execute(aconn, count):
async with aconn.cursor() as cur:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a server cursor, it is a client cursor. You have to give it a name 🙂


.. warning::

More explicitly, `!fetchall()` alone will not return all the
values returned! You must iterate on the results using
`!nextset()`.
`!results()`.
Copy link
Member
@dvarrazzo dvarrazzo May 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the docs, nextset() is also used in the from_pg2 page.

Do we want to change that example? Like:

using the usual `!fetch*()` methods. In order to access all the results,
you can use the `~Cursor.results()` method::

    >>> cur_pg3.execute("SELECT 1; SELECT 2")
    >>> for _ in cur_pg3:
    ...     print(cur_pg3.fetchone())
    (1,)
    (2,)

I don't feel the need to keep the nextset() example, in all its clunkiness. If anything we could add a paragraph like:

Before Psycopg 3.3 you can use `~Cursor.nextset()` instead.

-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a result iterator to all cursor classes
2 participants
0