]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix: raise DataError if IntDumper tries to dump another type
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 13 May 2022 14:54:54 +0000 (16:54 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 14 May 2022 00:00:19 +0000 (02:00 +0200)
Allow to dump int subclasses, but not other numeric type, which can lead
to truncation. As seen in #301, the Python implementation truncates; the
c implementation is reporting failing with a TypeError, but on Python
3.8 it raises a deprecation warning instead.

The condition largely happens dumping array of mixed types. However we
should probably test with array of mixed class in a more generic way.

Close #301.

docs/news.rst
psycopg/psycopg/types/numeric.py
psycopg_c/psycopg_c/types/numeric.pyx
tests/types/test_array.py

index 089c536a4cc4e359a563142e74cdff53772bdc76..b8ca1e21a3e134667b77185c71c1052add6f2335 100644 (file)
@@ -28,6 +28,12 @@ Psycopg 3.1 (unreleased)
 - Drop support for Python 3.6.
 
 
+Psycopg 3.0.14 (unreleased)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fail dumping arrays of mixed numbers with DataError (:ticket:`#301`).
+
+
 Current release
 ---------------
 
index dc4d52d5475e28f745ee2d0934db5f3cf3917ad0..f2d0e7d78e229f264502862f00d093af535d7573 100644 (file)
@@ -33,8 +33,15 @@ from .._wrappers import (
 
 class _IntDumper(Dumper):
     def dump(self, obj: Any) -> bytes:
-        # Convert to int in order to dump IntEnum correctly
-        return str(int(obj)).encode()
+        t = type(obj)
+        if t is not int:
+            # Convert to int in order to dump IntEnum correctly
+            if issubclass(t, int):
+                obj = int(obj)
+            else:
+                raise e.DataError(f"ingeger expected, got {type(obj).__name__!r}")
+
+        return str(obj).encode()
 
     def quote(self, obj: Any) -> bytes:
         value = self.dump(obj)
index be538fa19cefb2333358eaf34daca2c30d16045f..502168e2043164b5dac3ea18c9be5982d5e3dc37 100644 (file)
@@ -626,6 +626,11 @@ cdef Py_ssize_t dump_int_to_text(obj, bytearray rv, Py_ssize_t offset) except -1
     cdef char *src
     cdef Py_ssize_t length
 
+    # Ensure an int or a subclass. The 'is' type check is fast.
+    # Passing a float must give an error, but passing an Enum should work.
+    if type(obj) is not int and not isinstance(obj, int):
+        raise e.DataError(f"ingeger expected, got {type(obj).__name__!r}")
+
     val = PyLong_AsLongLongAndOverflow(obj, &overflow)
     if not overflow:
         buf = CDumper.ensure_size(rv, offset, MAXINT8LEN + 1)
index 6fe7a5ffe4b2342787abc1e019f6d18fa93fadc9..5abf207e90132a08931688ae44736163bb68f2f6 100644 (file)
@@ -1,3 +1,4 @@
+from enum import Enum
 from decimal import Decimal
 
 import pytest
@@ -183,6 +184,25 @@ def test_list_number_wrapper(conn, wrapper, fmt_in, fmt_out):
             assert type(i) is want_cls
 
 
+def test_mix_types(conn):
+    class MyEnum(int, Enum):
+        ONE = 2**30
+
+    cur = conn.execute("select %s", ([1, MyEnum.ONE],))
+    assert cur.fetchone() == ([1, 2**30],)
+    assert cur.description[0].type_code == cur.adapters.types["int4"].array_oid
+
+    cur = conn.execute("select %s", ([1, psycopg.types.numeric.Int8(2**60)],))
+    assert cur.fetchone() == ([1, 2**60],)
+    assert cur.description[0].type_code == cur.adapters.types["int8"].array_oid
+
+    with pytest.raises(psycopg.DataError):
+        conn.execute("select %s", ([1, 0.5],))
+
+    with pytest.raises(psycopg.DataError):
+        conn.execute("select %s", ([1, Decimal("0.5")],))
+
+
 @pytest.mark.parametrize("fmt_in", PyFormat)
 def test_empty_list_mix(conn, fmt_in):
     objs = list(range(3))