Skip to content

Commit 697b6f7

Browse files
authored
Add support for Oracle binary floating point types (#2349)
1 parent 8bb204f commit 697b6f7

File tree

9 files changed

+102
-19
lines changed

9 files changed

+102
-19
lines changed

src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class AverageTestsAsync : LinqTestCase
2222
[Test]
2323
public async Task CanGetAverageOfIntegersAsDoubleAsync()
2424
{
25+
// TODO 6.0: Enable test for Oracle once nhibernate.oracle.use_binary_floating_point_types is set to true
2526
if (Dialect is Oracle8iDialect)
2627
{
2728
// The point of this test is to verify that LINQ's Average over an
@@ -50,4 +51,4 @@ public async Task CanGetAverageOfIntegersAsDoubleAsync()
5051
Assert.AreEqual(average, 10.129870d, 0.000001d);
5152
}
5253
}
53-
}
54+
}

src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace NHibernate.Test.DriverTest
99
/// Summary description for OracleClientDriverFixture.
1010
/// </summary>
1111
[TestFixture]
12+
// Since v5.3
13+
[Obsolete]
1214
public class OracleClientDriverFixture
1315
{
1416
/// <summary>
@@ -35,4 +37,4 @@ public void CommandClassName()
3537
Assert.AreEqual("System.Data.OracleClient.OracleCommand", cmd.GetType().FullName);
3638
}
3739
}
38-
}
40+
}

src/NHibernate.Test/Linq/ByMethod/AverageTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public class AverageTests : LinqTestCase
1010
[Test]
1111
public void CanGetAverageOfIntegersAsDouble()
1212
{
13+
// TODO 6.0: Enable test for Oracle once nhibernate.oracle.use_binary_floating_point_types is set to true
1314
if (Dialect is Oracle8iDialect)
1415
{
1516
// The point of this test is to verify that LINQ's Average over an
@@ -38,4 +39,4 @@ public void CanGetAverageOfIntegersAsDouble()
3839
Assert.AreEqual(average, 10.129870d, 0.000001d);
3940
}
4041
}
41-
}
42+
}

src/NHibernate/Cfg/Environment.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,20 @@ public static string Version
319319
/// </remarks>
320320
public const string OracleUseNPrefixedTypesForUnicode = "oracle.use_n_prefixed_types_for_unicode";
321321

322+
/// <summary>
323+
/// Oracle 10g introduced BINARY_DOUBLE and BINARY_FLOAT types which are compatible with .NET
324+
/// <see cref="double"/> and <see cref="float"/> types, where FLOAT and DOUBLE are not. Oracle
325+
/// FLOAT and DOUBLE types do not conform to the IEEE standard as they are internally implemented as
326+
/// NUMBER type, which makes them an exact numeric type.
327+
/// <para>
328+
/// <see langword="false"/> by default.
329+
/// </para>
330+
/// </summary>
331+
/// <remarks>
332+
/// See https://docs.oracle.com/database/121/TTSQL/types.htm#TTSQL126
333+
/// </remarks>
334+
public const string OracleUseBinaryFloatingPointTypes = "oracle.use_binary_floating_point_types";
335+
322336
/// <summary>
323337
/// <para>
324338
/// Firebird with FirebirdSql.Data.FirebirdClient may be unable to determine the type

src/NHibernate/Dialect/Oracle10gDialect.cs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
using System.Collections.Generic;
2+
using System.Data;
3+
using NHibernate.Cfg;
14
using NHibernate.Dialect.Function;
25
using NHibernate.SqlCommand;
6+
using NHibernate.SqlTypes;
7+
using NHibernate.Util;
38

