]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix(c): fix excessive buffer allocation in binary copy
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 1 Sep 2025 15:22:34 +0000 (17:22 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 1 Sep 2025 16:05:51 +0000 (18:05 +0200)
Fix #1147

docs/news.rst
psycopg_c/psycopg_c/_psycopg/copy.pyx
tests/test_copy.py
tests/test_copy_async.py

index 973f62307e122b4c5c556e453352244f471179f0..705a3f1d0dc094fc3d7be88a078f55c1e2bf9b20 100644 (file)
@@ -39,6 +39,7 @@ Psycopg 3.2.10 (unreleased)
 - Fix memory leak when lambda/local functions are used as argument for
   `~.psycopg.types.json.set_json_dumps()`, `~.psycopg.types.json.set_json_loads()`
   (:ticket:`#1108`).
+- Fix bad data on error in binary copy (:ticket:`#1147`).
 - Fix `psycopg_binary.__version__`.
 
 
index cf29252bf35f5f089bc2e9e64764bcf9dd9a9afe..b130f69d46834d3f3fd54c9e2d348d7d333054ff 100644 (file)
@@ -34,10 +34,7 @@ def format_row_binary(
     else:
         pos = PyByteArray_GET_SIZE(out)
 
-    # let's start from a nice chunk
-    # (larger than most fixed size; for variable ones, oh well, we'll resize it)
-    cdef char *target = CDumper.ensure_size(
-        out, pos, sizeof(berowlen) + 20 * rowlen)
+    cdef char *target = CDumper.ensure_size(out, pos, sizeof(berowlen))
 
     # Write the number of fields as network-order 16 bits
     memcpy(target, <void *>&berowlen, sizeof(berowlen))
index 3c64196d43d5f9cadabd2fa4286b85a0ccfe2006..9b5604a9cea8994c87be1db4478f6a45b33294c9 100644 (file)
@@ -643,6 +643,18 @@ def test_description(conn):
         assert cur.description[2].name == "column_3"
 
 
+def test_binary_partial_row(conn):
+    cur = conn.cursor()
+    ensure_table(cur, "id serial primary key, num int4, arr int4[][]")
+    with pytest.raises(
+        psycopg.DataError, match="nested lists have inconsistent depths"
+    ):
+        with cur.copy("copy copy_in (num, arr) from stdin (format binary)") as copy:
+            copy.set_types(["int4", "int4[]"])
+            copy.write_row([15, None])
+            copy.write_row([16, [[None], None]])
+
+
 @pytest.mark.parametrize(
     "format, buffer",
     [(pq.Format.TEXT, "sample_text"), (pq.Format.BINARY, "sample_binary")],
index 7fa8223d63474d8cdd3c802327da506e9f0218ba..ae1fce2add05391f4b06a593cbe9a7684eac2084 100644 (file)
@@ -657,6 +657,20 @@ async def test_description(aconn):
         assert cur.description[2].name == "column_3"
 
 
+async def test_binary_partial_row(aconn):
+    cur = aconn.cursor()
+    await ensure_table_async(cur, "id serial primary key, num int4, arr int4[][]")
+    with pytest.raises(
+        psycopg.DataError, match="nested lists have inconsistent depths"
+    ):
+        async with cur.copy(
+            "copy copy_in (num, arr) from stdin (format binary)"
+        ) as copy:
+            copy.set_types(["int4", "int4[]"])
+            await copy.write_row([15, None])
+            await copy.write_row([16, [[None], None]])
+
+
 @pytest.mark.parametrize(
     "format, buffer",
     [(pq.Format.TEXT, "sample_text"), (pq.Format.BINARY, "sample_binary")],