]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Separate Numeric and Float
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 13 Apr 2020 14:25:27 +0000 (10:25 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 9 Dec 2024 19:29:26 +0000 (14:29 -0500)
the :class:`.Numeric` and :class:`.Float` SQL types have been separated out
so that :class:`.Float` no longer inherits from :class:`.Numeric`; instead,
they both extend from a common mixin :class:`.NumericCommon`.  This
corrects for some architectural shortcomings where numeric and float types
are typically separate, and establishes more consistency with
:class:`.Integer` also being a distinct type.   The change should not have
any end-user implications except for code that may be using
``isinstance()`` to test for the :class:`.Numeric` datatype; third party
dialects which rely upon specific implementation types for numeric and/or
float may also require adjustment to maintain compatibility.

Fixes: #5252
Change-Id: Iadc841340b3d97e3eb5f7e63f0a0cc3cb4e30f74

19 files changed:
doc/build/changelog/unreleased_21/5252.rst [new file with mode: 0644]
doc/build/core/type_basics.rst
lib/sqlalchemy/__init__.py
lib/sqlalchemy/dialects/mssql/base.py
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/dialects/mysql/types.py
lib/sqlalchemy/dialects/oracle/cx_oracle.py
lib/sqlalchemy/dialects/postgresql/_psycopg_common.py
lib/sqlalchemy/dialects/postgresql/asyncpg.py
lib/sqlalchemy/dialects/postgresql/pg8000.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/testing/suite/test_reflection.py
lib/sqlalchemy/types.py
test/dialect/mysql/test_reflection.py
test/ext/test_horizontal_shard.py
test/requirements.py
test/sql/test_metadata.py
test/sql/test_operators.py

diff --git a/doc/build/changelog/unreleased_21/5252.rst b/doc/build/changelog/unreleased_21/5252.rst
new file mode 100644 (file)
index 0000000..79d77b4
--- /dev/null
@@ -0,0 +1,14 @@
+.. change::
+    :tags: change, sql
+    :tickets: 5252
+
+    the :class:`.Numeric` and :class:`.Float` SQL types have been separated out
+    so that :class:`.Float` no longer inherits from :class:`.Numeric`; instead,
+    they both extend from a common mixin :class:`.NumericCommon`.  This
+    corrects for some architectural shortcomings where numeric and float types
+    are typically separate, and establishes more consistency with
+    :class:`.Integer` also being a distinct type.   The change should not have
+    any end-user implications except for code that may be using
+    ``isinstance()`` to test for the :class:`.Numeric` datatype; third party
+    dialects which rely upon specific implementation types for numeric and/or
+    float may also require adjustment to maintain compatibility.
index 817bca601aa158745fa5be063addf1b9938fef7c..c12dd99441c701f01a18fe5d00baf1a0e122dc36 100644 (file)
@@ -217,6 +217,9 @@ type is emitted in ``CREATE TABLE``, such as ``VARCHAR`` see
 .. autoclass:: Numeric
   :members:
 
+.. autoclass:: NumericCommon
+  :members:
+
 .. autoclass:: PickleType
   :members:
 
index 2672fed5141a2e6bedc8887806a1ff27f6105c4c..c3ab0b6d5d3f149e0fca743eb4fec9d84d286f89 100644 (file)
@@ -246,6 +246,7 @@ from .types import LargeBinary as LargeBinary
 from .types import NCHAR as NCHAR
 from .types import NUMERIC as NUMERIC
 from .types import Numeric as Numeric
+from .types import NumericCommon as NumericCommon
 from .types import NVARCHAR as NVARCHAR
 from .types import PickleType as PickleType
 from .types import REAL as REAL
index a617acf3dea953da045cd20fdeb79b684e0a3e02..f03bddbdf732d47dad944529e07430231a77a242 100644 (file)
@@ -2440,7 +2440,7 @@ class MSSQLCompiler(compiler.SQLCompiler):
                 self.process(binary.left, **kw),
                 self.process(binary.right, **kw),
             )
-        elif binary.type._type_affinity is sqltypes.Numeric:
+        elif binary.type._type_affinity in (sqltypes.Numeric, sqltypes.Float):
             type_expression = "ELSE CAST(JSON_VALUE(%s, %s) AS %s)" % (
                 self.process(binary.left, **kw),
                 self.process(binary.right, **kw),
@@ -3710,7 +3710,7 @@ where
                 )
                 coltype = sqltypes.NULLTYPE
             else:
