From: Daniele Varrazzo Date: Thu, 15 Dec 2022 11:03:44 +0000 (+0000) Subject: fix(copy): don't create a row maker on copy X-Git-Tag: 3.1.6~1^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1308dd0dd7d32da768cbc024d29e25c0e0fc1c4b;p=thirdparty%2Fpsycopg.git fix(copy): don't create a row maker on copy A COPY_OUT result has columns, but no names for the columns. This case must be handled in cur.description (see #235) but we don't need to handle it in copy. If we did handle it in copy, we would need a column name fallback, which we forgot to handle, hence the problem in #460. Close #460. --- diff --git a/docs/news.rst b/docs/news.rst index f26d4007b..318c34a88 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -7,6 +7,12 @@ ``psycopg`` release notes ========================= +Psycopg 3.1.6 (unreleased) +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fix `cursor.copy()` with cursors using row factories (:ticket:`#460`). + + Current release --------------- diff --git a/psycopg/psycopg/rows.py b/psycopg/psycopg/rows.py index 3bd921577..4f96a1af0 100644 --- a/psycopg/psycopg/rows.py +++ b/psycopg/psycopg/rows.py @@ -244,7 +244,7 @@ def _get_nfields(res: "PGresult") -> Optional[int]: """ nfields = res.nfields - if nfields or res.status == TUPLES_OK or res.status == SINGLE_TUPLE: + if res.status == TUPLES_OK or res.status == SINGLE_TUPLE: return nfields else: return None diff --git a/tests/test_copy.py b/tests/test_copy.py index 74e190fce..17cf2fc78 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -71,19 +71,31 @@ def test_copy_out_read(conn, format): @pytest.mark.parametrize("format", Format) -def test_copy_out_iter(conn, format): +@pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"]) +def test_copy_out_iter(conn, format, row_factory): if format == pq.Format.TEXT: want = [row + b"\n" for row in sample_text.splitlines()] else: want = sample_binary_rows - cur = conn.cursor() + rf = getattr(psycopg.rows, row_factory) + cur = conn.cursor(row_factory=rf) with cur.copy(f"copy ({sample_values}) to stdout (format {format.name})") as copy: assert list(copy) == want assert conn.info.transaction_status == conn.TransactionStatus.INTRANS +@pytest.mark.parametrize("format", Format) +@pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"]) +def test_copy_out_no_result(conn, format, row_factory): + rf = getattr(psycopg.rows, row_factory) + cur = conn.cursor(row_factory=rf) + with cur.copy(f"copy ({sample_values}) to stdout (format {format.name})"): + with pytest.raises(e.ProgrammingError): + cur.fetchone() + + @pytest.mark.parametrize("ph, params", [("%s", (10,)), ("%(n)s", {"n": 10})]) def test_copy_out_param(conn, ph, params): cur = conn.cursor() diff --git a/tests/test_copy_async.py b/tests/test_copy_async.py index 9b926a213..59389dd73 100644 --- a/tests/test_copy_async.py +++ b/tests/test_copy_async.py @@ -53,13 +53,15 @@ async def test_copy_out_read(aconn, format): @pytest.mark.parametrize("format", Format) -async def test_copy_out_iter(aconn, format): +@pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"]) +async def test_copy_out_iter(aconn, format, row_factory): if format == pq.Format.TEXT: want = [row + b"\n" for row in sample_text.splitlines()] else: want = sample_binary_rows - cur = aconn.cursor() + rf = getattr(psycopg.rows, row_factory) + cur = aconn.cursor(row_factory=rf) async with cur.copy( f"copy ({sample_values}) to stdout (format {format.name})" ) as copy: @@ -68,6 +70,16 @@ async def test_copy_out_iter(aconn, format): assert aconn.info.transaction_status == aconn.TransactionStatus.INTRANS +@pytest.mark.parametrize("format", Format) +@pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"]) +async def test_copy_out_no_result(aconn, format, row_factory): + rf = getattr(psycopg.rows, row_factory) + cur = aconn.cursor(row_factory=rf) + async with cur.copy(f"copy ({sample_values}) to stdout (format {format.name})"): + with pytest.raises(e.ProgrammingError): + await cur.fetchone() + + @pytest.mark.parametrize("ph, params", [("%s", (10,)), ("%(n)s", {"n": 10})]) async def test_copy_out_param(aconn, ph, params): cur = aconn.cursor()