]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix: manage dumpers returning None in nested dumpers
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 19 Sep 2022 01:20:38 +0000 (02:20 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 1 Jun 2024 11:07:21 +0000 (13:07 +0200)
psycopg/psycopg/types/composite.py
psycopg/psycopg/types/json.py
psycopg/psycopg/types/range.py
tests/types/test_composite.py
tests/types/test_multirange.py
tests/types/test_range.py

index d3f41b675bab4bcb3591bdd7694581abc1d04124..174727cfbde46dd1b982fef92af212dd92b71962 100644 (file)
@@ -97,7 +97,9 @@ class SequenceDumper(RecursiveDumper):
 
             dumper = self._tx.get_dumper(item, PyFormat.from_pq(self.format))
             ad = dumper.dump(item)
-            if not ad:
+            if ad is None:
+                ad = b""
+            elif not ad:
                 ad = b'""'
             elif self._re_needs_quotes.search(ad):
                 ad = b'"' + self._re_esc.sub(rb"\1\1", ad) + b'"'
index 0f5651e71e826af53b73eba11bb411b6b419826d..7ac2be63f179973fff3cc9db27713940330a1df8 100644 (file)
@@ -172,7 +172,11 @@ class JsonbBinaryDumper(_JsonDumper):
     oid = _oids.JSONB_OID
 
     def dump(self, obj: Any) -> Optional[Buffer]:
-        return b"\x01" + super().dump(obj)
+        obj_bytes = super().dump(obj)
+        if obj_bytes is not None:
+            return b"\x01" + obj_bytes
+        else:
+            return None
 
 
 class _JsonLoader(Loader):
index c3d472621dca90804613b24098258360fc1014c8..dba34139cbc2fc9c39f8b372d1f6715b7957cf04 100644 (file)
@@ -372,7 +372,9 @@ def dump_range_text(obj: Range[Any], dump: DumpFunc) -> Buffer:
 
     def dump_item(item: Any) -> Buffer:
         ad = dump(item)
-        if not ad:
+        if ad is None:
+            return b""
+        elif not ad:
             return b'""'
         elif _re_needs_quotes.search(ad):
             return b'"' + _re_esc.sub(rb"\1\1", ad) + b'"'
index 2a2a3a87806bd07276451df401d1219a3d14b022..b0433f19b3983fd0ca203a5210c9868221a6b737 100644 (file)
@@ -9,6 +9,7 @@ from psycopg.types.composite import TupleDumper, TupleBinaryDumper
 
 from ..utils import eur
 from ..fix_crdb import is_crdb, crdb_skip_message
+from ..test_adapt import StrNoneDumper, StrNoneBinaryDumper
 
 
 pytestmark = pytest.mark.crdb_skip("composite")
@@ -69,6 +70,23 @@ def test_dump_tuple(conn, rec, obj):
     assert res == obj
 
 
+def test_dump_tuple_null(conn):
+    cur = conn.cursor()
+    cur.execute(
+        """
+        drop type if exists tmptype;
+        create type tmptype as (f1 text, f2 text);
+        """
+    )
+    info = CompositeInfo.fetch(conn, "tmptype")
+    register_composite(info, conn)
+    conn.adapters.register_dumper(str, StrNoneDumper)
+    conn.adapters.register_dumper(str, StrNoneBinaryDumper)
+
+    res = conn.execute("select %s::tmptype", [("foo", "")]).fetchone()[0]
+    assert res == ("foo", None)
+
+
 @pytest.mark.parametrize("fmt_out", pq.Format)
 def test_load_all_chars(conn, fmt_out):
     cur = conn.cursor(binary=fmt_out)
index 65ff0fe0e08513b3c458c60897c3bed0cfddc3fc..2b0fecac1957e27882b3df6df37d80a703d6dfd8 100644 (file)
@@ -14,6 +14,7 @@ from psycopg.types.multirange import register_multirange
 
 from ..utils import eur
 from .test_range import create_test_range
+from ..test_adapt import StrNoneDumper, StrNoneBinaryDumper
 
 pytestmark = [
     pytest.mark.pg(">= 14"),
@@ -409,6 +410,18 @@ def test_dump_custom_empty(conn, testmr):
     assert cur.fetchone()[0] is True
 
 
+@pytest.mark.parametrize("fmt_in", PyFormat)
+def test_dump_custom_none(conn, fmt_in):
+    info = MultirangeInfo.fetch(conn, "testmultirange")
+    register_multirange(info, conn)
+    conn.adapters.register_dumper(str, StrNoneDumper)
+    conn.adapters.register_dumper(str, StrNoneBinaryDumper)
+
+    r = Multirange[str]()
+    cur = conn.execute("select '{}'::testmultirange = %s", (r,))
+    assert cur.fetchone()[0] is True
+
+
 @pytest.mark.parametrize("fmt_out", pq.Format)
 def test_load_custom_empty(conn, testmr, fmt_out):
     info = MultirangeInfo.fetch(conn, "testmultirange")
index b1026d3027c6f108d554340616184261e3a45ec4..08b2387ab3d3e468a1ecdc8921c50b03ba9aa519 100644 (file)
@@ -12,6 +12,7 @@ from psycopg.types.range import Range, RangeInfo, register_range
 
 from ..utils import eur
 from ..fix_crdb import is_crdb, crdb_skip_message
+from ..test_adapt import StrNoneDumper, StrNoneBinaryDumper
 
 pytestmark = pytest.mark.crdb_skip("range")
 
@@ -325,6 +326,20 @@ def test_dump_custom_empty(conn, testrange):
     assert cur.fetchone()[0] is True
 
 
+@pytest.mark.parametrize("fmt_in", PyFormat)
+def test_dump_custom_null(conn, testrange, fmt_in):
+    info = RangeInfo.fetch(conn, "testrange")
+    register_range(info, conn)
+    conn.adapters.register_dumper(str, StrNoneDumper)
+    conn.adapters.register_dumper(str, StrNoneBinaryDumper)
+
+    r = Range[str]("", "foo")
+    cur = conn.execute(f"select %{fmt_in.value}::testrange", (r,))
+    r1 = cur.fetchone()[0]
+    assert r1.lower is None
+    assert r1.upper == "foo"
+
+
 def test_dump_quoting(conn, testrange):
     info = RangeInfo.fetch(conn, "testrange")
     register_range(info, conn)