]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Don't use cx_Oracle.NATIVE_INT in output type handlers
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 19 Jan 2019 02:30:21 +0000 (21:30 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 19 Jan 2019 15:34:47 +0000 (10:34 -0500)
Fixed regression in integer precision logic due to the refactor of the
cx_Oracle dialect in 1.2.  We now no longer apply the cx_Oracle.NATIVE_INT
type to result columns sending integer values (detected as positive
precision with scale ==0) which encounters integer overflow issues with
values that go beyond the 32 bit boundary.  Instead, the output variable
is left untyped so that cx_Oracle can choose the best option.

Fixes: #4457
Change-Id: I1e3114c2f37bf028fb1f521a3e9789a77e3a7491

doc/build/changelog/unreleased_12/4457.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/oracle/cx_oracle.py
test/dialect/oracle/test_types.py

diff --git a/doc/build/changelog/unreleased_12/4457.rst b/doc/build/changelog/unreleased_12/4457.rst
new file mode 100644 (file)
index 0000000..902dc74
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+   :tags: bug, oracle
+   :tickets: 4457
+
+   Fixed regression in integer precision logic due to the refactor of the
+   cx_Oracle dialect in 1.2.  We now no longer apply the cx_Oracle.NATIVE_INT
+   type to result columns sending integer values (detected as positive
+   precision with scale ==0) which encounters integer overflow issues with
+   values that go beyond the 32 bit boundary.  Instead, the output variable
+   is left untyped so that cx_Oracle can choose the best option.
index ecf949dad25a8e8a5c9ba781f6e4d320991fe6b5..1164c09f77785f61075f53789ba88aa9b314b96e 100644 (file)
@@ -344,10 +344,10 @@ class _OracleNumeric(sqltypes.Numeric):
         cx_Oracle = dialect.dbapi
 
         is_cx_oracle_6 = dialect._is_cx_oracle_6
-        has_native_int = dialect._has_native_int
 
         def handler(cursor, name, default_type, size, precision, scale):
             outconverter = None
+
             if precision:
                 if self.asdecimal:
                     if default_type == cx_Oracle.NATIVE_FLOAT:
@@ -362,13 +362,13 @@ class _OracleNumeric(sqltypes.Numeric):
                         outconverter = dialect._to_decimal
                 else:
                     if self.is_number and scale == 0:
-                        if has_native_int:
-                            type_ = cx_Oracle.NATIVE_INT
-                        else:
-                            type_ = cx_Oracle.NUMBER
-                            outconverter = int
+                        # integer. cx_Oracle is observed to handle the widest
+                        # variety of ints when no directives are passed,
+                        # from 5.2 to 7.0.  See [ticket:4457]
+                        return None
                     else:
                         type_ = cx_Oracle.NATIVE_FLOAT
+
             else:
                 if self.asdecimal:
                     if default_type == cx_Oracle.NATIVE_FLOAT:
@@ -381,11 +381,10 @@ class _OracleNumeric(sqltypes.Numeric):
                         outconverter = dialect._to_decimal
                 else:
                     if self.is_number and scale == 0:
-                        if has_native_int:
-                            type_ = cx_Oracle.NATIVE_INT
-                        else:
-                            type_ = cx_Oracle.NUMBER
-                            outconverter = int
+                        # integer. cx_Oracle is observed to handle the widest
+                        # variety of ints when no directives are passed,
+                        # from 5.2 to 7.0.  See [ticket:4457]
+                        return None
                     else:
                         type_ = cx_Oracle.NATIVE_FLOAT
 
@@ -778,8 +777,6 @@ class OracleDialect_cx_oracle(OracleDialect):
                     "cx_Oracle version 5.2 and above are supported"
                 )
 
-            self._has_native_int = hasattr(cx_Oracle, "NATIVE_INT")
-
             self._include_setinputsizes = {
                 cx_Oracle.NCLOB,
                 cx_Oracle.CLOB,
index 476159da197ebf14aa2c40985a344c57d6448147..dbe91ec03058e37ce83f55c1d2d7faf7f580eff1 100644 (file)
@@ -679,6 +679,53 @@ class TypesTest(fixtures.TestBase):
         value = testing.db.scalar("SELECT 5.66 FROM DUAL")
         assert isinstance(value, decimal.Decimal)
 
+    @testing.only_on("oracle+cx_oracle", "cx_oracle-specific feature")
+    def test_raw_numerics(self):
+        query_cases = [
+            (
+                "Max 32-bit Number",
+                "SELECT CAST(2147483647 AS NUMBER(19,0)) FROM dual",
+            ),
+            (
+                "Min 32-bit Number",
+                "SELECT CAST(-2147483648 AS NUMBER(19,0)) FROM dual",
+            ),
+            (
+                "32-bit Integer Overflow",
+                "SELECT CAST(2147483648 AS NUMBER(19,0)) FROM dual",
+            ),
+            (
+                "32-bit Integer Underflow",
+                "SELECT CAST(-2147483649 AS NUMBER(19,0)) FROM dual",
+            ),
+            (
+                "Max Number with Precision 19",
+                "SELECT CAST(9999999999999999999 AS NUMBER(19,0)) FROM dual",
+            ),
+            (
+                "Min Number with Precision 19",
+                "SELECT CAST(-9999999999999999999 AS NUMBER(19,0)) FROM dual",
+            ),
+        ]
+
+        with testing.db.connect() as conn:
+            for title, stmt in query_cases:
+                # get a brand new connection that definitely is not
+                # in the pool to avoid any outputtypehandlers
+                cx_oracle_raw = testing.db.pool._creator()
+                cursor = cx_oracle_raw.cursor()
+                cursor.execute(stmt)
+                cx_oracle_result = cursor.fetchone()[0]
+                cursor.close()
+
+                sqla_result = conn.scalar(stmt)
+
+                print(
+                    "%s cx_oracle=%s sqlalchemy=%s"
+                    % (title, cx_oracle_result, sqla_result)
+                )
+                eq_(sqla_result, cx_oracle_result)
+
     @testing.only_on("oracle+cx_oracle", "cx_oracle-specific feature")
     @testing.fails_if(
         testing.requires.python3, "cx_oracle always returns unicode on py3k"