-                if issubclass(coltype, sqltypes.Numeric):
+                if issubclass(coltype, sqltypes.NumericCommon):
                     kwargs["precision"] = numericprec
 
                     if not issubclass(coltype, sqltypes.Float):
index c834495759e9c3f5c04f878df243fe8456dfaf78..bdaccb72ad13f024935a695a65de518dfcb92eb2 100644 (file)
@@ -1017,6 +1017,7 @@ from .reserved_words import RESERVED_WORDS_MYSQL
 from .types import _FloatType
 from .types import _IntegerType
 from .types import _MatchType
+from .types import _NumericCommonType
 from .types import _NumericType
 from .types import _StringType
 from .types import BIGINT
@@ -1114,6 +1115,7 @@ MSInteger = INTEGER
 
 colspecs = {
     _IntegerType: _IntegerType,
+    _NumericCommonType: _NumericCommonType,
     _NumericType: _NumericType,
     _FloatType: _FloatType,
     sqltypes.Numeric: NUMERIC,
@@ -1277,7 +1279,7 @@ class MySQLCompiler(compiler.SQLCompiler):
                     self.process(binary.right, **kw),
                 )
             )
-        elif binary.type._type_affinity is sqltypes.Numeric:
+        elif binary.type._type_affinity in (sqltypes.Numeric, sqltypes.Float):
             if (
                 binary.type.scale is not None
                 and binary.type.precision is not None
@@ -2145,7 +2147,7 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
         )
 
     def _mysql_type(self, type_):
-        return isinstance(type_, (_StringType, _NumericType))
+        return isinstance(type_, (_StringType, _NumericCommonType))
 
     def visit_NUMERIC(self, type_, **kw):
         if type_.precision is None:
index 734f6ae3723c1a5b8f9ba9b07feb240f5ab5667c..cd848c5efc1691699ee78f7061d6984156e3700e 100644 (file)
@@ -14,7 +14,7 @@ from ... import util
 from ...sql import sqltypes
 
 