49
namespace NHibernate.Dialect
510
{
@@ -12,11 +17,41 @@ namespace NHibernate.Dialect
1217
/// </remarks>
1318
public class Oracle10gDialect : Oracle9iDialect
1419
{
20+
private bool _useBinaryFloatingPointTypes;
21+
1522
public override JoinFragment CreateOuterJoinFragment()
1623
{
1724
return new ANSIJoinFragment();
1825
}
1926

27+
public override void Configure(IDictionary<string, string> settings)
28+
{
29+
base.Configure(settings);
30+
31+
_useBinaryFloatingPointTypes = PropertiesHelper.GetBoolean(
32+
Environment.OracleUseBinaryFloatingPointTypes,
33+
settings,
34+
false);
35+
}
36+
37+
// Avoid registering weighted double type when using binary floating point types
38+
protected override void RegisterFloatingPointTypeMappings()
39+
{
40+
if (_useBinaryFloatingPointTypes)
41+
{
42+
// Use binary_float (available since 10g) instead of float. With Oracle, float is a decimal but
43+
// with a precision expressed in number of bytes instead of digits.
44+
RegisterColumnType(DbType.Single, "binary_float");
45+
// Using binary_double (available since 10g) instead of double precision. With Oracle, double
46+
// precision is a float(126), which is a decimal with a 126 bytes precision.
47+
RegisterColumnType(DbType.Double, "binary_double");
48+
}
49+
else
50+
{
51+
base.RegisterFloatingPointTypeMappings();
52+
}
53+
}
54+
2055
protected override void RegisterFunctions()
2156
{
2257
base.RegisterFunctions();
@@ -29,19 +64,6 @@ protected override void RegisterFunctions()
2964
RegisterFunction("random", new SQLFunctionTemplate(NHibernateUtil.Double, "cast(DBMS_RANDOM.VALUE() as binary_double)"));
3065
}
3166

32-
/* 6.0 TODO: consider redefining float and double registrations
33-
protected override void RegisterNumericTypeMappings()
34-
{
35-
base.RegisterNumericTypeMappings();
36-
37-
// Use binary_float (available since 10g) instead of float. With Oracle, float is a decimal but
38-
// with a precision expressed in number of bytes instead of digits.
39-
RegisterColumnType(DbType.Single, "binary_float");
40-
// Using binary_double (available since 10g) instead of double precision. With Oracle, double
41-
// precision is a float(126), which is a decimal with a 126 bytes precision.
42-
RegisterColumnType(DbType.Double, "binary_double");
43-
}*/
44-
4567
/// <inheritdoc />
4668
public override bool SupportsCrossJoin => true;
4769
}

src/NHibernate/Dialect/Oracle8iDialect.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ public override void Configure(IDictionary<string, string> settings)
102102
// If changing the default value, keep it in sync with OracleDataClientDriverBase.Configure.
103103
UseNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Environment.OracleUseNPrefixedTypesForUnicode, settings, false);
104104
RegisterCharacterTypeMappings();
105+
RegisterFloatingPointTypeMappings();
105106
}
106107

107108
#region private static readonly string[] DialectKeywords = { ... }
@@ -184,14 +185,18 @@ protected virtual void RegisterNumericTypeMappings()
184185

185186
// 6.0 TODO: bring down to 18,4 for consistency with other dialects.
186187
RegisterColumnType(DbType.Currency, "NUMBER(22,4)");
187-
RegisterColumnType(DbType.Single, "FLOAT(24)");
188-
RegisterColumnType(DbType.Double, "DOUBLE PRECISION");
189-
RegisterColumnType(DbType.Double, 40, "NUMBER($p,$s)");
190188
RegisterColumnType(DbType.Decimal, "NUMBER(19,5)");
191189
// Oracle max precision is 39-40, but .Net is limited to 28-29.
192190
RegisterColumnType(DbType.Decimal, 29, "NUMBER($p,$s)");
193191
}
194192

