]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Oracle's "native decimal" metadata begins to return
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 1 Jul 2010 20:57:02 +0000 (16:57 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 1 Jul 2010 20:57:02 +0000 (16:57 -0400)
    ambiguous typing information about numerics
    when columns are embedded in subqueries as well
    as when ROWNUM is consulted with subqueries, as we
    do for limit/offset.  We've added these ambiguous
    conditions to the cx_oracle "convert to Decimal()"
    handler, so that we receive numerics as Decimal
    in more cases instead of as floats.  These are
    then converted, if requested, into Integer
    or Float, or otherwise kept as the lossless
    Decimal [ticket:1840].

CHANGES
lib/sqlalchemy/dialects/oracle/cx_oracle.py
test/dialect/test_oracle.py

diff --git a/CHANGES b/CHANGES
index 721cc4d7321750b9db7fed3b376d597332a088e7..b3b0d652425c6261ff3184d05ae22f10a2e63304 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -131,6 +131,18 @@ CHANGES
   - Fixed ora-8 compatibility flags such that they
     don't cache a stale value from before the first
     database connection actually occurs. [ticket:1819]
+
+  - Oracle's "native decimal" metadata begins to return
+    ambiguous typing information about numerics
+    when columns are embedded in subqueries as well
+    as when ROWNUM is consulted with subqueries, as we 
+    do for limit/offset.  We've added these ambiguous 
+    conditions to the cx_oracle "convert to Decimal()" 
+    handler, so that we receive numerics as Decimal 
+    in more cases instead of as floats.  These are 
+    then converted, if requested, into Integer
+    or Float, or otherwise kept as the lossless
+    Decimal [ticket:1840].
     
 - firebird
   - Fixed incorrect signature in do_execute(), error 
index dd32f3201606e7161e0f8491c03cdfddf7404ee3..01ac1a6855fb33259fda2108be0bf54d598c7c81 100644 (file)
@@ -86,10 +86,11 @@ class _OracleNumeric(sqltypes.Numeric):
         # we apply a connection output handler that 
         # returns Decimal for positive precision + scale NUMBER 
         # types
+        
         if dialect.supports_native_decimal:
             if self.asdecimal and self.scale is None:
                 processors.to_decimal_processor_factory(Decimal)
-            elif not self.asdecimal and self.scale > 0:
+            elif not self.asdecimal:
                 return processors.to_float
             else:
                 return None
@@ -465,9 +466,15 @@ class OracleDialect_cx_oracle(OracleDialect):
             
         cx_Oracle = self.dbapi
         def output_type_handler(cursor, name, defaultType, size, precision, scale):
-            # convert all NUMBER with precision + positive scale to Decimal.
+            # convert all NUMBER with precision + positive scale to Decimal,
+            # or zero precision and 0 or neg scale, indicates "don't know",
             # this effectively allows "native decimal" mode.
-            if defaultType == cx_Oracle.NUMBER and precision and scale > 0:
+
+            if defaultType == cx_Oracle.NUMBER \
+                and (
+                    (precision and scale > 0) or \
+                    (not precision and scale <= 0)
+                ):
                 return cursor.var(
                             cx_Oracle.STRING, 
                             255, 
index cc4bbab22827ef7c7723507c69ee59735e203353..27459bb3f421582efa8ca0e179e82044052fbfb5 100644 (file)
@@ -758,6 +758,90 @@ class TypesTest(TestBase, AssertsCompiledSQL):
         finally:
             t1.drop()
     
+    @testing.provide_metadata
+    def test_numerics_broken_inspection(self):
+        """Numeric scenarios where Oracle type info is 'broken',
+        returning us precision, scale of the form (0, 0) or (0, -127).
+        We convert to Decimal and let int()/float() processors take over.
+        
+        """
+        
+        # this test requires cx_oracle 5
+        
+        foo = Table('foo', metadata,
+            Column('idata', Integer),
+            Column('ndata', Numeric(20, 2)),
+            Column('fdata', Float()),
+        )
+        foo.create()
+        
+        foo.insert().execute(
+            {'idata':5, 'ndata':Decimal("45.6"), 'fdata':45.68392}
+        )
+
+        stmt = """
+        SELECT 
+            idata,
+            ndata,
+            fdata
+        FROM foo
+        """
+        eq_(
+            testing.db.execute(stmt).fetchall(), 
+            [(5, Decimal('45.6'), 45.683920000000001)]
+        )
+        
+        stmt = """
+        SELECT 
+            (SELECT (SELECT idata FROM foo) FROM DUAL) AS idata,
+            (SELECT CAST((SELECT ndata FROM foo) AS NUMERIC(20, 2)) FROM DUAL) AS ndata,
+            (SELECT CAST((SELECT fdata FROM foo) AS FLOAT) FROM DUAL) AS fdata
+        FROM dual
+        """
+        eq_(
+            testing.db.execute(stmt).fetchall(), 
+            [(Decimal('5'), Decimal('45.6'), Decimal('45.68392'))]
+        )
+        eq_(
+            testing.db.execute(text(stmt, 
+                                typemap={
+                                        'idata':Integer(), 
+                                        'ndata':Numeric(20, 2), 
+                                        'fdata':Float()
+                                })).fetchall(),
+            [(5, Decimal('45.6'), 45.683920000000001)]
+        )
+        
+        stmt = """
+        SELECT 
+                anon_1.idata AS anon_1_idata,
+                anon_1.ndata AS anon_1_ndata,
+                anon_1.fdata AS anon_1_fdata
+        FROM (SELECT idata, ndata, fdata
+        FROM (
+            SELECT 
+                (SELECT (SELECT idata FROM foo) FROM DUAL) AS idata,
+                (SELECT CAST((SELECT ndata FROM foo) AS NUMERIC(20, 2)) FROM DUAL) AS ndata,
+                (SELECT CAST((SELECT fdata FROM foo) AS FLOAT) FROM DUAL) AS fdata
+            FROM dual
+        )
+        WHERE ROWNUM >= 0) anon_1
+        """
+        eq_(
+            testing.db.execute(stmt).fetchall(), 
+            [(Decimal('5'), Decimal('45.6'), Decimal('45.68392'))]
+        )
+        eq_(
+            testing.db.execute(text(stmt, 
+                                typemap={
+                                        'anon_1_idata':Integer(), 
+                                        'anon_1_ndata':Numeric(20, 2), 
+                                        'anon_1_fdata':Float()
+                                })).fetchall(),
+            [(5, Decimal('45.6'), 45.683920000000001)]
+        )
+
+        
     def test_reflect_dates(self):
         metadata = MetaData(testing.db)
         Table(