-class _NumericType:
+class _NumericCommonType:
     """Base for MySQL numeric types.
 
     This is the base both for NUMERIC as well as INTEGER, hence
@@ -27,13 +27,18 @@ class _NumericType:
         self.zerofill = zerofill
         super().__init__(**kw)
 
+
+class _NumericType(_NumericCommonType, sqltypes.Numeric):
+
     def __repr__(self):
         return util.generic_repr(
-            self, to_inspect=[_NumericType, sqltypes.Numeric]
+            self,
+            to_inspect=[_NumericType, _NumericCommonType, sqltypes.Numeric],
         )
 
 
-class _FloatType(_NumericType, sqltypes.Float):
+class _FloatType(_NumericCommonType, sqltypes.Float):
+
     def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
         if isinstance(self, (REAL, DOUBLE)) and (
             (precision is None and scale is not None)
@@ -48,18 +53,19 @@ class _FloatType(_NumericType, sqltypes.Float):
 
     def __repr__(self):
         return util.generic_repr(
-            self, to_inspect=[_FloatType, _NumericType, sqltypes.Float]
+            self, to_inspect=[_FloatType, _NumericCommonType, sqltypes.Float]
         )
 
 
-class _IntegerType(_NumericType, sqltypes.Integer):
+class _IntegerType(_NumericCommonType, sqltypes.Integer):
     def __init__(self, display_width=None, **kw):
         self.display_width = display_width
         super().__init__(**kw)
 
     def __repr__(self):
         return util.generic_repr(
-            self, to_inspect=[_IntegerType, _NumericType, sqltypes.Integer]
+            self,
+            to_inspect=[_IntegerType, _NumericCommonType, sqltypes.Integer],
         )
 
 
index babb916a60209ab58f0af1457ab7f3d7a38a4519..081f3595cd80def92118ea2bb1c6b6b0c95d1bdc 100644 (file)
@@ -499,7 +499,7 @@ class _OracleInteger(sqltypes.Integer):
         return handler
 
 
-class _OracleNumeric(sqltypes.Numeric):
+class _OracleNumericCommon(sqltypes.NumericCommon, sqltypes.TypeEngine):
     is_number = False
 
     def bind_processor(self, dialect):
@@ -575,12 +575,20 @@ class _OracleNumeric(sqltypes.Numeric):
         return handler
 
 
+class _OracleNumeric(_OracleNumericCommon, sqltypes.Numeric):
+    pass
+
+
+class _OracleFloat(_OracleNumericCommon, sqltypes.Float):
+    pass
+
+
 class _OracleUUID(sqltypes.Uuid):
     def get_dbapi_type(self, dbapi):
         return dbapi.STRING
 
 
-class _OracleBinaryFloat(_OracleNumeric):
+class _OracleBinaryFloat(_OracleNumericCommon):
     def get_dbapi_type(self, dbapi):
         return dbapi.NATIVE_FLOAT
 
@@ -593,7 +601,7 @@ class _OracleBINARY_DOUBLE(_OracleBinaryFloat, oracle.BINARY_DOUBLE):
     pass
 
 
-class _OracleNUMBER(_OracleNumeric):
+class _OracleNUMBER(_OracleNumericCommon, sqltypes.Numeric):
     is_number = True
 
 
@@ -852,7 +860,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
                                 arraysize=len_params,
                             )
                         elif (
-                            isinstance(type_impl, _OracleNumeric)
+                            isinstance(type_impl, _OracleNumericCommon)
                             and type_impl.asdecimal
                         ):
                             out_parameters[name] = self.cursor.var(
@@ -1017,7 +1025,7 @@ class OracleDialect_cx_oracle(OracleDialect):
         {
             sqltypes.TIMESTAMP: _CXOracleTIMESTAMP,
             sqltypes.Numeric: _OracleNumeric,
-            sqltypes.Float: _OracleNumeric,
+            sqltypes.Float: _OracleFloat,
             oracle.BINARY_FLOAT: _OracleBINARY_FLOAT,
             oracle.BINARY_DOUBLE: _OracleBINARY_DOUBLE,
             sqltypes.Integer: _OracleInteger,
index 46858c9f22cee665bf29ee8288b3df0f0a531738..16d569b59bd796afc08dd15f56718cfe1b259799 100644 (file)
@@ -27,7 +27,7 @@ from ...engine import processors
 _server_side_id = util.counter()
 
 
-class _PsycopgNumeric(sqltypes.Numeric):
+class _PsycopgNumericCommon(sqltypes.NumericCommon):
     def bind_processor(self, dialect):
         return None
 
@@ -56,8 +56,12 @@ class _PsycopgNumeric(sqltypes.Numeric):
                 )
 
 
-class _PsycopgFloat(_PsycopgNumeric):
-    __visit_name__ = "float"
+class _PsycopgNumeric(_PsycopgNumericCommon, sqltypes.Numeric):
+    pass
+
+
+class _PsycopgFloat(_PsycopgNumericCommon, sqltypes.Float):
+    pass
 
 
 class _PsycopgHStore(HSTORE):
index a362c616e1d0d110cfa28137a13e1c860a955784..c7c20ee029dd4c2be0647329b5e452d55ca848de 100644 (file)
@@ -314,7 +314,7 @@ class AsyncpgJSONPathType(json.JSONPathType):
         return process
 
 
-class AsyncpgNumeric(sqltypes.Numeric):
+class _AsyncpgNumericCommon(sqltypes.NumericCommon):
     render_bind_cast = True
 
     def bind_processor(self, dialect):
@@ -345,9 +345,12 @@ class AsyncpgNumeric(sqltypes.Numeric):
                 )
 
 
-class AsyncpgFloat(AsyncpgNumeric, sqltypes.Float):
-    __visit_name__ = "float"
-    render_bind_cast = True
+class AsyncpgNumeric(_AsyncpgNumericCommon, sqltypes.Numeric):
+    pass
+
+
+class AsyncpgFloat(_AsyncpgNumericCommon, sqltypes.Float):
+    pass
 
 
 class AsyncpgREGCLASS(REGCLASS):
index 0151be0253daed70687a7e129dfc5f3261be9ca6..1f3b3c250d718943618cc6ef83aab52f02c309a5 100644 (file)
@@ -122,7 +122,7 @@ class _PGString(sqltypes.String):
     render_bind_cast = True
 
 
-class _PGNumeric(sqltypes.Numeric):
+class _PGNumericCommon(sqltypes.NumericCommon):
     render_bind_cast = True
 
     def result_processor(self, dialect, coltype):
@@ -150,9 +150,12 @@ class _PGNumeric(sqltypes.Numeric):
                 )
 
 
-class _PGFloat(_PGNumeric, sqltypes.Float):
-    __visit_name__ = "float"
-    render_bind_cast = True
+class _PGNumeric(_PGNumericCommon, sqltypes.Numeric):
+    pass
+
+
+class _PGFloat(_PGNumericCommon, sqltypes.Float):
+    pass
 
 
 class _PGNumericNoBind(_PGNumeric):
index 647d38e64015749014759492d5d2399c5f060f47..dacbfc38af0b94163b3e1aa198ee020d0278c620 100644 (file)
@@ -3106,7 +3106,7 @@ class SQLCompiler(Compiled):
                         (
                             binary.right.type
                             if binary.right.type._type_affinity
-                            is sqltypes.Numeric
+                            in (sqltypes.Numeric, sqltypes.Float)
                             else sqltypes.Numeric()
                         ),
                     ),
index 95d94a27deca2179b16e4566ffd3648f59638744..deb98311cb82a40df59576dcf24acf4de766c97b 100644 (file)
@@ -21,6 +21,7 @@ from typing import Any
 from typing import Callable
 from typing import cast
 from typing import Dict
+from typing import Generic
 from typing import List
 from typing import Optional
 from typing import overload
@@ -363,15 +364,25 @@ class Integer(HasExpressionLookup, TypeEngine[int]):
                 Date: Date,
                 Integer: self.__class__,
                 Numeric: Numeric,
+                Float: Float,
             },
             operators.mul: {
                 Interval: Interval,
                 Integer: self.__class__,
                 Numeric: Numeric,
+                Float: Float,
+            },
+            operators.truediv: {
+                Integer: Numeric,
+                Numeric: Numeric,
+                Float: Float,
             },
-            operators.truediv: {Integer: Numeric, Numeric: Numeric},
             operators.floordiv: {Integer: self.__class__, Numeric: Numeric},
-            operators.sub: {Integer: self.__class__, Numeric: Numeric},
+            operators.sub: {
+                Integer: self.__class__,
+                Numeric: Numeric,
+                Float: Float,
+            },
         }
 
 
@@ -400,7 +411,93 @@ class BigInteger(Integer):
 _N = TypeVar("_N", bound=Union[decimal.Decimal, float])
 
 
-class Numeric(HasExpressionLookup, TypeEngine[_N]):
+class NumericCommon(HasExpressionLookup, TypeEngineMixin, Generic[_N]):
+    """common mixin for the :class:`.Numeric` and :class:`.Float` types.
+
+
+    .. versionadded:: 2.1
+
+    """
+
+    _default_decimal_return_scale = 10
+
+    if TYPE_CHECKING:
+
+        @util.ro_memoized_property
+        def _type_affinity(self) -> Type[NumericCommon[_N]]: ...
+
+    def __init__(
+        self,
+        *,
+        precision: Optional[int],
+        scale: Optional[int],
+        decimal_return_scale: Optional[int],
+        asdecimal: bool,
+    ):
+        self.precision = precision
+        self.scale = scale
+        self.decimal_return_scale = decimal_return_scale
+        self.asdecimal = asdecimal
+
+    @property
+    def _effective_decimal_return_scale(self):
+        if self.decimal_return_scale is not None:
+            return self.decimal_return_scale
+        elif getattr(self, "scale", None) is not None:
+            return self.scale
+        else:
+            return self._default_decimal_return_scale
+
+    def get_dbapi_type(self, dbapi):
+        return dbapi.NUMBER
+
+    def literal_processor(self, dialect):
+        def process(value):
+            return str(value)
+
+        return process
+
+    @property
+    def python_type(self):
+        if self.asdecimal:
+            return decimal.Decimal
+        else:
+            return float
+
+    def bind_processor(self, dialect):
+        if dialect.supports_native_decimal:
+            return None
+        else:
+            return processors.to_float
+
+    @util.memoized_property
+    def _expression_adaptations(self):
+        return {
+            operators.mul: {
+                Interval: Interval,
+                Numeric: self.__class__,
+                Float: self.__class__,
+                Integer: self.__class__,
+            },
+            operators.truediv: {
+                Numeric: self.__class__,
+                Float: self.__class__,
+                Integer: self.__class__,
+            },
+            operators.add: {
+                Numeric: self.__class__,
+                Float: self.__class__,
+                Integer: self.__class__,
+            },
+            operators.sub: {
+                Numeric: self.__class__,
+                Float: self.__class__,
+                Integer: self.__class__,
+            },
+        }
+
+
+class Numeric(NumericCommon[_N], TypeEngine[_N]):
     """Base for non-integer numeric types, such as
     ``NUMERIC``, ``FLOAT``, ``DECIMAL``, and other variants.
 
