Skip to content

Commit e5701dc

Browse files
NH-4062 - Oracle Unicode support dual model.
1 parent 296c186 commit e5701dc

File tree

11 files changed

+331
-38
lines changed

11 files changed

+331
-38
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -782,15 +782,13 @@ public async Task GroupByComputedValueInObjectArrayWithJoinOnIdAsync()
782782
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));
783783
}
784784

785-
[Test(Description = "NH-3801")]
785+
[Test(Description = "NH-3801, NH-4062")]
786786
public async Task GroupByComputedValueInObjectArrayWithJoinInRightSideOfCaseAsync()
787787
{
788788
if (!TestDialect.SupportsComplexExpressionInGroupBy)
789789
Assert.Ignore(Dialect.GetType().Name + " does not support complex group by expressions");
790790
if (Sfi.ConnectionProvider.Driver is OdbcDriver)
791791
Assert.Ignore("SQL Server seems unable to match complex group by and select list arguments when running over ODBC.");
792-
if (Dialect is Oracle8iDialect)
793-
Assert.Ignore("ORA-12704: character set mismatch. Due to NHibernate creating Unicode string types as NVarchar2 but querying them as Varchar2.");
794792

795793
var orderGroups = await (db.OrderLines.GroupBy(o => new[] { o.Order.Customer.CustomerId == null ? "unknown" : o.Order.Customer.CompanyName }).Select(g => new { Key = g.Key, Count = g.Count() }).ToListAsync());
796794
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));

src/NHibernate.Test/DialectTest/Oracle8iDialectFixture.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using NHibernate.Dialect;
1+
using NHibernate.Cfg;
2+
using NHibernate.Dialect;
23
using NHibernate.SqlCommand;
4+
using NHibernate.Util;
35
using NUnit.Framework;
46

57
namespace NHibernate.Test.DialectTest
@@ -50,6 +52,7 @@ public void GetLimitStringWithTableNotStartingWithSelectKeywordAndDifferentCasin
5052
Assert.AreEqual(limited, expected);
5153
}
5254
#endregion
55+
5356
#region Offset And List
5457
[Test]
5558
public void GetLimitStringWithOffsetAndLimitAndTableStartingWithSelectKeyword()
@@ -93,5 +96,57 @@ public void GetLimitStringWithOffsetAndLimitAndTableNotStartingWithSelectKeyword
9396
Assert.AreEqual(limited, expected);
9497
}
9598
#endregion
99+
100+
#region Types
101+
102+
[Test]
103+
public void CheckUnicode()
104+
{
105+
var dialect = new Oracle8iDialect();
106+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
107+
dialect.Configure(cfg.Properties);
108+
var useNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Environment.OracleUseNPrefixedTypesForUnicode, cfg.Properties, false);
109+
110+
Assert.That(dialect.UseNPrefixedTypesForUnicode, Is.EqualTo(useNPrefixedTypesForUnicode),
111+
$"Unexpected value for {nameof(Oracle8iDialect)}.{nameof(Oracle8iDialect.UseNPrefixedTypesForUnicode)} after configuration");
112+
113+
var unicodeStringType = dialect.GetTypeName(NHibernateUtil.String.SqlType);
114+
Assert.That(unicodeStringType, (useNPrefixedTypesForUnicode ? Does.StartWith("N") : Does.Not.StartWith("N")).IgnoreCase,
115+
"Unexpected type name for an Unicode string");
116+
}
117+
118+
[Test]
119+
public void CheckUnicodeNoPrefix()
120+
{
121+
var dialect = new Oracle8iDialect();
122+
123+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
124+
cfg.SetProperty(Environment.OracleUseNPrefixedTypesForUnicode, "false");
125+
dialect.Configure(cfg.Properties);
126+
127+
Assert.That(dialect.UseNPrefixedTypesForUnicode, Is.False,
128+
$"Unexpected value for {nameof(Oracle8iDialect)}.{nameof(Oracle8iDialect.UseNPrefixedTypesForUnicode)} after configuration");
129+
130+
var unicodeStringType = dialect.GetTypeName(NHibernateUtil.String.SqlType);
131+
Assert.That(unicodeStringType, Does.Not.StartWith("N").IgnoreCase, "Unexpected type name for an Unicode string");
132+
}
133+
134+
[Test]
135+
public void CheckUnicodeWithPrefix()
136+
{
137+
var dialect = new Oracle8iDialect();
138+
139+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
140+
cfg.SetProperty(Environment.OracleUseNPrefixedTypesForUnicode, "true");
141+
dialect.Configure(cfg.Properties);
142+
143+
Assert.That(dialect.UseNPrefixedTypesForUnicode, Is.True,
144+
$"Unexpected value for {nameof(Oracle8iDialect)}.{nameof(Oracle8iDialect.UseNPrefixedTypesForUnicode)} after configuration");
145+
146+
var unicodeStringType = dialect.GetTypeName(NHibernateUtil.String.SqlType);
147+
Assert.That(unicodeStringType, Does.StartWith("N").IgnoreCase, "Unexpected type name for an Unicode string");
148+
}
149+
150+
#endregion
96151
}
97152
}
Lines changed: 124 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Data;
34
using System.Data.Common;
5+
using NHibernate.Dialect;
46
using NHibernate.Driver;
57
using NHibernate.SqlCommand;
68
using NHibernate.SqlTypes;
9+
using NHibernate.Util;
710
using NUnit.Framework;
11+
using Environment = NHibernate.Cfg.Environment;
812

