Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Oracle binary floating point types #2349

Merged
merged 1 commit into from
Apr 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -50,4 +51,4 @@ public async Task CanGetAverageOfIntegersAsDoubleAsync()
Assert.AreEqual(average, 10.129870d, 0.000001d);
}
}
}
}
4 changes: 3 additions & 1 deletion src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace NHibernate.Test.DriverTest
/// Summary description for OracleClientDriverFixture.
/// </summary>
[TestFixture]
// Since v5.3
[Obsolete]
public class OracleClientDriverFixture
{
/// <summary>
Expand All @@ -35,4 +37,4 @@ public void CommandClassName()
Assert.AreEqual("System.Data.OracleClient.OracleCommand", cmd.GetType().FullName);
}
}
}
}
3 changes: 2 additions & 1 deletion src/NHibernate.Test/Linq/ByMethod/AverageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -38,4 +39,4 @@ public void CanGetAverageOfIntegersAsDouble()
Assert.AreEqual(average, 10.129870d, 0.000001d);
}
}
}
}
14 changes: 14 additions & 0 deletions src/NHibernate/Cfg/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,20 @@ public static string Version
/// </remarks>
public const string OracleUseNPrefixedTypesForUnicode = "oracle.use_n_prefixed_types_for_unicode";

/// <summary>
/// Oracle 10g introduced BINARY_DOUBLE and BINARY_FLOAT types which are compatible with .NET
/// <see cref="double"/> and <see cref="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.
/// <para>
/// <see langword="false"/> by default.
/// </para>
/// </summary>
/// <remarks>
/// See https://docs.oracle.com/database/121/TTSQL/types.htm#TTSQL126
/// </remarks>
public const string OracleUseBinaryFloatingPointTypes = "oracle.use_binary_floating_point_types";

/// <summary>
/// <para>
/// Firebird with FirebirdSql.Data.FirebirdClient may be unable to determine the type
Expand Down
48 changes: 35 additions & 13 deletions src/NHibernate/Dialect/Oracle10gDialect.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -12,11 +17,41 @@ namespace NHibernate.Dialect
/// </remarks>
public class Oracle10gDialect : Oracle9iDialect
{
private bool _useBinaryFloatingPointTypes;

public override JoinFragment CreateOuterJoinFragment()
{
return new ANSIJoinFragment();
}

public override void Configure(IDictionary<string, string> 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();
Expand All @@ -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()
Copy link
Contributor Author

@maca88 maca88 Apr 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here _useBinaryFloatingPointTypes is not yet populated as RegisterNumericTypeMappings is called in the constructor.

{
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");
}*/

/// <inheritdoc />
public override bool SupportsCrossJoin => true;
}
Expand Down
11 changes: 8 additions & 3 deletions src/NHibernate/Dialect/Oracle8iDialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public override void Configure(IDictionary<string, string> 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 = { ... }
Expand Down Expand Up @@ -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");
Expand Down
4 changes: 4 additions & 0 deletions src/NHibernate/Driver/OracleClientDriver.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Data;
using System.Data.Common;
using NHibernate.Engine.Query;
Expand All @@ -8,6 +9,9 @@ namespace NHibernate.Driver
/// <summary>
/// A NHibernate Driver for using the Oracle DataProvider.
/// </summary>
// 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
hazzik marked this conversation as resolved.
Show resolved Hide resolved
{
private static readonly SqlType GuidSqlType = new SqlType(DbType.Binary, 16);
Expand Down
22 changes: 22 additions & 0 deletions src/NHibernate/Driver/OracleDataClientDriverBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// Default constructor.
Expand Down Expand Up @@ -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");
}

/// <inheritdoc/>
Expand All @@ -67,6 +71,7 @@ public override void Configure(IDictionary<string, string> 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);
}

/// <summary>
Expand All @@ -84,6 +89,11 @@ public override void Configure(IDictionary<string, string> settings)
/// </remarks>
public bool UseNPrefixedTypesForUnicode { get; private set; }

/// <summary>
/// Whether binary_double and binary_float are used for <see cref="double"/> and <see cref="float"/> types.
/// </summary>
public bool UseBinaryFloatingPointTypes { get; private set; }

/// <inheritdoc/>
public override bool UseNamedPrefixInSql => true;

Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/NHibernate/nhibernate-configuration.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,18 @@
</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="oracle.use_binary_floating_point_types">
<xs:annotation>
<xs:documentation>
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
</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="firebird.disable_parameter_casting">
<xs:annotation>
<xs:documentation>
Expand Down