From: Daniele Varrazzo Date: Fri, 13 May 2022 14:54:54 +0000 (+0200) Subject: fix: raise DataError if IntDumper tries to dump another type X-Git-Tag: 3.1~101^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=56aa235f2b21954ff6bab829da2d01361b18d617;p=thirdparty%2Fpsycopg.git fix: raise DataError if IntDumper tries to dump another type 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. --- diff --git a/docs/news.rst b/docs/news.rst index 089c536a4..b8ca1e21a 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -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 --------------- diff --git a/psycopg/psycopg/types/numeric.py b/psycopg/psycopg/types/numeric.py index dc4d52d54..f2d0e7d78 100644 --- a/psycopg/psycopg/types/numeric.py +++ b/psycopg/psycopg/types/numeric.py @@ -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) diff --git a/psycopg_c/psycopg_c/types/numeric.pyx b/psycopg_c/psycopg_c/types/numeric.pyx index be538fa19..502168e20 100644 --- a/psycopg_c/psycopg_c/types/numeric.pyx +++ b/psycopg_c/psycopg_c/types/numeric.pyx @@ -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) diff --git a/tests/types/test_array.py b/tests/types/test_array.py index 6fe7a5ffe..5abf207e9 100644 --- a/tests/types/test_array.py +++ b/tests/types/test_array.py @@ -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))