913
namespace NHibernate.Test.DriverTest
1014
{
@@ -17,21 +21,134 @@ public class OracleDataClientDriverFixture
1721
/// </summary>
1822
[Test]
1923
[Category("ODP.NET")]
20-
[Explicit]
21-
public void NoBooleanParameters()
24+
[Theory]
25+
public void NoBooleanParameters(bool managed)
2226
{
23-
OracleDataClientDriver driver = new OracleDataClientDriver();
27+
var driver = GetDriver(managed, TestConfigurationHelper.GetDefaultConfiguration().Properties);
28+
var param = GetParameterForType(driver, SqlTypeFactory.Boolean);
2429

25-
SqlStringBuilder builder = new SqlStringBuilder();
30+
Assert.That(param.DbType, Is.Not.EqualTo(DbType.Boolean), "should not still be a DbType.Boolean");
31+
}
32+
33+
[Test]
34+
[Category("ODP.NET")]
35+
[Theory]
36+
public void UnicodeParameters(bool managed)
37+
{
38+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
39+
var driver = GetDriver(managed, cfg.Properties);
40+
var useNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Environment.OracleUseNPrefixedTypesForUnicode, cfg.Properties, false);
41+
42+
Assert.That(driver.UseNPrefixedTypesForUnicode, Is.EqualTo(useNPrefixedTypesForUnicode),
43+
$"Unexpected value for {nameof(OracleDataClientDriverBase)}.{nameof(OracleDataClientDriverBase.UseNPrefixedTypesForUnicode)}");
44+
45+
var param = GetParameterForType(driver, SqlTypeFactory.GetString(200));
46+
var oracleParamType = GetOracleParameterType(param);
47+
Assert.That(oracleParamType.ToString(), Is.EqualTo(useNPrefixedTypesForUnicode ? "NVarchar2" : "Varchar2").IgnoreCase,
48+
"Unexpected Unicode string parameter type");
49+
50+
param = GetParameterForType(driver, new StringFixedLengthSqlType(10));
51+
oracleParamType = GetOracleParameterType(param);
52+
Assert.That(oracleParamType.ToString(), Is.EqualTo(useNPrefixedTypesForUnicode ? "NChar" : "Char").IgnoreCase,
53+
"Unexpected Unicode string fixed length parameter type");
54+
}
55+
56+
[Test]
57+
[Category("ODP.NET")]
58+
[Theory]
59+
public void UnicodeParametersNoPrefix(bool managed)
60+
{
61+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
62+
cfg.SetProperty(Environment.OracleUseNPrefixedTypesForUnicode, "false");
63+
var driver = GetDriver(managed, cfg.Properties);
64+
65+
Assert.That(driver.UseNPrefixedTypesForUnicode, Is.False,
66+
$"Unexpected value for {nameof(OracleDataClientDriverBase)}.{nameof(OracleDataClientDriverBase.UseNPrefixedTypesForUnicode)}");
67+
68+
var param = GetParameterForType(driver, SqlTypeFactory.GetString(200));
69+
var oracleParamType = GetOracleParameterType(param);
70+
Assert.That(oracleParamType.ToString(), Is.EqualTo("Varchar2").IgnoreCase, "Unexpected Unicode string parameter type");
71+
72+
param = GetParameterForType(driver, new StringFixedLengthSqlType(10));
73+
oracleParamType = GetOracleParameterType(param);
74+
Assert.That(oracleParamType.ToString(), Is.EqualTo("Char").IgnoreCase, "Unexpected Unicode string fixed length parameter type");
75+
}
76+
77+
[Test]
78+
[Category("ODP.NET")]
79+
[Theory]
80+
public void UnicodeParametersWithPrefix(bool managed)
81+
{
82+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
83+
cfg.SetProperty(Environment.OracleUseNPrefixedTypesForUnicode, "true");
84+
var driver = GetDriver(managed, cfg.Properties);
85+
86+
Assert.That(driver.UseNPrefixedTypesForUnicode, Is.True,
87+
$"Unexpected value for {nameof(OracleDataClientDriverBase)}.{nameof(OracleDataClientDriverBase.UseNPrefixedTypesForUnicode)}");
88+
89+
var param = GetParameterForType(driver, SqlTypeFactory.GetString(200));
90+
var oracleParamType = GetOracleParameterType(param);
91+
Assert.That(oracleParamType.ToString(), Is.EqualTo("NVarchar2").IgnoreCase, "Unexpected Unicode string parameter type");
92+
93+
param = GetParameterForType(driver, new StringFixedLengthSqlType(10));
94+
oracleParamType = GetOracleParameterType(param);
95+
Assert.That(oracleParamType.ToString(), Is.EqualTo("NChar").IgnoreCase, "Unexpected Unicode string fixed length parameter type");
96+
}
97+
98+
[Test]
99+
[Category("ODP.NET")]
100+
[Theory]
101+
public void HasSameUnicodeDefaultThanDialect(bool managed)
102+
{
103+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
104+
cfg.Properties.Remove(Environment.OracleUseNPrefixedTypesForUnicode);
105+
var driver = GetDriver(managed, cfg.Properties);
106+
var dialect = new Oracle8iDialect();
107+
dialect.Configure(cfg.Properties);
108+
109+
Assert.That(driver.UseNPrefixedTypesForUnicode, Is.EqualTo(dialect.UseNPrefixedTypesForUnicode),
110+
$"Default {nameof(Oracle8iDialect.UseNPrefixedTypesForUnicode)} values mismatch between driver and dialect");
111+
}
112+
113+
114+
private static OracleDataClientDriverBase GetDriver(bool managed, IDictionary<string, string> settings)
115+
{
116+
OracleDataClientDriverBase driver = null;
117+
try
118+
{
119+
driver = managed
120+
? (OracleDataClientDriverBase)new OracleManagedDataClientDriver()
121+
: new OracleDataClientDriver();
122+
}
123+
catch (Exception ex)
124+
{
125+
Assert.Ignore("Unable to load the driver: {0}", ex);
126+
}
127+
128+
driver.Configure(settings);
129+
130+
return driver;
131+
}
132+
133+
private static DbParameter GetParameterForType(IDriver driver, SqlType paramType)
134+
{
135+
var builder = new SqlStringBuilder();
26136
builder.Add("select * from table1 where col1=");
27137
builder.Add(Parameter.Placeholder);
28138

29-
var cmd = driver.GenerateCommand(CommandType.Text, builder.ToSqlString(), new SqlType[] {SqlTypeFactory.Boolean});
139+
var cmd = driver.GenerateCommand(CommandType.Text, builder.ToSqlString(), new[] { paramType });
30140

141+
Assert.That(cmd.Parameters, Has.Count.EqualTo(1), "Unexpected parameters count");
31142
var param = cmd.Parameters[0];
143+
return param;
144+
}
32145