@@ -434,13 +531,6 @@ class Numeric(HasExpressionLookup, TypeEngine[_N]):
 
     __visit_name__ = "numeric"
 
-    if TYPE_CHECKING:
-
-        @util.ro_memoized_property
-        def _type_affinity(self) -> Type[Numeric[_N]]: ...
-
-    _default_decimal_return_scale = 10
-
     @overload
     def __init__(
         self: Numeric[decimal.Decimal],
@@ -508,41 +598,16 @@ class Numeric(HasExpressionLookup, TypeEngine[_N]):
         conversion overhead.
 
         """
-        self.precision = precision
-        self.scale = scale
-        self.decimal_return_scale = decimal_return_scale
-        self.asdecimal = asdecimal
-
-    @property
-    def _effective_decimal_return_scale(self):
-        if self.decimal_return_scale is not None:
-            return self.decimal_return_scale
-        elif getattr(self, "scale", None) is not None:
-            return self.scale
-        else:
-            return self._default_decimal_return_scale
-
-    def get_dbapi_type(self, dbapi):
-        return dbapi.NUMBER
-
-    def literal_processor(self, dialect):
-        def process(value):
-            return str(value)
-
-        return process
+        super().__init__(
+            precision=precision,
+            scale=scale,
+            decimal_return_scale=decimal_return_scale,
+            asdecimal=asdecimal,
+        )
 
     @property
-    def python_type(self):
-        if self.asdecimal:
-            return decimal.Decimal
-        else:
-            return float
-
-    def bind_processor(self, dialect):
-        if dialect.supports_native_decimal:
-            return None
-        else:
-            return processors.to_float
+    def _type_affinity(self):
+        return Numeric
 
     def result_processor(self, dialect, coltype):
         if self.asdecimal:
@@ -565,24 +630,8 @@ class Numeric(HasExpressionLookup, TypeEngine[_N]):
             else:
                 return None
 
-    @util.memoized_property
-    def _expression_adaptations(self):
-        return {
-            operators.mul: {
-                Interval: Interval,
-                Numeric: self.__class__,
-                Integer: self.__class__,
-            },
-            operators.truediv: {
-                Numeric: self.__class__,
-                Integer: self.__class__,
-            },
-            operators.add: {Numeric: self.__class__, Integer: self.__class__},
-            operators.sub: {Numeric: self.__class__, Integer: self.__class__},
-        }
 
-
-class Float(Numeric[_N]):
+class Float(NumericCommon[_N], TypeEngine[_N]):
     """Type representing floating point types, such as ``FLOAT`` or ``REAL``.
 
     This type returns Python ``float`` objects by default, unless the
@@ -669,9 +718,16 @@ class Float(Numeric[_N]):
          as the default for decimal_return_scale, if not otherwise specified.
 
         """  # noqa: E501
-        self.precision = precision
-        self.asdecimal = asdecimal
-        self.decimal_return_scale = decimal_return_scale
+        super().__init__(
+            precision=precision,
+            scale=None,
+            asdecimal=asdecimal,
+            decimal_return_scale=decimal_return_scale,
+        )
+
+    @property
+    def _type_affinity(self):
+        return Float
 
     def result_processor(self, dialect, coltype):
         if self.asdecimal:
@@ -2018,8 +2074,11 @@ class _AbstractInterval(HasExpressionLookup, TypeEngine[dt.timedelta]):
                 Time: Time,
             },
             operators.sub: {Interval: self.__class__},
-            operators.mul: {Numeric: self.__class__},
-            operators.truediv: {Numeric: self.__class__},
+            operators.mul: {Numeric: self.__class__, Float: self.__class__},
+            operators.truediv: {
+                Numeric: self.__class__,
+                Float: self.__class__,
+            },
         }
 
     @util.ro_non_memoized_property
index 91113be9b49bedada5b57d5e9a139d87df3e2bf0..d738f76893391db577dd6eea124eef3fc043d830 100644 (file)
@@ -1649,6 +1649,7 @@ class ComponentReflectionTest(ComparesTables, OneConnectionTablesTest):
                             [
                                 sql_types.Integer,
                                 sql_types.Numeric,
+                                sql_types.Float,
                                 sql_types.DateTime,
                                 sql_types.Date,
                                 sql_types.Time,
index a5bb56cf6616213c1c26c52534cc36542919baf6..a1374d94f68792aebbcc6348314c86247390c97d 100644 (file)
@@ -47,6 +47,7 @@ from .sql.sqltypes import NULLTYPE as NULLTYPE
 from .sql.sqltypes import NullType as NullType
 from .sql.sqltypes import NUMERIC as NUMERIC
 from .sql.sqltypes import Numeric as Numeric
+from .sql.sqltypes import NumericCommon as NumericCommon
 from .sql.sqltypes import NVARCHAR as NVARCHAR
 from .sql.sqltypes import PickleType as PickleType
 from .sql.sqltypes import REAL as REAL
index 92cf3818e2477f5ecf2347ba74fb09cacfeb2741..f3210ad61522ce829a1eb6f347dfe1d7def5cced 100644 (file)
@@ -7,6 +7,7 @@ from sqlalchemy import DDL
 from sqlalchemy import DefaultClause
 from sqlalchemy import event
 from sqlalchemy import exc
+from sqlalchemy import Float
 from sqlalchemy import ForeignKey
 from sqlalchemy import ForeignKeyConstraint
 from sqlalchemy import Index
@@ -298,7 +299,7 @@ class ReflectionTest(fixtures.TestBase, AssertsCompiledSQL):
         col = insp.get_columns("t1")[0]
         if hasattr(expected, "match"):
             assert expected.match(col["default"])
-        elif isinstance(datatype_inst, (Integer, Numeric)):
+        elif isinstance(datatype_inst, (Integer, Numeric, Float)):
             pattern = re.compile(r"\'?%s\'?" % expected)
             assert pattern.match(col["default"])
         else:
index 9aa38c9c69017c8966df80a37bd02d719550024a..8215c44e5d09bad5a268274ec9892bcec7a41bed 100644 (file)
@@ -6,10 +6,10 @@ from sqlalchemy import DateTime
 from sqlalchemy import delete
 from sqlalchemy import event
 from sqlalchemy import exc
-from sqlalchemy import Float
 from sqlalchemy import ForeignKey
 from sqlalchemy import inspect
 from sqlalchemy import Integer
+from sqlalchemy import Numeric
 from sqlalchemy import select
 from sqlalchemy import sql
 from sqlalchemy import String
@@ -81,7 +81,7 @@ class ShardTest:
             metadata,
             Column("id", Integer, primary_key=True),
             Column("location_id", Integer, ForeignKey(weather_locations.c.id)),
-            Column("temperature", Float),
+            Column("temperature", Numeric(asdecimal=False)),
             Column("report_time", DateTime, default=datetime.datetime.now),
             schema=cls.schema,
         )
index a5f4ee11ec826cd022ca9995be720c6fbb7f29d8..ce19f1bcc478e77893f77d73b110db5928e95169 100644 (file)
@@ -1488,10 +1488,6 @@ class DefaultRequirements(SuiteRequirements):
     def fetch_null_from_numeric(self):
         return skip_if(("mssql+pyodbc", None, None, "crashes due to bug #351"))
 
-    @property
-    def float_is_numeric(self):
-        return exclusions.fails_if(["oracle"])
-
     @property
     def duplicate_key_raises_integrity_error(self):
         return exclusions.open()
index c9c6c55c02a2ea726a5418f60a0961999c78840b..b7a2dedbf1c260d0e8fbac4db143d2e34ba3e667 100644 (file)
@@ -2136,13 +2136,19 @@ class PKAutoIncrementTest(fixtures.TestBase):
         )
 
     def test_float_illegal_autoinc(self):
-        """test that Float is not acceptable if autoincrement=True"""
+        """test that Float is not acceptable if autoincrement=True
+
+        note this changed in 2.1 with #5252 where Numeric/Float were split out
+
+        """
         t = Table("t", MetaData(), Column("a", Float, autoincrement=True))
         pk = PrimaryKeyConstraint(t.c.a)
         t.append_constraint(pk)
 
         with expect_raises_message(
-            exc.ArgumentError, "Column type FLOAT with non-zero scale "
+            exc.ArgumentError,
+            "Column type FLOAT on column 't.a' is not compatible "
+            "with autoincrement=True",
         ):
             pk._autoincrement_column,
 
index 1804d02ca9b6e025bbc48c5dfa36db075136e59b..fbe9ba3900d1359a30f9d5507d020a53521100ef 100644 (file)
@@ -8,6 +8,7 @@ from sqlalchemy import and_
 from sqlalchemy import between
 from sqlalchemy import bindparam
 from sqlalchemy import exc
+from sqlalchemy import Float
 from sqlalchemy import Integer
 from sqlalchemy import join
 from sqlalchemy import LargeBinary
@@ -1105,7 +1106,7 @@ class JSONIndexOpTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         return testing.combinations(
             ("integer", Integer),
             ("boolean", Boolean),
-            ("float", Numeric),
+            ("float", Float),
             ("string", String),
         )(fn)