diff options
| author | zeeeeeb <5767468+zeeeeeb@users.noreply.github.com> | 2022-02-12 14:00:02 -0500 |
|---|---|---|
| committer | mike bayer <mike_mp@zzzcomputing.com> | 2022-02-25 00:51:32 +0000 |
| commit | b9d231869d7e39decabdec12478e359c4dcb95ee (patch) | |
| tree | c6d8ebecc9c73206816cb54211f28a6dd7180e76 /lib/sqlalchemy/dialects/oracle | |
| parent | 0353a9db76db6a46fa63d99a1d05c5cac45ea460 (diff) | |
| download | sqlalchemy-b9d231869d7e39decabdec12478e359c4dcb95ee.tar.gz | |
Implement generic Double and related fixed types
Added :class:`.Double`, :class:`.DOUBLE`, :class:`.DOUBLE_PRECISION`
datatypes to the base ``sqlalchemy.`` module namespace, for explicit use of
double/double precision as well as generic "double" datatypes. Use
:class:`.Double` for generic support that will resolve to DOUBLE/DOUBLE
PRECISION/FLOAT as needed for different backends.
Implemented DDL and reflection support for ``FLOAT`` datatypes which
include an explicit "binary_precision" value. Using the Oracle-specific
:class:`_oracle.FLOAT` datatype, the new parameter
:paramref:`_oracle.FLOAT.binary_precision` may be specified which will
render Oracle's precision for floating point types directly. This value is
interpreted during reflection. Upon reflecting back a ``FLOAT`` datatype,
the datatype returned is one of :class:`_types.DOUBLE_PRECISION` for a
``FLOAT`` for a precision of 126 (this is also Oracle's default precision
for ``FLOAT``), :class:`_types.REAL` for a precision of 63, and
:class:`_oracle.FLOAT` for a custom precision, as per Oracle documentation.
As part of this change, the generic :paramref:`_sqltypes.Float.precision`
value is explicitly rejected when generating DDL for Oracle, as this
precision cannot be accurately converted to "binary precision"; instead, an
error message encourages the use of
:meth:`_sqltypes.TypeEngine.with_variant` so that Oracle's specific form of
precision may be chosen exactly. This is a backwards-incompatible change in
behavior, as the previous "precision" value was silently ignored for
Oracle.
Fixes: #5465
Closes: #7674
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/7674
Pull-request-sha: 5c68419e5aee2e27bf21a8ac9eb5950d196c77e5
Change-Id: I831f4af3ee3b23fde02e8f6393c83e23dd7cd34d
Diffstat (limited to 'lib/sqlalchemy/dialects/oracle')
| -rw-r--r-- | lib/sqlalchemy/dialects/oracle/__init__.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/oracle/base.py | 104 |
2 files changed, 95 insertions, 10 deletions
diff --git a/lib/sqlalchemy/dialects/oracle/__init__.py b/lib/sqlalchemy/dialects/oracle/__init__.py index c83e0573d..8994b4a10 100644 --- a/lib/sqlalchemy/dialects/oracle/__init__.py +++ b/lib/sqlalchemy/dialects/oracle/__init__.py @@ -24,6 +24,7 @@ from .base import NUMBER from .base import NVARCHAR from .base import NVARCHAR2 from .base import RAW +from .base import REAL from .base import ROWID from .base import TIMESTAMP from .base import VARCHAR diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py index 578450d24..1ae58b8f4 100644 --- a/lib/sqlalchemy/dialects/oracle/base.py +++ b/lib/sqlalchemy/dialects/oracle/base.py @@ -558,10 +558,12 @@ from ...sql import visitors from ...types import BLOB from ...types import CHAR from ...types import CLOB +from ...types import DOUBLE_PRECISION from ...types import FLOAT from ...types import INTEGER from ...types import NCHAR from ...types import NVARCHAR +from ...types import REAL from ...types import TIMESTAMP from ...types import VARCHAR @@ -625,8 +627,49 @@ class NUMBER(sqltypes.Numeric, sqltypes.Integer): return sqltypes.Integer -class DOUBLE_PRECISION(sqltypes.Float): - __visit_name__ = "DOUBLE_PRECISION" +class FLOAT(sqltypes.FLOAT): + """Oracle FLOAT. + + This is the same as :class:`_sqltypes.FLOAT` except that + an Oracle-specific :paramref:`_oracle.FLOAT.binary_precision` + parameter is accepted, and + the :paramref:`_sqltypes.Float.precision` parameter is not accepted. + + Oracle FLOAT types indicate precision in terms of "binary precision", which + defaults to 126. For a REAL type, the value is 63. This parameter does not + cleanly map to a specific number of decimal places but is roughly + equivalent to the desired number of decimal places divided by 0.3103. + + .. versionadded:: 2.0 + + """ + + __visit_name__ = "FLOAT" + + def __init__( + self, + binary_precision=None, + asdecimal=False, + decimal_return_scale=None, + ): + r""" + Construct a FLOAT + + :param binary_precision: Oracle binary precision value to be rendered + in DDL. This may be approximated to the number of decimal characters + using the formula "decimal precision = 0.30103 * binary precision". + The default value used by Oracle for FLOAT / DOUBLE PRECISION is 126. + + :param asdecimal: See :paramref:`_sqltypes.Float.asdecimal` + + :param decimal_return_scale: See + :paramref:`_sqltypes.Float.decimal_return_scale` + + """ + super().__init__( + asdecimal=asdecimal, decimal_return_scale=decimal_return_scale + ) + self.binary_precision = binary_precision class BINARY_DOUBLE(sqltypes.Float): @@ -742,6 +785,7 @@ ischema_names = { "RAW": RAW, "FLOAT": FLOAT, "DOUBLE PRECISION": DOUBLE_PRECISION, + "REAL": REAL, "LONG": LONG, "BINARY_DOUBLE": BINARY_DOUBLE, "BINARY_FLOAT": BINARY_FLOAT, @@ -760,6 +804,9 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler): def visit_float(self, type_, **kw): return self.visit_FLOAT(type_, **kw) + def visit_double(self, type_, **kw): + return self.visit_DOUBLE_PRECISION(type_, **kw) + def visit_unicode(self, type_, **kw): if self.dialect._use_nchar_for_unicode: return self.visit_NVARCHAR2(type_, **kw) @@ -795,24 +842,50 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler): return self._generate_numeric(type_, "BINARY_FLOAT", **kw) def visit_FLOAT(self, type_, **kw): - # don't support conversion between decimal/binary - # precision yet - kw["no_precision"] = True + kw["_requires_binary_precision"] = True return self._generate_numeric(type_, "FLOAT", **kw) def visit_NUMBER(self, type_, **kw): return self._generate_numeric(type_, "NUMBER", **kw) def _generate_numeric( - self, type_, name, precision=None, scale=None, no_precision=False, **kw + self, + type_, + name, + precision=None, + scale=None, + _requires_binary_precision=False, + **kw, ): if precision is None: - precision = type_.precision + + precision = getattr(type_, "precision", None) + + if _requires_binary_precision: + binary_precision = getattr(type_, "binary_precision", None) + + if precision and binary_precision is None: + # https://www.oracletutorial.com/oracle-basics/oracle-float/ + estimated_binary_precision = int(precision / 0.30103) + raise exc.ArgumentError( + "Oracle FLOAT types use 'binary precision', which does " + "not convert cleanly from decimal 'precision'. Please " + "specify " + f"this type with a separate Oracle variant, such as " + f"{type_.__class__.__name__}(precision={precision})." + f"with_variant(oracle.FLOAT" + f"(binary_precision=" + f"{estimated_binary_precision}), 'oracle'), so that the " + "Oracle specific 'binary_precision' may be specified " + "accurately." + ) + else: + precision = binary_precision if scale is None: scale = getattr(type_, "scale", None) - if no_precision or precision is None: + if precision is None: return name elif scale is None: n = "%(name)s(%(precision)s)" @@ -1964,8 +2037,19 @@ class OracleDialect(default.DefaultDialect): else: coltype = NUMBER(precision, scale) elif coltype == "FLOAT": - # TODO: support "precision" here as "binary_precision" - coltype = FLOAT() + # https://docs.oracle.com/cd/B14117_01/server.101/b10758/sqlqr06.htm + if precision == 126: + # The DOUBLE PRECISION datatype is a floating-point + # number with binary precision 126. + coltype = DOUBLE_PRECISION() + elif precision == 63: + # The REAL datatype is a floating-point number with a + # binary precision of 63, or 18 decimal. + coltype = REAL() + else: + # non standard precision + coltype = FLOAT(binary_precision=precision) + elif coltype in ("VARCHAR2", "NVARCHAR2", "CHAR", "NCHAR"): coltype = self.ischema_names.get(coltype)(length) elif "WITH TIME ZONE" in coltype: |