33-
Assert.AreEqual("col1", param.ParameterName, "kept same param name");
34-
Assert.IsFalse(param.DbType == DbType.Boolean, "should not still be a DbType.Boolean");
146+
private static object GetOracleParameterType(DbParameter dbParameter)
147+
{
148+
var parameterType = dbParameter.GetType();
149+
var typeProperty = parameterType.GetProperty("OracleDbType") ??
150+
throw new InvalidOperationException("Unable to find OracleDbType property");
151+
return typeProperty.GetValue(dbParameter);
35152
}
36153
}
37154
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -771,15 +771,13 @@ public void GroupByComputedValueInObjectArrayWithJoinOnId()
771771
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));
772772
}
773773

774-
[Test(Description = "NH-3801")]
774+
[Test(Description = "NH-3801, NH-4062")]
775775
public void GroupByComputedValueInObjectArrayWithJoinInRightSideOfCase()
776776
{
777777
if (!TestDialect.SupportsComplexExpressionInGroupBy)
778778
Assert.Ignore(Dialect.GetType().Name + " does not support complex group by expressions");
779779
if (Sfi.ConnectionProvider.Driver is OdbcDriver)
780780
Assert.Ignore("SQL Server seems unable to match complex group by and select list arguments when running over ODBC.");
781-
if (Dialect is Oracle8iDialect)
782-
Assert.Ignore("ORA-12704: character set mismatch. Due to NHibernate creating Unicode string types as NVarchar2 but querying them as Varchar2.");
783781

784782
var orderGroups = db.OrderLines.GroupBy(o => new[] { o.Order.Customer.CustomerId == null ? "unknown" : o.Order.Customer.CompanyName }).Select(g => new { Key = g.Key, Count = g.Count() }).ToList();
785783
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));

