From 38af672fc062de9660bc50bdd04581cb06738da9 Mon Sep 17 00:00:00 2001 From: maca88 Date: Tue, 14 Apr 2020 23:29:09 +0200 Subject: [PATCH] Add support for Oracle binary floating point types --- .../Async/Linq/ByMethod/AverageTests.cs | 3 +- .../DriverTest/OracleClientDriverFixture.cs | 4 +- .../Linq/ByMethod/AverageTests.cs | 3 +- src/NHibernate/Cfg/Environment.cs | 14 ++++++ src/NHibernate/Dialect/Oracle10gDialect.cs | 48 ++++++++++++++----- src/NHibernate/Dialect/Oracle8iDialect.cs | 11 +++-- src/NHibernate/Driver/OracleClientDriver.cs | 4 ++ .../Driver/OracleDataClientDriverBase.cs | 22 +++++++++ src/NHibernate/nhibernate-configuration.xsd | 12 +++++ 9 files changed, 102 insertions(+), 19 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs index 7e9eaa14526..4839bde265e 100644 --- a/src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs +++ b/src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs @@ -22,6 +22,7 @@ public class AverageTestsAsync : LinqTestCase [Test] public async Task CanGetAverageOfIntegersAsDoubleAsync() { + // TODO 6.0: Enable test for Oracle once nhibernate.oracle.use_binary_floating_point_types is set to true if (Dialect is Oracle8iDialect) { // The point of this test is to verify that LINQ's Average over an @@ -50,4 +51,4 @@ public async Task CanGetAverageOfIntegersAsDoubleAsync() Assert.AreEqual(average, 10.129870d, 0.000001d); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs b/src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs index 7306e106840..1067f009224 100644 --- a/src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs +++ b/src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs @@ -9,6 +9,8 @@ namespace NHibernate.Test.DriverTest /// Summary description for OracleClientDriverFixture. /// [TestFixture] + // Since v5.3 + [Obsolete] public class OracleClientDriverFixture { /// @@ -35,4 +37,4 @@ public void CommandClassName() Assert.AreEqual("System.Data.OracleClient.OracleCommand", cmd.GetType().FullName); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Linq/ByMethod/AverageTests.cs b/src/NHibernate.Test/Linq/ByMethod/AverageTests.cs index d220cfece5f..dfb0890d324 100644 --- a/src/NHibernate.Test/Linq/ByMethod/AverageTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/AverageTests.cs @@ -10,6 +10,7 @@ public class AverageTests : LinqTestCase [Test] public void CanGetAverageOfIntegersAsDouble() { + // TODO 6.0: Enable test for Oracle once nhibernate.oracle.use_binary_floating_point_types is set to true if (Dialect is Oracle8iDialect) { // The point of this test is to verify that LINQ's Average over an @@ -38,4 +39,4 @@ public void CanGetAverageOfIntegersAsDouble() Assert.AreEqual(average, 10.129870d, 0.000001d); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index 55a21c43637..0c8e16f8cb0 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -319,6 +319,20 @@ public static string Version /// public const string OracleUseNPrefixedTypesForUnicode = "oracle.use_n_prefixed_types_for_unicode"; + /// + /// Oracle 10g introduced BINARY_DOUBLE and BINARY_FLOAT types which are compatible with .NET + /// and types, where FLOAT and DOUBLE are not. Oracle + /// FLOAT and DOUBLE types do not conform to the IEEE standard as they are internally implemented as + /// NUMBER type, which makes them an exact numeric type. + /// + /// by default. + /// + /// + /// + /// See https://docs.oracle.com/database/121/TTSQL/types.htm#TTSQL126 + /// + public const string OracleUseBinaryFloatingPointTypes = "oracle.use_binary_floating_point_types"; + /// /// /// Firebird with FirebirdSql.Data.FirebirdClient may be unable to determine the type diff --git a/src/NHibernate/Dialect/Oracle10gDialect.cs b/src/NHibernate/Dialect/Oracle10gDialect.cs index 1ad7f135b44..0495805c4ef 100644 --- a/src/NHibernate/Dialect/Oracle10gDialect.cs +++ b/src/NHibernate/Dialect/Oracle10gDialect.cs @@ -1,5 +1,10 @@ +using System.Collections.Generic; +using System.Data; +using NHibernate.Cfg; using NHibernate.Dialect.Function; using NHibernate.SqlCommand; +using NHibernate.SqlTypes; +using NHibernate.Util; namespace NHibernate.Dialect { @@ -12,11 +17,41 @@ namespace NHibernate.Dialect /// public class Oracle10gDialect : Oracle9iDialect { + private bool _useBinaryFloatingPointTypes; + public override JoinFragment CreateOuterJoinFragment() { return new ANSIJoinFragment(); } + public override void Configure(IDictionary settings) + { + base.Configure(settings); + + _useBinaryFloatingPointTypes = PropertiesHelper.GetBoolean( + Environment.OracleUseBinaryFloatingPointTypes, + settings, + false); + } + + // Avoid registering weighted double type when using binary floating point types + protected override void RegisterFloatingPointTypeMappings() + { + if (_useBinaryFloatingPointTypes) + { + // Use binary_float (available since 10g) instead of float. With Oracle, float is a decimal but + // with a precision expressed in number of bytes instead of digits. + RegisterColumnType(DbType.Single, "binary_float"); + // Using binary_double (available since 10g) instead of double precision. With Oracle, double + // precision is a float(126), which is a decimal with a 126 bytes precision. + RegisterColumnType(DbType.Double, "binary_double"); + } + else + { + base.RegisterFloatingPointTypeMappings(); + } + } + protected override void RegisterFunctions() { base.RegisterFunctions(); @@ -29,19 +64,6 @@ protected override void RegisterFunctions() RegisterFunction("random", new SQLFunctionTemplate(NHibernateUtil.Double, "cast(DBMS_RANDOM.VALUE() as binary_double)")); } - /* 6.0 TODO: consider redefining float and double registrations - protected override void RegisterNumericTypeMappings() - { - base.RegisterNumericTypeMappings(); - - // Use binary_float (available since 10g) instead of float. With Oracle, float is a decimal but - // with a precision expressed in number of bytes instead of digits. - RegisterColumnType(DbType.Single, "binary_float"); - // Using binary_double (available since 10g) instead of double precision. With Oracle, double - // precision is a float(126), which is a decimal with a 126 bytes precision. - RegisterColumnType(DbType.Double, "binary_double"); - }*/ - /// public override bool SupportsCrossJoin => true; } diff --git a/src/NHibernate/Dialect/Oracle8iDialect.cs b/src/NHibernate/Dialect/Oracle8iDialect.cs index 749c1f0d056..18d299bab10 100644 --- a/src/NHibernate/Dialect/Oracle8iDialect.cs +++ b/src/NHibernate/Dialect/Oracle8iDialect.cs @@ -102,6 +102,7 @@ public override void Configure(IDictionary settings) // If changing the default value, keep it in sync with OracleDataClientDriverBase.Configure. UseNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Environment.OracleUseNPrefixedTypesForUnicode, settings, false); RegisterCharacterTypeMappings(); + RegisterFloatingPointTypeMappings(); } #region private static readonly string[] DialectKeywords = { ... } @@ -184,14 +185,18 @@ protected virtual void RegisterNumericTypeMappings() // 6.0 TODO: bring down to 18,4 for consistency with other dialects. RegisterColumnType(DbType.Currency, "NUMBER(22,4)"); - RegisterColumnType(DbType.Single, "FLOAT(24)"); - RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); - RegisterColumnType(DbType.Double, 40, "NUMBER($p,$s)"); RegisterColumnType(DbType.Decimal, "NUMBER(19,5)"); // Oracle max precision is 39-40, but .Net is limited to 28-29. RegisterColumnType(DbType.Decimal, 29, "NUMBER($p,$s)"); } + protected virtual void RegisterFloatingPointTypeMappings() + { + RegisterColumnType(DbType.Single, "FLOAT(24)"); + RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); + RegisterColumnType(DbType.Double, 40, "NUMBER($p,$s)"); + } + protected virtual void RegisterDateTimeTypeMappings() { RegisterColumnType(DbType.Date, "DATE"); diff --git a/src/NHibernate/Driver/OracleClientDriver.cs b/src/NHibernate/Driver/OracleClientDriver.cs index 334a1f09f67..f3cf9d28dd3 100644 --- a/src/NHibernate/Driver/OracleClientDriver.cs +++ b/src/NHibernate/Driver/OracleClientDriver.cs @@ -1,3 +1,4 @@ +using System; using System.Data; using System.Data.Common; using NHibernate.Engine.Query; @@ -8,6 +9,9 @@ namespace NHibernate.Driver /// /// A NHibernate Driver for using the Oracle DataProvider. /// + // Since v5.3 + // Deprecated by Microsoft: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/oracle-and-adonet + [Obsolete("Use OracleManagedDataClientDriver or OracleDataClientDriver driver instead.")] public class OracleClientDriver : ReflectionBasedDriver { private static readonly SqlType GuidSqlType = new SqlType(DbType.Binary, 16); diff --git a/src/NHibernate/Driver/OracleDataClientDriverBase.cs b/src/NHibernate/Driver/OracleDataClientDriverBase.cs index f5e8620af6d..85f8cbdbd88 100644 --- a/src/NHibernate/Driver/OracleDataClientDriverBase.cs +++ b/src/NHibernate/Driver/OracleDataClientDriverBase.cs @@ -30,6 +30,8 @@ public abstract class OracleDataClientDriverBase : ReflectionBasedDriver, IEmbed private readonly object _oracleDbTypeBlob; private readonly object _oracleDbTypeNVarchar2; private readonly object _oracleDbTypeNChar; + private readonly object _oracleDbTypeBinaryDouble; + private readonly object _oracleDbTypeBinaryFloat; /// /// Default constructor. @@ -58,6 +60,8 @@ private OracleDataClientDriverBase(string driverAssemblyName, string clientNames _oracleDbTypeBlob = Enum.Parse(oracleDbTypeEnum, "Blob"); _oracleDbTypeNVarchar2 = Enum.Parse(oracleDbTypeEnum, "NVarchar2"); _oracleDbTypeNChar = Enum.Parse(oracleDbTypeEnum, "NChar"); + _oracleDbTypeBinaryDouble = Enum.Parse(oracleDbTypeEnum, "BinaryDouble"); + _oracleDbTypeBinaryFloat = Enum.Parse(oracleDbTypeEnum, "BinaryFloat"); } /// @@ -67,6 +71,7 @@ public override void Configure(IDictionary settings) // If changing the default value, keep it in sync with Oracle8iDialect.Configure. UseNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Cfg.Environment.OracleUseNPrefixedTypesForUnicode, settings, false); + UseBinaryFloatingPointTypes = PropertiesHelper.GetBoolean(Cfg.Environment.OracleUseBinaryFloatingPointTypes, settings, false); } /// @@ -84,6 +89,11 @@ public override void Configure(IDictionary settings) /// public bool UseNPrefixedTypesForUnicode { get; private set; } + /// + /// Whether binary_double and binary_float are used for and types. + /// + public bool UseBinaryFloatingPointTypes { get; private set; } + /// public override bool UseNamedPrefixInSql => true; @@ -131,6 +141,18 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq case DbType.Currency: base.InitializeParameter(dbParam, name, SqlTypeFactory.Decimal); break; + case DbType.Double: + if (UseBinaryFloatingPointTypes) + InitializeParameter(dbParam, name, _oracleDbTypeBinaryDouble); + else + base.InitializeParameter(dbParam, name, sqlType); + break; + case DbType.Single: + if (UseBinaryFloatingPointTypes) + InitializeParameter(dbParam, name, _oracleDbTypeBinaryFloat); + else + base.InitializeParameter(dbParam, name, sqlType); + break; default: base.InitializeParameter(dbParam, name, sqlType); break; diff --git a/src/NHibernate/nhibernate-configuration.xsd b/src/NHibernate/nhibernate-configuration.xsd index c8280b03de8..3eb35a76e87 100644 --- a/src/NHibernate/nhibernate-configuration.xsd +++ b/src/NHibernate/nhibernate-configuration.xsd @@ -240,6 +240,18 @@ + + + + Oracle 10g introduced BINARY_DOUBLE and BINARY_FLOAT types which are compatible with .NET + double and float types, where FLOAT and DOUBLE are not. Oracle FLOAT and DOUBLE types do + not conform to the IEEE standard as they are internally implemented as NUMBER type, which + makes them an exact numeric type. + False by default. + See https://docs.oracle.com/database/121/TTSQL/types.htm#TTSQL126 + + +