--- /dev/null
+.. 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.
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:
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:
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
"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,
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"