src/NHibernate/Cfg/Environment.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,20 @@ public static string Version
187187
/// </summary>
188188
public const string OdbcDateTimeScale = "odbc.explicit_datetime_scale";
189189

190+
/// <summary>
191+
/// <para>Oracle has a dual Unicode support model.</para>
192+
/// <para>Either the whole database use an Unicode encoding, and then all string types
193+
/// will be Unicode. In such case, Unicode strings should be mapped to non <c>N</c> prefixed
194+
/// types, such as <c>Varchar2</c>. This is the default.</para>
195+
/// <para>Or <c>N</c> prefixed types such as <c>NVarchar2</c> are to be used for Unicode strings.</para>
196+
/// </summary>
197+
/// <remarks>
198+
/// See https://docs.oracle.com/cd/B19306_01/server.102/b14225/ch6unicode.htm#CACHCAHF
199+
/// https://docs.oracle.com/database/121/ODPNT/featOraCommand.htm#i1007557
200+
/// This setting applies only to Oracle dialects and ODP.Net managed or unmanaged driver.
201+
/// </remarks>
202+
public const string OracleUseNPrefixedTypesForUnicode = "oracle.use_n_prefixed_types_for_unicode";
203+
190204
private static readonly Dictionary<string, string> GlobalProperties;
191205

192206
private static IBytecodeProvider BytecodeProviderInstance;

src/NHibernate/Dialect/Dialect.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public static Dialect GetDialect()
153153
{
154154
throw new HibernateException("The dialect was not set. Set the property 'dialect'.", e);
155155
}
156-
return InstantiateDialect(dialectName);
156+
return InstantiateDialect(dialectName, Environment.Properties);
157157
}
158158

159159
/// <summary>
@@ -166,7 +166,7 @@ public static Dialect GetDialect()
166166
public static Dialect GetDialect(IDictionary<string, string> props)
167167
{
168168
if (props == null)
169-
throw new ArgumentNullException("props");
169+
throw new ArgumentNullException(nameof(props));
170170
string dialectName;
171171
if (props.TryGetValue(Environment.Dialect, out dialectName) == false)
172172
throw new InvalidOperationException("Could not find the dialect in the configuration");
@@ -175,21 +175,31 @@ public static Dialect GetDialect(IDictionary<string, string> props)
175175
return GetDialect();
176176
}
177177

178-
return InstantiateDialect(dialectName);
178+
return InstantiateDialect(dialectName, props);
179179
}
180180

181-
private static Dialect InstantiateDialect(string dialectName)
181+
private static Dialect InstantiateDialect(string dialectName, IDictionary<string, string> props)
182182
{
183183
try
184184
{
185-
return (Dialect)Environment.BytecodeProvider.ObjectsFactory.CreateInstance(ReflectHelper.ClassForName(dialectName));
185+
var dialect = (Dialect)Environment.BytecodeProvider.ObjectsFactory.CreateInstance(ReflectHelper.ClassForName(dialectName));
186+
dialect.Configure(props);
187+
return dialect;
186188
}
187189
catch (Exception e)
188190
{
189191
throw new HibernateException("Could not instantiate dialect class " + dialectName, e);
190192
}
191193
}
192194

195+
/// <summary>
196+
/// Configure the dialect.
197+
/// </summary>
198+
/// <param name="settings">The configuration settings.</param>
199+
public virtual void Configure(IDictionary<string, string> settings)
200+
{
201+
}
202+
193203
#endregion
194204

195205
#region Database type mapping support

0 commit comments

Comments
 (0)