193+
protected virtual void RegisterFloatingPointTypeMappings()
194+
{
195+
RegisterColumnType(DbType.Single, "FLOAT(24)");
196+
RegisterColumnType(DbType.Double, "DOUBLE PRECISION");
197+
RegisterColumnType(DbType.Double, 40, "NUMBER($p,$s)");
198+
}
199+
195200
protected virtual void RegisterDateTimeTypeMappings()
196201
{
197202
RegisterColumnType(DbType.Date, "DATE");

src/NHibernate/Driver/OracleClientDriver.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Data;
23
using System.Data.Common;
34
using NHibernate.Engine.Query;
@@ -8,6 +9,9 @@ namespace NHibernate.Driver
89
/// <summary>
910
/// A NHibernate Driver for using the Oracle DataProvider.
1011
/// </summary>
12+
// Since v5.3
13+
// Deprecated by Microsoft: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/oracle-and-adonet
14+
[Obsolete("Use OracleManagedDataClientDriver or OracleDataClientDriver driver instead.")]
1115
public class OracleClientDriver : ReflectionBasedDriver
1216
{
1317
private static readonly SqlType GuidSqlType = new SqlType(DbType.Binary, 16);

src/NHibernate/Driver/OracleDataClientDriverBase.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public abstract class OracleDataClientDriverBase : ReflectionBasedDriver, IEmbed
3030
private readonly object _oracleDbTypeBlob;
3131
private readonly object _oracleDbTypeNVarchar2;
3232
private readonly object _oracleDbTypeNChar;
33+
private readonly object _oracleDbTypeBinaryDouble;
34+
private readonly object _oracleDbTypeBinaryFloat;
3335

3436
/// <summary>
3537
/// Default constructor.
@@ -58,6 +60,8 @@ private OracleDataClientDriverBase(string driverAssemblyName, string clientNames
5860
_oracleDbTypeBlob = Enum.Parse(oracleDbTypeEnum, "Blob");
5961
_oracleDbTypeNVarchar2 = Enum.Parse(oracleDbTypeEnum, "NVarchar2");
6062
_oracleDbTypeNChar = Enum.Parse(oracleDbTypeEnum, "NChar");
63+
_oracleDbTypeBinaryDouble = Enum.Parse(oracleDbTypeEnum, "BinaryDouble");
64+
_oracleDbTypeBinaryFloat = Enum.Parse(oracleDbTypeEnum, "BinaryFloat");
6165
}
6266

6367
/// <inheritdoc/>
@@ -67,6 +71,7 @@ public override void Configure(IDictionary<string, string> settings)
6771

6872
// If changing the default value, keep it in sync with Oracle8iDialect.Configure.
6973
UseNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Cfg.Environment.OracleUseNPrefixedTypesForUnicode, settings, false);
74+
UseBinaryFloatingPointTypes = PropertiesHelper.GetBoolean(Cfg.Environment.OracleUseBinaryFloatingPointTypes, settings, false);
7075
}
7176

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

92+
/// <summary>
93+
/// Whether binary_double and binary_float are used for <see cref="double"/> and <see cref="float"/> types.
94+
/// </summary>
95+
public bool UseBinaryFloatingPointTypes { get; private set; }
96+
8797
/// <inheritdoc/>
8898
public override bool UseNamedPrefixInSql => true;
8999

@@ -131,6 +141,18 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq
131141
case DbType.Currency:
132142
base.InitializeParameter(dbParam, name, SqlTypeFactory.Decimal);
133143
break;
144+
case DbType.Double:
145+
if (UseBinaryFloatingPointTypes)
146+
InitializeParameter(dbParam, name, _oracleDbTypeBinaryDouble);
147+
else
148+
base.InitializeParameter(dbParam, name, sqlType);
149+
break;
150+
case DbType.Single:
151+
if (UseBinaryFloatingPointTypes)
152+
InitializeParameter(dbParam, name, _oracleDbTypeBinaryFloat);
153+
else
154+
base.InitializeParameter(dbParam, name, sqlType);
155+
break;
134156
default:
135157
base.InitializeParameter(dbParam, name, sqlType);
136158
break;

src/NHibernate/nhibernate-configuration.xsd

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,18 @@
240240
</xs:documentation>
241241
</xs:annotation>
242242
</xs:enumeration>
243+
<xs:enumeration value="oracle.use_binary_floating_point_types">
244+
<xs:annotation>
245+
<xs:documentation>
246+
Oracle 10g introduced BINARY_DOUBLE and BINARY_FLOAT types which are compatible with .NET
247+
double and float types, where FLOAT and DOUBLE are not. Oracle FLOAT and DOUBLE types do
248+
not conform to the IEEE standard as they are internally implemented as NUMBER type, which
249+
makes them an exact numeric type.
250+
False by default.
251+
See https://docs.oracle.com/database/121/TTSQL/types.htm#TTSQL126
252+
</xs:documentation>
253+
</xs:annotation>
254+
</xs:enumeration>
243255
<xs:enumeration value="firebird.disable_parameter_casting">
244256
<xs:annotation>
245257
<xs:documentation>

0 commit comments

Comments
 (0)